summaryrefslogtreecommitdiff
path: root/t
diff options
context:
space:
mode:
Diffstat (limited to 't')
-rw-r--r--t/README37
-rw-r--r--t/helper/test-dump-untracked-cache.c4
-rw-r--r--t/helper/test-hashmap.c53
-rw-r--r--t/helper/test-wildmatch.c2
-rw-r--r--t/lib-terminal.sh4
-rwxr-xr-xt/perf/aggregate.perl116
-rwxr-xr-xt/perf/run8
-rwxr-xr-xt/t0002-gitfile.sh53
-rwxr-xr-xt/t0008-ignores.sh22
-rwxr-xr-xt/t0040-parse-options.sh2
-rwxr-xr-xt/t0041-usage.sh107
-rwxr-xr-xt/t0050-filesystem.sh16
-rwxr-xr-xt/t0205-gettext-poison.sh4
-rwxr-xr-xt/t1011-read-tree-sparse-checkout.sh7
-rwxr-xr-xt/t1300-repo-config.sh29
-rwxr-xr-xt/t1304-default-acl.sh2
-rwxr-xr-xt/t1405-main-ref-store.sh4
-rwxr-xr-xt/t1411-reflog-show.sh21
-rwxr-xr-xt/t1506-rev-parse-diagnosis.sh2
-rwxr-xr-xt/t1507-rev-parse-upstream.sh19
-rwxr-xr-xt/t1510-repo-setup.sh4
-rwxr-xr-xt/t2020-checkout-detach.sh40
-rwxr-xr-xt/t2025-worktree-add.sh54
-rwxr-xr-xt/t2026-worktree-prune.sh7
-rwxr-xr-xt/t2028-worktree-move.sh85
-rwxr-xr-xt/t2101-update-index-reupdate.sh91
-rwxr-xr-xt/t2107-update-index-basic.sh6
-rwxr-xr-xt/t3030-merge-recursive.sh36
-rwxr-xr-xt/t3070-wildmatch.sh655
-rwxr-xr-xt/t3200-branch.sh2
-rwxr-xr-xt/t3400-rebase.sh34
-rwxr-xr-xt/t3404-rebase-interactive.sh14
-rwxr-xr-xt/t3405-rebase-malformed.sh23
-rwxr-xr-xt/t3408-rebase-multi-line.sh26
-rwxr-xr-xt/t3501-revert-cherry-pick.sh5
-rwxr-xr-xt/t3701-add-interactive.sh315
-rwxr-xr-xt/t3905-stash-include-untracked.sh52
-rwxr-xr-xt/t4001-diff-rename.sh11
-rwxr-xr-xt/t4013-diff-various.sh5
-rw-r--r--t/t4013/diff.diff-tree_--pretty_--root_--stat_--compact-summary_initial12
-rw-r--r--t/t4013/diff.diff-tree_--pretty_-R_--root_--stat_--compact-summary_initial12
-rw-r--r--t/t4013/diff.diff-tree_--stat_--compact-summary_initial_mode4
-rw-r--r--t/t4013/diff.diff-tree_-R_--stat_--compact-summary_initial_mode4
-rwxr-xr-xt/t4018-diff-funcname.sh1
-rw-r--r--t/t4018/golang-complex-function8
-rw-r--r--t/t4018/golang-func4
-rw-r--r--t/t4018/golang-interface4
-rw-r--r--t/t4018/golang-long-func5
-rw-r--r--t/t4018/golang-struct4
-rwxr-xr-xt/t4052-stat-output.sh46
-rwxr-xr-xt/t4135-apply-weird-filenames.sh17
-rwxr-xr-xt/t4150-am.sh17
-rwxr-xr-xt/t4151-am-abort.sh5
-rwxr-xr-xt/t4201-shortlog.sh5
-rwxr-xr-xt/t5302-pack-index.sh5
-rwxr-xr-xt/t5500-fetch-pack.sh12
-rwxr-xr-xt/t5510-fetch.sh247
-rwxr-xr-xt/t5526-fetch-submodules.sh4
-rwxr-xr-xt/t5536-fetch-conflicts.sh22
-rwxr-xr-xt/t5541-http-push-smart.sh2
-rwxr-xr-xt/t5545-push-options.sh40
-rwxr-xr-xt/t5570-git-daemon.sh2
-rwxr-xr-xt/t5616-partial-clone.sh11
-rwxr-xr-xt/t5812-proto-disable-http.sh5
-rwxr-xr-xt/t6015-rev-list-show-all-parents.sh31
-rwxr-xr-xt/t6022-merge-rename.sh6
-rwxr-xr-xt/t6040-tracking-info.sh42
-rwxr-xr-xt/t6043-merge-rename-directories.sh3998
-rwxr-xr-xt/t6120-describe.sh8
-rwxr-xr-xt/t6200-fmt-merge-msg.sh2
-rwxr-xr-xt/t6300-for-each-ref.sh7
-rwxr-xr-xt/t7001-mv.sh6
-rwxr-xr-xt/t7003-filter-branch.sh14
-rwxr-xr-xt/t7004-tag.sh30
-rwxr-xr-xt/t7006-pager.sh49
-rwxr-xr-xt/t7063-status-untracked-cache.sh90
-rwxr-xr-xt/t7064-wtstatus-pv2.sh62
-rwxr-xr-xt/t7409-submodule-detached-work-tree.sh2
-rwxr-xr-xt/t7519-status-fsmonitor.sh39
-rwxr-xr-xt/t7600-merge.sh38
-rwxr-xr-xt/t7607-merge-overwrite.sh7
-rwxr-xr-xt/t9001-send-email.sh2
-rwxr-xr-xt/t9400-git-cvsserver-server.sh8
-rwxr-xr-xt/t9402-git-cvsserver-refs.sh8
-rwxr-xr-xt/t9902-completion.sh49
-rwxr-xr-xt/t9903-bash-prompt.sh14
-rw-r--r--t/test-lib-functions.sh101
-rw-r--r--t/test-lib.sh50
88 files changed, 6296 insertions, 842 deletions
diff --git a/t/README b/t/README
index b3f7b449c3..24ddebfabf 100644
--- a/t/README
+++ b/t/README
@@ -84,9 +84,10 @@ appropriately before running "make".
-x::
Turn on shell tracing (i.e., `set -x`) during the tests
- themselves. Implies `--verbose`. Note that in non-bash shells,
- this can cause failures in some tests which redirect and test
- the output of shell functions. Use with caution.
+ themselves. Implies `--verbose`.
+ Ignored in test scripts that set the variable 'test_untraceable'
+ to a non-empty value, unless it's run with a Bash version
+ supporting BASH_XTRACEFD, i.e. v4.1 or later.
-d::
--debug::
@@ -452,6 +453,22 @@ Don't:
causing the next test to start in an unexpected directory. Do so
inside a subshell if necessary.
+ - save and verify the standard error of compound commands, i.e. group
+ commands, subshells, and shell functions (except test helper
+ functions like 'test_must_fail') like this:
+
+ ( cd dir && git cmd ) 2>error &&
+ test_cmp expect error
+
+ When running the test with '-x' tracing, then the trace of commands
+ executed in the compound command will be included in standard error
+ as well, quite possibly throwing off the subsequent checks examining
+ the output. Instead, save only the relevant git command's standard
+ error:
+
+ ( cd dir && git cmd 2>../error ) &&
+ test_cmp expect error
+
- Break the TAP output
The raw output from your test may be interpreted by a TAP harness. TAP
@@ -655,7 +672,7 @@ library for your script to use.
test_expect_code 1 git merge "merge msg" B master
'
- - test_must_fail <git-command>
+ - test_must_fail [<options>] <git-command>
Run a git command and ensure it fails in a controlled way. Use
this instead of "! <git-command>". When git-command dies due to a
@@ -663,11 +680,21 @@ library for your script to use.
treats it as just another expected failure, which would let such a
bug go unnoticed.
- - test_might_fail <git-command>
+ Accepts the following options:
+
+ ok=<signal-name>[,<...>]:
+ Don't treat an exit caused by the given signal as error.
+ Multiple signals can be specified as a comma separated list.
+ Currently recognized signal names are: sigpipe, success.
+ (Don't use 'success', use 'test_might_fail' instead.)
+
+ - test_might_fail [<options>] <git-command>
Similar to test_must_fail, but tolerate success, too. Use this
instead of "<git-command> || :" to catch failures due to segv.
+ Accepts the same options as test_must_fail.
+
- test_cmp <expected> <actual>
Check whether the content of the <actual> file matches the
diff --git a/t/helper/test-dump-untracked-cache.c b/t/helper/test-dump-untracked-cache.c
index f752532ffb..d7c55c2355 100644
--- a/t/helper/test-dump-untracked-cache.c
+++ b/t/helper/test-dump-untracked-cache.c
@@ -54,8 +54,8 @@ int cmd_main(int ac, const char **av)
printf("no untracked cache\n");
return 0;
}
- printf("info/exclude %s\n", sha1_to_hex(uc->ss_info_exclude.sha1));
- printf("core.excludesfile %s\n", sha1_to_hex(uc->ss_excludes_file.sha1));
+ printf("info/exclude %s\n", oid_to_hex(&uc->ss_info_exclude.oid));
+ printf("core.excludesfile %s\n", oid_to_hex(&uc->ss_excludes_file.oid));
printf("exclude_per_dir %s\n", uc->exclude_per_dir);
printf("flags %08x\n", uc->dir_flags);
if (uc->root)
diff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c
index 1145d51671..9ae9281c07 100644
--- a/t/helper/test-hashmap.c
+++ b/t/helper/test-hashmap.c
@@ -1,5 +1,6 @@
#include "git-compat-util.h"
#include "hashmap.h"
+#include "strbuf.h"
struct test_entry
{
@@ -29,11 +30,12 @@ static int test_entry_cmp(const void *cmp_data,
return strcmp(e1->key, key ? key : e2->key);
}
-static struct test_entry *alloc_test_entry(int hash, char *key, int klen,
- char *value, int vlen)
+static struct test_entry *alloc_test_entry(unsigned int hash,
+ char *key, char *value)
{
- struct test_entry *entry = malloc(sizeof(struct test_entry) + klen
- + vlen + 2);
+ size_t klen = strlen(key);
+ size_t vlen = strlen(value);
+ struct test_entry *entry = xmalloc(st_add4(sizeof(*entry), klen, vlen, 2));
hashmap_entry_init(entry, hash);
memcpy(entry->key, key, klen + 1);
memcpy(entry->key + klen + 1, value, vlen + 1);
@@ -85,11 +87,11 @@ static void perf_hashmap(unsigned int method, unsigned int rounds)
unsigned int *hashes;
unsigned int i, j;
- entries = malloc(TEST_SIZE * sizeof(struct test_entry *));
- hashes = malloc(TEST_SIZE * sizeof(int));
+ ALLOC_ARRAY(entries, TEST_SIZE);
+ ALLOC_ARRAY(hashes, TEST_SIZE);
for (i = 0; i < TEST_SIZE; i++) {
- snprintf(buf, sizeof(buf), "%i", i);
- entries[i] = alloc_test_entry(0, buf, strlen(buf), "", 0);
+ xsnprintf(buf, sizeof(buf), "%i", i);
+ entries[i] = alloc_test_entry(0, buf, "");
hashes[i] = hash(method, i, entries[i]->key);
}
@@ -144,7 +146,7 @@ static void perf_hashmap(unsigned int method, unsigned int rounds)
*/
int cmd_main(int argc, const char **argv)
{
- char line[1024];
+ struct strbuf line = STRBUF_INIT;
struct hashmap map;
int icase;
@@ -153,44 +155,42 @@ int cmd_main(int argc, const char **argv)
hashmap_init(&map, test_entry_cmp, &icase, 0);
/* process commands from stdin */
- while (fgets(line, sizeof(line), stdin)) {
+ while (strbuf_getline(&line, stdin) != EOF) {
char *cmd, *p1 = NULL, *p2 = NULL;
- int l1 = 0, l2 = 0, hash = 0;
+ unsigned int hash = 0;
struct test_entry *entry;
/* break line into command and up to two parameters */
- cmd = strtok(line, DELIM);
+ cmd = strtok(line.buf, DELIM);
/* ignore empty lines */
if (!cmd || *cmd == '#')
continue;
p1 = strtok(NULL, DELIM);
if (p1) {
- l1 = strlen(p1);
hash = icase ? strihash(p1) : strhash(p1);
p2 = strtok(NULL, DELIM);
- if (p2)
- l2 = strlen(p2);
}
- if (!strcmp("hash", cmd) && l1) {
+ if (!strcmp("hash", cmd) && p1) {
/* print results of different hash functions */
- printf("%u %u %u %u\n", strhash(p1), memhash(p1, l1),
- strihash(p1), memihash(p1, l1));
+ printf("%u %u %u %u\n",
+ strhash(p1), memhash(p1, strlen(p1)),
+ strihash(p1), memihash(p1, strlen(p1)));
- } else if (!strcmp("add", cmd) && l1 && l2) {
+ } else if (!strcmp("add", cmd) && p1 && p2) {
/* create entry with key = p1, value = p2 */
- entry = alloc_test_entry(hash, p1, l1, p2, l2);
+ entry = alloc_test_entry(hash, p1, p2);
/* add to hashmap */
hashmap_add(&map, entry);
- } else if (!strcmp("put", cmd) && l1 && l2) {
+ } else if (!strcmp("put", cmd) && p1 && p2) {
/* create entry with key = p1, value = p2 */
- entry = alloc_test_entry(hash, p1, l1, p2, l2);
+ entry = alloc_test_entry(hash, p1, p2);
/* add / replace entry */
entry = hashmap_put(&map, entry);
@@ -199,7 +199,7 @@ int cmd_main(int argc, const char **argv)
puts(entry ? get_value(entry) : "NULL");
free(entry);
- } else if (!strcmp("get", cmd) && l1) {
+ } else if (!strcmp("get", cmd) && p1) {
/* lookup entry in hashmap */
entry = hashmap_get_from_hash(&map, hash, p1);
@@ -212,7 +212,7 @@ int cmd_main(int argc, const char **argv)
entry = hashmap_get_next(&map, entry);
}
- } else if (!strcmp("remove", cmd) && l1) {
+ } else if (!strcmp("remove", cmd) && p1) {
/* setup static key */
struct hashmap_entry key;
@@ -238,7 +238,7 @@ int cmd_main(int argc, const char **argv)
printf("%u %u\n", map.tablesize,
hashmap_get_size(&map));
- } else if (!strcmp("intern", cmd) && l1) {
+ } else if (!strcmp("intern", cmd) && p1) {
/* test that strintern works */
const char *i1 = strintern(p1);
@@ -252,7 +252,7 @@ int cmd_main(int argc, const char **argv)
else
printf("%s\n", i1);
- } else if (!strcmp("perfhashmap", cmd) && l1 && l2) {
+ } else if (!strcmp("perfhashmap", cmd) && p1 && p2) {
perf_hashmap(atoi(p1), atoi(p2));
@@ -263,6 +263,7 @@ int cmd_main(int argc, const char **argv)
}
}
+ strbuf_release(&line);
hashmap_free(&map, 1);
return 0;
}
diff --git a/t/helper/test-wildmatch.c b/t/helper/test-wildmatch.c
index 921d7b3e7e..66d33dfcfd 100644
--- a/t/helper/test-wildmatch.c
+++ b/t/helper/test-wildmatch.c
@@ -16,6 +16,8 @@ int cmd_main(int argc, const char **argv)
return !!wildmatch(argv[3], argv[2], WM_PATHNAME | WM_CASEFOLD);
else if (!strcmp(argv[1], "pathmatch"))
return !!wildmatch(argv[3], argv[2], 0);
+ else if (!strcmp(argv[1], "ipathmatch"))
+ return !!wildmatch(argv[3], argv[2], WM_CASEFOLD);
else
return 1;
}
diff --git a/t/lib-terminal.sh b/t/lib-terminal.sh
index cd220e378e..e3809dcead 100644
--- a/t/lib-terminal.sh
+++ b/t/lib-terminal.sh
@@ -9,8 +9,8 @@ test_terminal () {
echo >&4 "test_terminal: need to declare TTY prerequisite"
return 127
fi
- perl "$TEST_DIRECTORY"/test-terminal.perl "$@"
-}
+ perl "$TEST_DIRECTORY"/test-terminal.perl "$@" 2>&7
+} 7>&2 2>&4
test_lazy_prereq TTY '
test_have_prereq PERL &&
diff --git a/t/perf/aggregate.perl b/t/perf/aggregate.perl
index 3a0917fa61..48637ef64b 100755
--- a/t/perf/aggregate.perl
+++ b/t/perf/aggregate.perl
@@ -36,7 +36,8 @@ sub format_times {
return $out;
}
-my (@dirs, %dirnames, %dirabbrevs, %prefixes, @tests, $codespeed);
+my (@dirs, %dirnames, %dirabbrevs, %prefixes, @tests,
+ $codespeed, $sortby, $subsection, $reponame);
while (scalar @ARGV) {
my $arg = $ARGV[0];
my $dir;
@@ -45,6 +46,36 @@ while (scalar @ARGV) {
shift @ARGV;
next;
}
+ if ($arg =~ /--sort-by(?:=(.*))?/) {
+ shift @ARGV;
+ if (defined $1) {
+ $sortby = $1;
+ } else {
+ $sortby = shift @ARGV;
+ if (! defined $sortby) {
+ die "'--sort-by' requires an argument";
+ }
+ }
+ next;
+ }
+ if ($arg eq "--subsection") {
+ shift @ARGV;
+ $subsection = $ARGV[0];
+ shift @ARGV;
+ if (! $subsection) {
+ die "empty subsection";
+ }
+ next;
+ }
+ if ($arg eq "--reponame") {
+ shift @ARGV;
+ $reponame = $ARGV[0];
+ shift @ARGV;
+ if (! $reponame) {
+ die "empty reponame";
+ }
+ next;
+ }
last if -f $arg or $arg eq "--";
if (! -d $arg) {
my $rev = Git::command_oneline(qw(rev-parse --verify), $arg);
@@ -76,10 +107,15 @@ if (not @tests) {
}
my $resultsdir = "test-results";
-my $results_section = "";
-if (exists $ENV{GIT_PERF_SUBSECTION} and $ENV{GIT_PERF_SUBSECTION} ne "") {
- $resultsdir .= "/" . $ENV{GIT_PERF_SUBSECTION};
- $results_section = $ENV{GIT_PERF_SUBSECTION};
+
+if (! $subsection and
+ exists $ENV{GIT_PERF_SUBSECTION} and
+ $ENV{GIT_PERF_SUBSECTION} ne "") {
+ $subsection = $ENV{GIT_PERF_SUBSECTION};
+}
+
+if ($subsection) {
+ $resultsdir .= "/" . $subsection;
}
my @subtests;
@@ -123,6 +159,11 @@ sub have_slash {
return 0;
}
+sub display_dir {
+ my ($d) = @_;
+ return exists $dirabbrevs{$d} ? $dirabbrevs{$d} : $dirnames{$d};
+}
+
sub print_default_results {
my %descrs;
my $descrlen = 4; # "Test"
@@ -144,8 +185,7 @@ sub print_default_results {
my %times;
my @colwidth = ((0)x@dirs);
for my $i (0..$#dirs) {
- my $d = $dirs[$i];
- my $w = length (exists $dirabbrevs{$d} ? $dirabbrevs{$d} : $dirnames{$d});
+ my $w = length display_dir($dirs[$i]);
$colwidth[$i] = $w if $w > $colwidth[$i];
}
for my $t (@subtests) {
@@ -164,8 +204,7 @@ sub print_default_results {
printf "%-${descrlen}s", "Test";
for my $i (0..$#dirs) {
- my $d = $dirs[$i];
- printf " %-$colwidth[$i]s", (exists $dirabbrevs{$d} ? $dirabbrevs{$d} : $dirnames{$d});
+ printf " %-$colwidth[$i]s", display_dir($dirs[$i]);
}
print "\n";
print "-"x$totalwidth, "\n";
@@ -182,20 +221,65 @@ sub print_default_results {
}
}
+sub print_sorted_results {
+ my ($sortby) = @_;
+
+ if ($sortby ne "regression") {
+ die "only 'regression' is supported as '--sort-by' argument";
+ }
+
+ my @evolutions;
+ for my $t (@subtests) {
+ my ($prevr, $prevu, $prevs, $prevrev);
+ for my $i (0..$#dirs) {
+ my $d = $dirs[$i];
+ my ($r, $u, $s) = get_times("$resultsdir/$prefixes{$d}$t.times");
+ if ($i > 0 and defined $r and defined $prevr and $prevr > 0) {
+ my $percent = 100.0 * ($r - $prevr) / $prevr;
+ push @evolutions, { "percent" => $percent,
+ "test" => $t,
+ "prevrev" => $prevrev,
+ "rev" => $d,
+ "prevr" => $prevr,
+ "r" => $r,
+ "prevu" => $prevu,
+ "u" => $u,
+ "prevs" => $prevs,
+ "s" => $s};
+ }
+ ($prevr, $prevu, $prevs, $prevrev) = ($r, $u, $s, $d);
+ }
+ }
+
+ my @sorted_evolutions = sort { $b->{percent} <=> $a->{percent} } @evolutions;
+
+ for my $e (@sorted_evolutions) {
+ printf "%+.1f%%", $e->{percent};
+ print " " . $e->{test};
+ print " " . format_times($e->{prevr}, $e->{prevu}, $e->{prevs});
+ print " " . format_times($e->{r}, $e->{u}, $e->{s});
+ print " " . display_dir($e->{prevrev});
+ print " " . display_dir($e->{rev});
+ print "\n";
+ }
+}
+
sub print_codespeed_results {
- my ($results_section) = @_;
+ my ($subsection) = @_;
my $project = "Git";
my $executable = `uname -s -m`;
chomp $executable;
- if ($results_section ne "") {
- $executable .= ", " . $results_section;
+ if ($subsection) {
+ $executable .= ", " . $subsection;
}
my $environment;
- if (exists $ENV{GIT_PERF_REPO_NAME} and $ENV{GIT_PERF_REPO_NAME} ne "") {
+ if ($reponame) {
+ $environment = $reponame;
+ } elsif (exists $ENV{GIT_PERF_REPO_NAME} and $ENV{GIT_PERF_REPO_NAME} ne "") {
$environment = $ENV{GIT_PERF_REPO_NAME};
} elsif (exists $ENV{GIT_TEST_INSTALLED} and $ENV{GIT_TEST_INSTALLED} ne "") {
$environment = $ENV{GIT_TEST_INSTALLED};
@@ -227,13 +311,15 @@ sub print_codespeed_results {
}
}
- print to_json(\@data, {utf8 => 1, pretty => 1}), "\n";
+ print to_json(\@data, {utf8 => 1, pretty => 1, canonical => 1}), "\n";
}
binmode STDOUT, ":utf8" or die "PANIC on binmode: $!";
if ($codespeed) {
- print_codespeed_results($results_section);
+ print_codespeed_results($subsection);
+} elsif (defined $sortby) {
+ print_sorted_results($sortby);
} else {
print_default_results();
}
diff --git a/t/perf/run b/t/perf/run
index 1a100d6134..213da5d6b9 100755
--- a/t/perf/run
+++ b/t/perf/run
@@ -106,7 +106,6 @@ get_var_from_env_or_config () {
conf_sec="$2"
conf_var="$3"
conf_opts="$4" # optional
- # $5 can be set to a default value
# Do nothing if the env variable is already set
eval "test -z \"\${$env_var+x}\"" || return
@@ -122,13 +121,12 @@ get_var_from_env_or_config () {
fi
var="$conf_sec.$conf_var"
conf_value=$(git config $conf_opts -f "$GIT_PERF_CONFIG_FILE" "$var") &&
- eval "$env_var=\"$conf_value\"" && return
-
- test -n "${5+x}" && eval "$env_var=\"$5\""
+ eval "$env_var=\"$conf_value\""
}
run_subsection () {
- get_var_from_env_or_config "GIT_PERF_REPEAT_COUNT" "perf" "repeatCount" "--int" 3
+ get_var_from_env_or_config "GIT_PERF_REPEAT_COUNT" "perf" "repeatCount" "--int"
+ : ${GIT_PERF_REPEAT_COUNT:=3}
export GIT_PERF_REPEAT_COUNT
get_var_from_env_or_config "GIT_PERF_DIRS_OR_REVS" "perf" "dirsOrRevs"
diff --git a/t/t0002-gitfile.sh b/t/t0002-gitfile.sh
index 9670e8cbe6..3691023d51 100755
--- a/t/t0002-gitfile.sh
+++ b/t/t0002-gitfile.sh
@@ -10,15 +10,6 @@ objpath() {
echo "$1" | sed -e 's|\(..\)|\1/|'
}
-objck() {
- p=$(objpath "$1")
- if test ! -f "$REAL/objects/$p"
- then
- echo "Object not found: $REAL/objects/$p"
- false
- fi
-}
-
test_expect_success 'initial setup' '
REAL="$(pwd)/.real" &&
mv .git "$REAL"
@@ -26,30 +17,14 @@ test_expect_success 'initial setup' '
test_expect_success 'bad setup: invalid .git file format' '
echo "gitdir $REAL" >.git &&
- if git rev-parse 2>.err
- then
- echo "git rev-parse accepted an invalid .git file"
- false
- fi &&
- if ! grep "Invalid gitfile format" .err
- then
- echo "git rev-parse returned wrong error"
- false
- fi
+ test_must_fail git rev-parse 2>.err &&
+ test_i18ngrep "invalid gitfile format" .err
'
test_expect_success 'bad setup: invalid .git file path' '
echo "gitdir: $REAL.not" >.git &&
- if git rev-parse 2>.err
- then
- echo "git rev-parse accepted an invalid .git file path"
- false
- fi &&
- if ! grep "Not a git repository" .err
- then
- echo "git rev-parse returned wrong error"
- false
- fi
+ test_must_fail git rev-parse 2>.err &&
+ test_i18ngrep "not a git repository" .err
'
test_expect_success 'final setup + check rev-parse --git-dir' '
@@ -60,7 +35,7 @@ test_expect_success 'final setup + check rev-parse --git-dir' '
test_expect_success 'check hash-object' '
echo "foo" >bar &&
SHA=$(cat bar | git hash-object -w --stdin) &&
- objck $SHA
+ test_path_is_file "$REAL/objects/$(objpath $SHA)"
'
test_expect_success 'check cat-file' '
@@ -69,29 +44,21 @@ test_expect_success 'check cat-file' '
'
test_expect_success 'check update-index' '
- if test -f "$REAL/index"
- then
- echo "Hmm, $REAL/index exists?"
- false
- fi &&
+ test_path_is_missing "$REAL/index" &&
rm -f "$REAL/objects/$(objpath $SHA)" &&
git update-index --add bar &&
- if ! test -f "$REAL/index"
- then
- echo "$REAL/index not found"
- false
- fi &&
- objck $SHA
+ test_path_is_file "$REAL/index" &&
+ test_path_is_file "$REAL/objects/$(objpath $SHA)"
'
test_expect_success 'check write-tree' '
SHA=$(git write-tree) &&
- objck $SHA
+ test_path_is_file "$REAL/objects/$(objpath $SHA)"
'
test_expect_success 'check commit-tree' '
SHA=$(echo "commit bar" | git commit-tree $SHA) &&
- objck $SHA
+ test_path_is_file "$REAL/objects/$(objpath $SHA)"
'
test_expect_success 'check rev-list' '
diff --git a/t/t0008-ignores.sh b/t/t0008-ignores.sh
index d27f438bf4..c03f155a35 100755
--- a/t/t0008-ignores.sh
+++ b/t/t0008-ignores.sh
@@ -307,7 +307,7 @@ test_expect_success_multi 'needs work tree' '' '
cd .git &&
test_check_ignore "foo" 128
) &&
- stderr_contains "fatal: This operation must be run in a work tree"
+ stderr_contains "fatal: this operation must be run in a work tree"
'
############################################################################
@@ -775,6 +775,26 @@ test_expect_success PIPE 'streaming support for --stdin' '
echo "$response" | grep "^:: two"
'
+test_expect_success 'existing file and directory' '
+ test_when_finished "rm one" &&
+ test_when_finished "rmdir top-level-dir" &&
+ >one &&
+ mkdir top-level-dir &&
+ git check-ignore one top-level-dir >actual &&
+ grep one actual &&
+ grep top-level-dir actual
+'
+
+test_expect_success 'existing directory and file' '
+ test_when_finished "rm one" &&
+ test_when_finished "rmdir top-level-dir" &&
+ >one &&
+ mkdir top-level-dir &&
+ git check-ignore top-level-dir one >actual &&
+ grep one actual &&
+ grep top-level-dir actual
+'
+
############################################################################
#
# test whitespace handling
diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh
index 0c2fc81d7b..04d474c84f 100755
--- a/t/t0040-parse-options.sh
+++ b/t/t0040-parse-options.sh
@@ -291,7 +291,7 @@ test_expect_success 'OPT_CALLBACK() and OPT_BIT() work' '
test_expect_success 'OPT_CALLBACK() and callback errors work' '
test_must_fail test-parse-options --no-length >output 2>output.err &&
test_i18ncmp expect output &&
- test_i18ncmp expect.err output.err
+ test_must_be_empty output.err
'
cat >expect <<\EOF
diff --git a/t/t0041-usage.sh b/t/t0041-usage.sh
new file mode 100755
index 0000000000..5b927b76fe
--- /dev/null
+++ b/t/t0041-usage.sh
@@ -0,0 +1,107 @@
+#!/bin/sh
+
+test_description='Test commands behavior when given invalid argument value'
+
+. ./test-lib.sh
+
+test_expect_success 'setup ' '
+ test_commit "v1.0"
+'
+
+test_expect_success 'tag --contains <existent_tag>' '
+ git tag --contains "v1.0" >actual 2>actual.err &&
+ grep "v1.0" actual &&
+ test_line_count = 0 actual.err
+'
+
+test_expect_success 'tag --contains <inexistent_tag>' '
+ test_must_fail git tag --contains "notag" >actual 2>actual.err &&
+ test_line_count = 0 actual &&
+ test_i18ngrep "error" actual.err &&
+ test_i18ngrep ! "usage" actual.err
+'
+
+test_expect_success 'tag --no-contains <existent_tag>' '
+ git tag --no-contains "v1.0" >actual 2>actual.err &&
+ test_line_count = 0 actual &&
+ test_line_count = 0 actual.err
+'
+
+test_expect_success 'tag --no-contains <inexistent_tag>' '
+ test_must_fail git tag --no-contains "notag" >actual 2>actual.err &&
+ test_line_count = 0 actual &&
+ test_i18ngrep "error" actual.err &&
+ test_i18ngrep ! "usage" actual.err
+'
+
+test_expect_success 'tag usage error' '
+ test_must_fail git tag --noopt >actual 2>actual.err &&
+ test_line_count = 0 actual &&
+ test_i18ngrep "usage" actual.err
+'
+
+test_expect_success 'branch --contains <existent_commit>' '
+ git branch --contains "master" >actual 2>actual.err &&
+ test_i18ngrep "master" actual &&
+ test_line_count = 0 actual.err
+'
+
+test_expect_success 'branch --contains <inexistent_commit>' '
+ test_must_fail git branch --no-contains "nocommit" >actual 2>actual.err &&
+ test_line_count = 0 actual &&
+ test_i18ngrep "error" actual.err &&
+ test_i18ngrep ! "usage" actual.err
+'
+
+test_expect_success 'branch --no-contains <existent_commit>' '
+ git branch --no-contains "master" >actual 2>actual.err &&
+ test_line_count = 0 actual &&
+ test_line_count = 0 actual.err
+'
+
+test_expect_success 'branch --no-contains <inexistent_commit>' '
+ test_must_fail git branch --no-contains "nocommit" >actual 2>actual.err &&
+ test_line_count = 0 actual &&
+ test_i18ngrep "error" actual.err &&
+ test_i18ngrep ! "usage" actual.err
+'
+
+test_expect_success 'branch usage error' '
+ test_must_fail git branch --noopt >actual 2>actual.err &&
+ test_line_count = 0 actual &&
+ test_i18ngrep "usage" actual.err
+'
+
+test_expect_success 'for-each-ref --contains <existent_object>' '
+ git for-each-ref --contains "master" >actual 2>actual.err &&
+ test_line_count = 2 actual &&
+ test_line_count = 0 actual.err
+'
+
+test_expect_success 'for-each-ref --contains <inexistent_object>' '
+ test_must_fail git for-each-ref --no-contains "noobject" >actual 2>actual.err &&
+ test_line_count = 0 actual &&
+ test_i18ngrep "error" actual.err &&
+ test_i18ngrep ! "usage" actual.err
+'
+
+test_expect_success 'for-each-ref --no-contains <existent_object>' '
+ git for-each-ref --no-contains "master" >actual 2>actual.err &&
+ test_line_count = 0 actual &&
+ test_line_count = 0 actual.err
+'
+
+test_expect_success 'for-each-ref --no-contains <inexistent_object>' '
+ test_must_fail git for-each-ref --no-contains "noobject" >actual 2>actual.err &&
+ test_line_count = 0 actual &&
+ test_i18ngrep "error" actual.err &&
+ test_i18ngrep ! "usage" actual.err
+'
+
+test_expect_success 'for-each-ref usage error' '
+ test_must_fail git for-each-ref --noopt >actual 2>actual.err &&
+ test_line_count = 0 actual &&
+ test_i18ngrep "usage" actual.err
+'
+
+test_done
diff --git a/t/t0050-filesystem.sh b/t/t0050-filesystem.sh
index b29d749bb7..192c94eccd 100755
--- a/t/t0050-filesystem.sh
+++ b/t/t0050-filesystem.sh
@@ -80,7 +80,21 @@ test_expect_success 'merge (case change)' '
git merge topic
'
-
+test_expect_success CASE_INSENSITIVE_FS 'add directory (with different case)' '
+ git reset --hard initial &&
+ mkdir -p dir1/dir2 &&
+ echo >dir1/dir2/a &&
+ echo >dir1/dir2/b &&
+ git add dir1/dir2/a &&
+ git add dir1/DIR2/b &&
+ git ls-files >actual &&
+ cat >expected <<-\EOF &&
+ camelcase
+ dir1/dir2/a
+ dir1/dir2/b
+ EOF
+ test_cmp expected actual
+'
test_expect_failure CASE_INSENSITIVE_FS 'add (with different case)' '
git reset --hard initial &&
diff --git a/t/t0205-gettext-poison.sh b/t/t0205-gettext-poison.sh
index 2361590d54..438e778d6a 100755
--- a/t/t0205-gettext-poison.sh
+++ b/t/t0205-gettext-poison.sh
@@ -7,10 +7,6 @@ test_description='Gettext Shell poison'
. ./lib-gettext.sh
-test_expect_success GETTEXT_POISON "sanity: \$GIT_INTERNAL_GETTEXT_SH_SCHEME is set (to $GIT_INTERNAL_GETTEXT_SH_SCHEME)" '
- test -n "$GIT_INTERNAL_GETTEXT_SH_SCHEME"
-'
-
test_expect_success GETTEXT_POISON 'sanity: $GIT_INTERNAL_GETTEXT_SH_SCHEME" is poison' '
test "$GIT_INTERNAL_GETTEXT_SH_SCHEME" = "poison"
'
diff --git a/t/t1011-read-tree-sparse-checkout.sh b/t/t1011-read-tree-sparse-checkout.sh
index c167f606ca..0c6f48f302 100755
--- a/t/t1011-read-tree-sparse-checkout.sh
+++ b/t/t1011-read-tree-sparse-checkout.sh
@@ -15,8 +15,11 @@ test_description='sparse checkout tests
. "$TEST_DIRECTORY"/lib-read-tree.sh
test_expect_success 'setup' '
+ test_commit init &&
+ echo modified >>init.t &&
+
cat >expected <<-EOF &&
- 100644 77f0ba1734ed79d12881f81b36ee134de6a3327b 0 init.t
+ 100644 $(git hash-object init.t) 0 init.t
100644 $EMPTY_BLOB 0 sub/added
100644 $EMPTY_BLOB 0 sub/addedtoo
100644 $EMPTY_BLOB 0 subsub/added
@@ -28,8 +31,6 @@ test_expect_success 'setup' '
H subsub/added
EOF
- test_commit init &&
- echo modified >>init.t &&
mkdir sub subsub &&
touch sub/added sub/addedtoo subsub/added &&
git add init.t sub/added sub/addedtoo subsub/added &&
diff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh
index cbeb9bebee..dc7e6c2e77 100755
--- a/t/t1300-repo-config.sh
+++ b/t/t1300-repo-config.sh
@@ -1206,6 +1206,29 @@ test_expect_success 'git -c is not confused by empty environment' '
GIT_CONFIG_PARAMETERS="" git -c x.one=1 config --list
'
+sq="'"
+test_expect_success 'detect bogus GIT_CONFIG_PARAMETERS' '
+ cat >expect <<-\EOF &&
+ env.one one
+ env.two two
+ EOF
+ GIT_CONFIG_PARAMETERS="${sq}env.one=one${sq} ${sq}env.two=two${sq}" \
+ git config --get-regexp "env.*" >actual &&
+ test_cmp expect actual &&
+
+ cat >expect <<-EOF &&
+ env.one one${sq}
+ env.two two
+ EOF
+ GIT_CONFIG_PARAMETERS="${sq}env.one=one${sq}\\$sq$sq$sq ${sq}env.two=two${sq}" \
+ git config --get-regexp "env.*" >actual &&
+ test_cmp expect actual &&
+
+ test_must_fail env \
+ GIT_CONFIG_PARAMETERS="${sq}env.one=one${sq}\\$sq ${sq}env.two=two${sq}" \
+ git config --get-regexp "env.*"
+'
+
test_expect_success 'git config --edit works' '
git config -f tmp test.value no &&
echo test.value=yes >expect &&
@@ -1564,10 +1587,10 @@ test_expect_success '--show-origin stdin with file include' '
'
test_expect_success !MINGW '--show-origin blob' '
- cat >expect <<-\EOF &&
- blob:a9d9f9e555b5c6f07cbe09d3f06fe3df11e09c08 user.custom=true
- EOF
blob=$(git hash-object -w "$CUSTOM_CONFIG_FILE") &&
+ cat >expect <<-EOF &&
+ blob:$blob user.custom=true
+ EOF
git config --blob=$blob --show-origin --list >output &&
test_cmp expect output
'
diff --git a/t/t1304-default-acl.sh b/t/t1304-default-acl.sh
index f5422f1d33..335d3f3211 100755
--- a/t/t1304-default-acl.sh
+++ b/t/t1304-default-acl.sh
@@ -54,7 +54,7 @@ test_expect_success SETFACL 'Setup test repo' '
test_expect_success SETFACL 'Objects creation does not break ACLs with restrictive umask' '
# SHA1 for empty blob
- check_perms_and_acl .git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
+ check_perms_and_acl .git/objects/$(echo $EMPTY_BLOB | sed -e "s,^\(..\),\1/,")
'
test_expect_success SETFACL 'git gc does not break ACLs with restrictive umask' '
diff --git a/t/t1405-main-ref-store.sh b/t/t1405-main-ref-store.sh
index e8115df5ba..a1e243a05c 100755
--- a/t/t1405-main-ref-store.sh
+++ b/t/t1405-main-ref-store.sh
@@ -45,7 +45,7 @@ test_expect_success 'rename_refs(master, new-master)' '
'
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
@@ -71,7 +71,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 -k2 | cut -c 42- >actual &&
cat >expected <<-\EOF &&
HEAD 0x1
refs/heads/master 0x0
diff --git a/t/t1411-reflog-show.sh b/t/t1411-reflog-show.sh
index 6ac7734d79..596907758d 100755
--- a/t/t1411-reflog-show.sh
+++ b/t/t1411-reflog-show.sh
@@ -10,6 +10,7 @@ test_expect_success 'setup' '
git commit -m one
'
+commit=$(git rev-parse --short HEAD)
cat >expect <<'EOF'
Reflog: HEAD@{0} (C O Mitter <committer@example.com>)
Reflog message: commit (initial): one
@@ -20,8 +21,8 @@ test_expect_success 'log -g shows reflog headers' '
test_cmp expect actual
'
-cat >expect <<'EOF'
-e46513e HEAD@{0}: commit (initial): one
+cat >expect <<EOF
+$commit HEAD@{0}: commit (initial): one
EOF
test_expect_success 'oneline reflog format' '
git log -g -1 --oneline >actual &&
@@ -33,8 +34,8 @@ test_expect_success 'reflog default format' '
test_cmp expect actual
'
-cat >expect <<'EOF'
-commit e46513e
+cat >expect <<EOF
+commit $commit
Reflog: HEAD@{0} (C O Mitter <committer@example.com>)
Reflog message: commit (initial): one
Author: A U Thor <author@example.com>
@@ -56,8 +57,8 @@ test_expect_success 'using @{now} syntax shows reflog date (multiline)' '
test_cmp expect actual
'
-cat >expect <<'EOF'
-e46513e HEAD@{Thu Apr 7 15:13:13 2005 -0700}: commit (initial): one
+cat >expect <<EOF
+$commit HEAD@{Thu Apr 7 15:13:13 2005 -0700}: commit (initial): one
EOF
test_expect_success 'using @{now} syntax shows reflog date (oneline)' '
git log -g -1 --oneline HEAD@{now} >actual &&
@@ -82,8 +83,8 @@ test_expect_success 'using --date= shows reflog date (multiline)' '
test_cmp expect actual
'
-cat >expect <<'EOF'
-e46513e HEAD@{Thu Apr 7 15:13:13 2005 -0700}: commit (initial): one
+cat >expect <<EOF
+$commit HEAD@{Thu Apr 7 15:13:13 2005 -0700}: commit (initial): one
EOF
test_expect_success 'using --date= shows reflog date (oneline)' '
git log -g -1 --oneline --date=default >actual &&
@@ -109,8 +110,8 @@ test_expect_success 'log.date does not invoke "--date" magic (multiline)' '
test_cmp expect actual
'
-cat >expect <<'EOF'
-e46513e HEAD@{0}: commit (initial): one
+cat >expect <<EOF
+$commit HEAD@{0}: commit (initial): one
EOF
test_expect_success 'log.date does not invoke "--date" magic (oneline)' '
test_config log.date raw &&
diff --git a/t/t1506-rev-parse-diagnosis.sh b/t/t1506-rev-parse-diagnosis.sh
index 79a0251efa..4ee009da66 100755
--- a/t/t1506-rev-parse-diagnosis.sh
+++ b/t/t1506-rev-parse-diagnosis.sh
@@ -157,7 +157,7 @@ test_expect_success 'relative path not found' '
test_expect_success 'relative path outside worktree' '
test_must_fail git rev-parse HEAD:../file.txt >output 2>error &&
test -z "$(cat output)" &&
- grep "outside repository" error
+ test_i18ngrep "outside repository" error
'
test_expect_success 'relative path when cwd is outside worktree' '
diff --git a/t/t1507-rev-parse-upstream.sh b/t/t1507-rev-parse-upstream.sh
index b23c4e3fab..93c77eac45 100755
--- a/t/t1507-rev-parse-upstream.sh
+++ b/t/t1507-rev-parse-upstream.sh
@@ -42,7 +42,7 @@ commit_subject () {
error_message () {
(cd clone &&
- test_must_fail git rev-parse --verify "$@")
+ test_must_fail git rev-parse --verify "$@" 2>../error)
}
test_expect_success '@{upstream} resolves to correct full name' '
@@ -159,8 +159,8 @@ test_expect_success 'branch@{u} error message when no upstream' '
cat >expect <<-EOF &&
fatal: no upstream configured for branch ${sq}non-tracking${sq}
EOF
- error_message non-tracking@{u} 2>actual &&
- test_i18ncmp expect actual
+ error_message non-tracking@{u} &&
+ test_i18ncmp expect error
'
test_expect_success '@{u} error message when no upstream' '
@@ -175,8 +175,8 @@ test_expect_success 'branch@{u} error message with misspelt branch' '
cat >expect <<-EOF &&
fatal: no such branch: ${sq}no-such-branch${sq}
EOF
- error_message no-such-branch@{u} 2>actual &&
- test_i18ncmp expect actual
+ error_message no-such-branch@{u} &&
+ test_i18ncmp expect error
'
test_expect_success '@{u} error message when not on a branch' '
@@ -192,8 +192,8 @@ test_expect_success 'branch@{u} error message if upstream branch not fetched' '
cat >expect <<-EOF &&
fatal: upstream branch ${sq}refs/heads/side${sq} not stored as a remote-tracking branch
EOF
- error_message bad-upstream@{u} 2>actual &&
- test_i18ncmp expect actual
+ error_message bad-upstream@{u} &&
+ test_i18ncmp expect error
'
test_expect_success 'pull works when tracking a local branch' '
@@ -209,8 +209,9 @@ test_expect_success '@{u} works when tracking a local branch' '
test refs/heads/master = "$(full_name @{u})"
'
+commit=$(git rev-parse HEAD)
cat >expect <<EOF
-commit 8f489d01d0cc65c3b0f09504ec50b5ed02a70bd5
+commit $commit
Reflog: master@{0} (C O Mitter <committer@example.com>)
Reflog message: branch: Created from HEAD
Author: A U Thor <author@example.com>
@@ -224,7 +225,7 @@ test_expect_success 'log -g other@{u}' '
'
cat >expect <<EOF
-commit 8f489d01d0cc65c3b0f09504ec50b5ed02a70bd5
+commit $commit
Reflog: master@{Thu Apr 7 15:17:13 2005 -0700} (C O Mitter <committer@example.com>)
Reflog message: branch: Created from HEAD
Author: A U Thor <author@example.com>
diff --git a/t/t1510-repo-setup.sh b/t/t1510-repo-setup.sh
index 13ae12dfa7..e6854b828e 100755
--- a/t/t1510-repo-setup.sh
+++ b/t/t1510-repo-setup.sh
@@ -39,6 +39,10 @@ A few rules for repo setup:
11. When user's cwd is outside worktree, cwd remains unchanged,
prefix is NULL.
"
+
+# This test heavily relies on the standard error of nested function calls.
+test_untraceable=UnfortunatelyYes
+
. ./test-lib.sh
here=$(pwd)
diff --git a/t/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh
index bb4f2e0c63..1fa670625c 100755
--- a/t/t2020-checkout-detach.sh
+++ b/t/t2020-checkout-detach.sh
@@ -189,8 +189,12 @@ test_expect_success 'no advice given for explicit detached head state' '
# Detached HEAD tests for GIT_PRINT_SHA1_ELLIPSIS (new format)
test_expect_success 'describe_detached_head prints no SHA-1 ellipsis when not asked to' "
+ commit=$(git rev-parse --short=12 master^) &&
+ commit2=$(git rev-parse --short=12 master~2) &&
+ commit3=$(git rev-parse --short=12 master~3) &&
+
# The first detach operation is more chatty than the following ones.
- cat >1st_detach <<-'EOF' &&
+ cat >1st_detach <<-EOF &&
Note: checking out 'HEAD^'.
You are in 'detached HEAD' state. You can look around, make experimental
@@ -202,18 +206,18 @@ test_expect_success 'describe_detached_head prints no SHA-1 ellipsis when not as
git checkout -b <new-branch-name>
- HEAD is now at 7c7cd714e262 three
+ HEAD is now at \$commit three
EOF
# The remaining ones just show info about previous and current HEADs.
- cat >2nd_detach <<-'EOF' &&
- Previous HEAD position was 7c7cd714e262 three
- HEAD is now at 139b20d8e6c5 two
+ cat >2nd_detach <<-EOF &&
+ Previous HEAD position was \$commit three
+ HEAD is now at \$commit2 two
EOF
- cat >3rd_detach <<-'EOF' &&
- Previous HEAD position was 139b20d8e6c5 two
- HEAD is now at d79ce1670bdc one
+ cat >3rd_detach <<-EOF &&
+ Previous HEAD position was \$commit2 two
+ HEAD is now at \$commit3 one
EOF
reset &&
@@ -261,8 +265,12 @@ test_expect_success 'describe_detached_head prints no SHA-1 ellipsis when not as
# Detached HEAD tests for GIT_PRINT_SHA1_ELLIPSIS (old format)
test_expect_success 'describe_detached_head does print SHA-1 ellipsis when asked to' "
+ commit=$(git rev-parse --short=12 master^) &&
+ commit2=$(git rev-parse --short=12 master~2) &&
+ commit3=$(git rev-parse --short=12 master~3) &&
+
# The first detach operation is more chatty than the following ones.
- cat >1st_detach <<-'EOF' &&
+ cat >1st_detach <<-EOF &&
Note: checking out 'HEAD^'.
You are in 'detached HEAD' state. You can look around, make experimental
@@ -274,18 +282,18 @@ test_expect_success 'describe_detached_head does print SHA-1 ellipsis when asked
git checkout -b <new-branch-name>
- HEAD is now at 7c7cd714e262... three
+ HEAD is now at \$commit... three
EOF
# The remaining ones just show info about previous and current HEADs.
- cat >2nd_detach <<-'EOF' &&
- Previous HEAD position was 7c7cd714e262... three
- HEAD is now at 139b20d8e6c5... two
+ cat >2nd_detach <<-EOF &&
+ Previous HEAD position was \$commit... three
+ HEAD is now at \$commit2... two
EOF
- cat >3rd_detach <<-'EOF' &&
- Previous HEAD position was 139b20d8e6c5... two
- HEAD is now at d79ce1670bdc... one
+ cat >3rd_detach <<-EOF &&
+ Previous HEAD position was \$commit2... two
+ HEAD is now at \$commit3... one
EOF
reset &&
diff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh
index 2b95944973..d0d2e4f7ec 100755
--- a/t/t2025-worktree-add.sh
+++ b/t/t2025-worktree-add.sh
@@ -451,32 +451,68 @@ test_expect_success 'git worktree --no-guess-remote option overrides config' '
'
post_checkout_hook () {
- test_when_finished "rm -f .git/hooks/post-checkout" &&
- mkdir -p .git/hooks &&
- write_script .git/hooks/post-checkout <<-\EOF
- echo $* >hook.actual
+ gitdir=${1:-.git}
+ test_when_finished "rm -f $gitdir/hooks/post-checkout" &&
+ mkdir -p $gitdir/hooks &&
+ write_script $gitdir/hooks/post-checkout <<-\EOF
+ {
+ echo $*
+ git rev-parse --git-dir --show-toplevel
+ } >hook.actual
EOF
}
test_expect_success '"add" invokes post-checkout hook (branch)' '
post_checkout_hook &&
- printf "%s %s 1\n" $_z40 $(git rev-parse HEAD) >hook.expect &&
+ {
+ echo $_z40 $(git rev-parse HEAD) 1 &&
+ echo $(pwd)/.git/worktrees/gumby &&
+ echo $(pwd)/gumby
+ } >hook.expect &&
git worktree add gumby &&
- test_cmp hook.expect hook.actual
+ test_cmp hook.expect gumby/hook.actual
'
test_expect_success '"add" invokes post-checkout hook (detached)' '
post_checkout_hook &&
- printf "%s %s 1\n" $_z40 $(git rev-parse HEAD) >hook.expect &&
+ {
+ echo $_z40 $(git rev-parse HEAD) 1 &&
+ echo $(pwd)/.git/worktrees/grumpy &&
+ echo $(pwd)/grumpy
+ } >hook.expect &&
git worktree add --detach grumpy &&
- test_cmp hook.expect hook.actual
+ test_cmp hook.expect grumpy/hook.actual
'
test_expect_success '"add --no-checkout" suppresses post-checkout hook' '
post_checkout_hook &&
rm -f hook.actual &&
git worktree add --no-checkout gloopy &&
- test_path_is_missing hook.actual
+ test_path_is_missing gloopy/hook.actual
+'
+
+test_expect_success '"add" in other worktree invokes post-checkout hook' '
+ post_checkout_hook &&
+ {
+ echo $_z40 $(git rev-parse HEAD) 1 &&
+ echo $(pwd)/.git/worktrees/guppy &&
+ echo $(pwd)/guppy
+ } >hook.expect &&
+ git -C gloopy worktree add --detach ../guppy &&
+ test_cmp hook.expect guppy/hook.actual
+'
+
+test_expect_success '"add" in bare repo invokes post-checkout hook' '
+ rm -rf bare &&
+ git clone --bare . bare &&
+ {
+ echo $_z40 $(git --git-dir=bare rev-parse HEAD) 1 &&
+ echo $(pwd)/bare/worktrees/goozy &&
+ echo $(pwd)/goozy
+ } >hook.expect &&
+ post_checkout_hook bare &&
+ git -C bare worktree add --detach ../goozy &&
+ test_cmp hook.expect goozy/hook.actual
'
test_done
diff --git a/t/t2026-worktree-prune.sh b/t/t2026-worktree-prune.sh
index a0f1e3bb80..b7d6d5d45a 100755
--- a/t/t2026-worktree-prune.sh
+++ b/t/t2026-worktree-prune.sh
@@ -78,10 +78,9 @@ test_expect_success 'not prune locked checkout' '
test_expect_success 'not prune recent checkouts' '
test_when_finished rm -r .git/worktrees &&
- mkdir zz &&
- mkdir -p .git/worktrees/jlm &&
- echo "$(pwd)"/zz >.git/worktrees/jlm/gitdir &&
- rmdir zz &&
+ git worktree add jlm HEAD &&
+ test -d .git/worktrees/jlm &&
+ rm -rf jlm &&
git worktree prune --verbose --expire=2.days.ago &&
test -d .git/worktrees/jlm
'
diff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh
index 8298aaf97f..5d5b3632ba 100755
--- a/t/t2028-worktree-move.sh
+++ b/t/t2028-worktree-move.sh
@@ -7,7 +7,8 @@ test_description='test git worktree move, remove, lock and unlock'
test_expect_success 'setup' '
test_commit init &&
git worktree add source &&
- git worktree list --porcelain | grep "^worktree" >actual &&
+ git worktree list --porcelain >out &&
+ grep "^worktree" out >actual &&
cat <<-EOF >expected &&
worktree $(pwd)
worktree $(pwd)/source
@@ -59,4 +60,86 @@ test_expect_success 'unlock worktree twice' '
test_path_is_missing .git/worktrees/source/locked
'
+test_expect_success 'move non-worktree' '
+ mkdir abc &&
+ test_must_fail git worktree move abc def
+'
+
+test_expect_success 'move locked worktree' '
+ git worktree lock source &&
+ test_when_finished "git worktree unlock source" &&
+ test_must_fail git worktree move source destination
+'
+
+test_expect_success 'move worktree' '
+ toplevel="$(pwd)" &&
+ git worktree move source destination &&
+ test_path_is_missing source &&
+ git worktree list --porcelain >out &&
+ grep "^worktree.*/destination" out &&
+ ! grep "^worktree.*/source" out &&
+ git -C destination log --format=%s >actual2 &&
+ echo init >expected2 &&
+ test_cmp expected2 actual2
+'
+
+test_expect_success 'move main worktree' '
+ test_must_fail git worktree move . def
+'
+
+test_expect_success 'move worktree to another dir' '
+ mkdir some-dir &&
+ git worktree move destination some-dir &&
+ test_when_finished "git worktree move some-dir/destination destination" &&
+ test_path_is_missing destination &&
+ git worktree list --porcelain >out &&
+ grep "^worktree.*/some-dir/destination" out &&
+ git -C some-dir/destination log --format=%s >actual2 &&
+ echo init >expected2 &&
+ test_cmp expected2 actual2
+'
+
+test_expect_success 'remove main worktree' '
+ test_must_fail git worktree remove .
+'
+
+test_expect_success 'remove locked worktree' '
+ git worktree lock destination &&
+ test_when_finished "git worktree unlock destination" &&
+ test_must_fail git worktree remove destination
+'
+
+test_expect_success 'remove worktree with dirty tracked file' '
+ echo dirty >>destination/init.t &&
+ test_when_finished "git -C destination checkout init.t" &&
+ test_must_fail git worktree remove destination
+'
+
+test_expect_success 'remove worktree with untracked file' '
+ : >destination/untracked &&
+ test_must_fail git worktree remove destination
+'
+
+test_expect_success 'force remove worktree with untracked file' '
+ git worktree remove --force destination &&
+ test_path_is_missing destination
+'
+
+test_expect_success 'remove missing worktree' '
+ git worktree add to-be-gone &&
+ test -d .git/worktrees/to-be-gone &&
+ mv to-be-gone gone &&
+ git worktree remove to-be-gone &&
+ test_path_is_missing .git/worktrees/to-be-gone
+'
+
+test_expect_success 'NOT remove missing-but-locked worktree' '
+ git worktree add gone-but-locked &&
+ git worktree lock gone-but-locked &&
+ test -d .git/worktrees/gone-but-locked &&
+ mv gone-but-locked really-gone-now &&
+ test_must_fail git worktree remove gone-but-locked &&
+ test_path_is_dir .git/worktrees/gone-but-locked
+'
+
test_done
diff --git a/t/t2101-update-index-reupdate.sh b/t/t2101-update-index-reupdate.sh
index c8bce8c2e4..685ec45639 100755
--- a/t/t2101-update-index-reupdate.sh
+++ b/t/t2101-update-index-reupdate.sh
@@ -8,19 +8,20 @@ test_description='git update-index --again test.
. ./test-lib.sh
-cat > expected <<\EOF
-100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0 file1
-100644 9db8893856a8a02eaa73470054b7c1c5a7c82e47 0 file2
-EOF
-test_expect_success 'update-index --add' \
- 'echo hello world >file1 &&
- echo goodbye people >file2 &&
- git update-index --add file1 file2 &&
- git ls-files -s >current &&
- cmp current expected'
+test_expect_success 'update-index --add' '
+ echo hello world >file1 &&
+ echo goodbye people >file2 &&
+ git update-index --add file1 file2 &&
+ git ls-files -s >current &&
+ cat >expected <<-EOF &&
+ 100644 $(git hash-object file1) 0 file1
+ 100644 $(git hash-object file2) 0 file2
+ EOF
+ cmp current expected
+'
-test_expect_success 'update-index --again' \
- 'rm -f file1 &&
+test_expect_success 'update-index --again' '
+ rm -f file1 &&
echo hello everybody >file2 &&
if git update-index --again
then
@@ -29,25 +30,23 @@ test_expect_success 'update-index --again' \
else
echo happy - failed as expected
fi &&
- git ls-files -s >current &&
- cmp current expected'
+ git ls-files -s >current &&
+ cmp current expected
+'
-cat > expected <<\EOF
-100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0 file2
-EOF
-test_expect_success 'update-index --remove --again' \
- 'git update-index --remove --again &&
- git ls-files -s >current &&
- cmp current expected'
+test_expect_success 'update-index --remove --again' '
+ git update-index --remove --again &&
+ git ls-files -s >current &&
+ cat >expected <<-EOF &&
+ 100644 $(git hash-object file2) 0 file2
+ EOF
+ cmp current expected
+'
test_expect_success 'first commit' 'git commit -m initial'
-cat > expected <<\EOF
-100644 53ab446c3f4e42ce9bb728a0ccb283a101be4979 0 dir1/file3
-100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0 file2
-EOF
-test_expect_success 'update-index again' \
- 'mkdir -p dir1 &&
+test_expect_success 'update-index again' '
+ mkdir -p dir1 &&
echo hello world >dir1/file3 &&
echo goodbye people >file2 &&
git update-index --add file2 dir1/file3 &&
@@ -55,30 +54,38 @@ test_expect_success 'update-index again' \
echo happy >dir1/file3 &&
git update-index --again &&
git ls-files -s >current &&
- cmp current expected'
+ cat >expected <<-EOF &&
+ 100644 $(git hash-object dir1/file3) 0 dir1/file3
+ 100644 $(git hash-object file2) 0 file2
+ EOF
+ cmp current expected
+'
-cat > expected <<\EOF
-100644 d7fb3f695f06c759dbf3ab00046e7cc2da22d10f 0 dir1/file3
-100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0 file2
-EOF
-test_expect_success 'update-index --update from subdir' \
- 'echo not so happy >file2 &&
+file2=$(git hash-object file2)
+test_expect_success 'update-index --update from subdir' '
+ echo not so happy >file2 &&
(cd dir1 &&
cat ../file2 >file3 &&
git update-index --again
) &&
git ls-files -s >current &&
- cmp current expected'
+ cat >expected <<-EOF &&
+ 100644 $(git hash-object dir1/file3) 0 dir1/file3
+ 100644 $file2 0 file2
+ EOF
+ test_cmp current expected
+'
-cat > expected <<\EOF
-100644 594fb5bb1759d90998e2bf2a38261ae8e243c760 0 dir1/file3
-100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0 file2
-EOF
-test_expect_success 'update-index --update with pathspec' \
- 'echo very happy >file2 &&
+test_expect_success 'update-index --update with pathspec' '
+ echo very happy >file2 &&
cat file2 >dir1/file3 &&
git update-index --again dir1/ &&
git ls-files -s >current &&
- cmp current expected'
+ cat >expected <<-EOF &&
+ 100644 $(git hash-object dir1/file3) 0 dir1/file3
+ 100644 $file2 0 file2
+ EOF
+ cmp current expected
+'
test_done
diff --git a/t/t2107-update-index-basic.sh b/t/t2107-update-index-basic.sh
index 32ac6e09bd..1db7e6a1ab 100755
--- a/t/t2107-update-index-basic.sh
+++ b/t/t2107-update-index-basic.sh
@@ -85,9 +85,9 @@ test_expect_success '--chmod=+x and chmod=-x in the same argument list' '
>B &&
git add A B &&
git update-index --chmod=+x A --chmod=-x B &&
- cat >expect <<-\EOF &&
- 100755 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 A
- 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 B
+ cat >expect <<-EOF &&
+ 100755 $EMPTY_BLOB 0 A
+ 100644 $EMPTY_BLOB 0 B
EOF
git ls-files --stage A B >actual &&
test_cmp expect actual
diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh
index cdc38fe5d1..3563e77b37 100755
--- a/t/t3030-merge-recursive.sh
+++ b/t/t3030-merge-recursive.sh
@@ -525,20 +525,22 @@ test_expect_success 'merge-recursive w/ empty work tree - ours has rename' '
GIT_INDEX_FILE="$PWD/ours-has-rename-index" &&
export GIT_INDEX_FILE &&
mkdir "$GIT_WORK_TREE" &&
- git read-tree -i -m $c7 &&
- git update-index --ignore-missing --refresh &&
- git merge-recursive $c0 -- $c7 $c3 &&
- git ls-files -s >actual-files
- ) 2>actual-err &&
- >expected-err &&
+ git read-tree -i -m $c7 2>actual-err &&
+ test_must_be_empty actual-err &&
+ git update-index --ignore-missing --refresh 2>actual-err &&
+ test_must_be_empty actual-err &&
+ git merge-recursive $c0 -- $c7 $c3 2>actual-err &&
+ test_must_be_empty actual-err &&
+ git ls-files -s >actual-files 2>actual-err &&
+ test_must_be_empty actual-err
+ ) &&
cat >expected-files <<-EOF &&
100644 $o3 0 b/c
100644 $o0 0 c
100644 $o0 0 d/e
100644 $o0 0 e
EOF
- test_cmp expected-files actual-files &&
- test_cmp expected-err actual-err
+ test_cmp expected-files actual-files
'
test_expect_success 'merge-recursive w/ empty work tree - theirs has rename' '
@@ -548,20 +550,22 @@ test_expect_success 'merge-recursive w/ empty work tree - theirs has rename' '
GIT_INDEX_FILE="$PWD/theirs-has-rename-index" &&
export GIT_INDEX_FILE &&
mkdir "$GIT_WORK_TREE" &&
- git read-tree -i -m $c3 &&
- git update-index --ignore-missing --refresh &&
- git merge-recursive $c0 -- $c3 $c7 &&
- git ls-files -s >actual-files
- ) 2>actual-err &&
- >expected-err &&
+ git read-tree -i -m $c3 2>actual-err &&
+ test_must_be_empty actual-err &&
+ git update-index --ignore-missing --refresh 2>actual-err &&
+ test_must_be_empty actual-err &&
+ git merge-recursive $c0 -- $c3 $c7 2>actual-err &&
+ test_must_be_empty actual-err &&
+ git ls-files -s >actual-files 2>actual-err &&
+ test_must_be_empty actual-err
+ ) &&
cat >expected-files <<-EOF &&
100644 $o3 0 b/c
100644 $o0 0 c
100644 $o0 0 d/e
100644 $o0 0 e
EOF
- test_cmp expected-files actual-files &&
- test_cmp expected-err actual-err
+ test_cmp expected-files actual-files
'
test_expect_success 'merge removes empty directories' '
diff --git a/t/t3070-wildmatch.sh b/t/t3070-wildmatch.sh
index 163a14a1c2..c1fc6ca730 100755
--- a/t/t3070-wildmatch.sh
+++ b/t/t3070-wildmatch.sh
@@ -4,266 +4,431 @@ test_description='wildmatch tests'
. ./test-lib.sh
-match() {
- if [ $1 = 1 ]; then
- test_expect_success "wildmatch: match '$3' '$4'" "
- test-wildmatch wildmatch '$3' '$4'
- "
- else
- test_expect_success "wildmatch: no match '$3' '$4'" "
- ! test-wildmatch wildmatch '$3' '$4'
- "
- fi
+should_create_test_file() {
+ file=$1
+
+ case $file in
+ # `touch .` will succeed but obviously not do what we intend
+ # here.
+ ".")
+ return 1
+ ;;
+ # We cannot create a file with an empty filename.
+ "")
+ return 1
+ ;;
+ # The tests that are testing that e.g. foo//bar is matched by
+ # foo/*/bar can't be tested on filesystems since there's no
+ # way we're getting a double slash.
+ *//*)
+ return 1
+ ;;
+ # When testing the difference between foo/bar and foo/bar/ we
+ # can't test the latter.
+ */)
+ return 1
+ ;;
+ # On Windows, \ in paths is silently converted to /, which
+ # would result in the "touch" below working, but the test
+ # itself failing. See 6fd1106aa4 ("t3700: Skip a test with
+ # backslashes in pathspec", 2009-03-13) for prior art and
+ # details.
+ *\\*)
+ if ! test_have_prereq BSLASHPSPEC
+ then
+ return 1
+ fi
+ # NOTE: The ;;& bash extension is not portable, so
+ # this test needs to be at the end of the pattern
+ # list.
+ #
+ # If we want to add more conditional returns we either
+ # need a new case statement, or turn this whole thing
+ # into a series of "if" tests.
+ ;;
+ esac
+
+
+ # On Windows proper (i.e. not Cygwin) many file names which
+ # under Cygwin would be emulated don't work.
+ if test_have_prereq MINGW
+ then
+ case $file in
+ " ")
+ # Files called " " are forbidden on Windows
+ return 1
+ ;;
+ *\<*|*\>*|*:*|*\"*|*\|*|*\?*|*\**)
+ # Files with various special characters aren't
+ # allowed on Windows. Sourced from
+ # https://stackoverflow.com/a/31976060
+ return 1
+ ;;
+ esac
+ fi
+
+ return 0
}
-imatch() {
- if [ $1 = 1 ]; then
- test_expect_success "iwildmatch: match '$2' '$3'" "
- test-wildmatch iwildmatch '$2' '$3'
- "
- else
- test_expect_success "iwildmatch: no match '$2' '$3'" "
- ! test-wildmatch iwildmatch '$2' '$3'
- "
- fi
+match_with_function() {
+ text=$1
+ pattern=$2
+ match_expect=$3
+ match_function=$4
+
+ if test "$match_expect" = 1
+ then
+ test_expect_success "$match_function: match '$text' '$pattern'" "
+ test-wildmatch $match_function '$text' '$pattern'
+ "
+ elif test "$match_expect" = 0
+ then
+ test_expect_success "$match_function: no match '$text' '$pattern'" "
+ test_must_fail test-wildmatch $match_function '$text' '$pattern'
+ "
+ else
+ test_expect_success "PANIC: Test framework error. Unknown matches value $match_expect" 'false'
+ fi
+
+}
+
+match_with_ls_files() {
+ text=$1
+ pattern=$2
+ match_expect=$3
+ match_function=$4
+ ls_files_args=$5
+
+ match_stdout_stderr_cmp="
+ tr -d '\0' <actual.raw >actual &&
+ >expect.err &&
+ test_cmp expect.err actual.err &&
+ test_cmp expect actual"
+
+ if test "$match_expect" = 'E'
+ then
+ if test -e .git/created_test_file
+ then
+ test_expect_success EXPENSIVE_ON_WINDOWS "$match_function (via ls-files): match dies on '$pattern' '$text'" "
+ printf '%s' '$text' >expect &&
+ test_must_fail git$ls_files_args ls-files -z -- '$pattern'
+ "
+ else
+ test_expect_failure EXPENSIVE_ON_WINDOWS "$match_function (via ls-files): match skip '$pattern' '$text'" 'false'
+ fi
+ elif test "$match_expect" = 1
+ then
+ if test -e .git/created_test_file
+ then
+ test_expect_success EXPENSIVE_ON_WINDOWS "$match_function (via ls-files): match '$pattern' '$text'" "
+ printf '%s' '$text' >expect &&
+ git$ls_files_args ls-files -z -- '$pattern' >actual.raw 2>actual.err &&
+ $match_stdout_stderr_cmp
+ "
+ else
+ test_expect_failure EXPENSIVE_ON_WINDOWS "$match_function (via ls-files): match skip '$pattern' '$text'" 'false'
+ fi
+ elif test "$match_expect" = 0
+ then
+ if test -e .git/created_test_file
+ then
+ test_expect_success EXPENSIVE_ON_WINDOWS "$match_function (via ls-files): no match '$pattern' '$text'" "
+ >expect &&
+ git$ls_files_args ls-files -z -- '$pattern' >actual.raw 2>actual.err &&
+ $match_stdout_stderr_cmp
+ "
+ else
+ test_expect_failure EXPENSIVE_ON_WINDOWS "$match_function (via ls-files): no match skip '$pattern' '$text'" 'false'
+ fi
+ else
+ test_expect_success "PANIC: Test framework error. Unknown matches value $match_expect" 'false'
+ fi
}
-pathmatch() {
- if [ $1 = 1 ]; then
- test_expect_success "pathmatch: match '$2' '$3'" "
- test-wildmatch pathmatch '$2' '$3'
- "
- else
- test_expect_success "pathmatch: no match '$2' '$3'" "
- ! test-wildmatch pathmatch '$2' '$3'
- "
- fi
+match() {
+ if test "$#" = 6
+ then
+ # When test-wildmatch and git ls-files produce the same
+ # result.
+ match_glob=$1
+ match_file_glob=$match_glob
+ match_iglob=$2
+ match_file_iglob=$match_iglob
+ match_pathmatch=$3
+ match_file_pathmatch=$match_pathmatch
+ match_pathmatchi=$4
+ match_file_pathmatchi=$match_pathmatchi
+ text=$5
+ pattern=$6
+ elif test "$#" = 10
+ then
+ match_glob=$1
+ match_iglob=$2
+ match_pathmatch=$3
+ match_pathmatchi=$4
+ match_file_glob=$5
+ match_file_iglob=$6
+ match_file_pathmatch=$7
+ match_file_pathmatchi=$8
+ text=$9
+ pattern=${10}
+ fi
+
+ test_expect_success EXPENSIVE_ON_WINDOWS 'cleanup after previous file test' '
+ if test -e .git/created_test_file
+ then
+ git reset &&
+ git clean -df
+ fi
+ '
+
+ printf '%s' "$text" >.git/expected_test_file
+
+ test_expect_success EXPENSIVE_ON_WINDOWS "setup match file test for $text" '
+ file=$(cat .git/expected_test_file) &&
+ if should_create_test_file "$file"
+ then
+ dirs=${file%/*}
+ if test "$file" != "$dirs"
+ then
+ mkdir -p -- "$dirs" &&
+ touch -- "./$text"
+ else
+ touch -- "./$file"
+ fi &&
+ git add -A &&
+ printf "%s" "$file" >.git/created_test_file
+ elif test -e .git/created_test_file
+ then
+ rm .git/created_test_file
+ fi
+ '
+
+ # $1: Case sensitive glob match: test-wildmatch & ls-files
+ match_with_function "$text" "$pattern" $match_glob "wildmatch"
+ match_with_ls_files "$text" "$pattern" $match_file_glob "wildmatch" " --glob-pathspecs"
+
+ # $2: Case insensitive glob match: test-wildmatch & ls-files
+ match_with_function "$text" "$pattern" $match_iglob "iwildmatch"
+ match_with_ls_files "$text" "$pattern" $match_file_iglob "iwildmatch" " --glob-pathspecs --icase-pathspecs"
+
+ # $3: Case sensitive path match: test-wildmatch & ls-files
+ match_with_function "$text" "$pattern" $match_pathmatch "pathmatch"
+ match_with_ls_files "$text" "$pattern" $match_file_pathmatch "pathmatch" ""
+
+ # $4: Case insensitive path match: test-wildmatch & ls-files
+ match_with_function "$text" "$pattern" $match_pathmatchi "ipathmatch"
+ match_with_ls_files "$text" "$pattern" $match_file_pathmatchi "ipathmatch" " --icase-pathspecs"
}
-# Basic wildmat features
-match 1 1 foo foo
-match 0 0 foo bar
-match 1 1 '' ""
-match 1 1 foo '???'
-match 0 0 foo '??'
-match 1 1 foo '*'
-match 1 1 foo 'f*'
-match 0 0 foo '*f'
-match 1 1 foo '*foo*'
-match 1 1 foobar '*ob*a*r*'
-match 1 1 aaaaaaabababab '*ab'
-match 1 1 'foo*' 'foo\*'
-match 0 0 foobar 'foo\*bar'
-match 1 1 'f\oo' 'f\\oo'
-match 1 1 ball '*[al]?'
-match 0 0 ten '[ten]'
-match 0 1 ten '**[!te]'
-match 0 0 ten '**[!ten]'
-match 1 1 ten 't[a-g]n'
-match 0 0 ten 't[!a-g]n'
-match 1 1 ton 't[!a-g]n'
-match 1 1 ton 't[^a-g]n'
-match 1 x 'a]b' 'a[]]b'
-match 1 x a-b 'a[]-]b'
-match 1 x 'a]b' 'a[]-]b'
-match 0 x aab 'a[]-]b'
-match 1 x aab 'a[]a-]b'
-match 1 1 ']' ']'
+# Basic wildmatch features
+match 1 1 1 1 foo foo
+match 0 0 0 0 foo bar
+match 1 1 1 1 '' ""
+match 1 1 1 1 foo '???'
+match 0 0 0 0 foo '??'
+match 1 1 1 1 foo '*'
+match 1 1 1 1 foo 'f*'
+match 0 0 0 0 foo '*f'
+match 1 1 1 1 foo '*foo*'
+match 1 1 1 1 foobar '*ob*a*r*'
+match 1 1 1 1 aaaaaaabababab '*ab'
+match 1 1 1 1 'foo*' 'foo\*'
+match 0 0 0 0 foobar 'foo\*bar'
+match 1 1 1 1 'f\oo' 'f\\oo'
+match 1 1 1 1 ball '*[al]?'
+match 0 0 0 0 ten '[ten]'
+match 0 0 1 1 ten '**[!te]'
+match 0 0 0 0 ten '**[!ten]'
+match 1 1 1 1 ten 't[a-g]n'
+match 0 0 0 0 ten 't[!a-g]n'
+match 1 1 1 1 ton 't[!a-g]n'
+match 1 1 1 1 ton 't[^a-g]n'
+match 1 1 1 1 'a]b' 'a[]]b'
+match 1 1 1 1 a-b 'a[]-]b'
+match 1 1 1 1 'a]b' 'a[]-]b'
+match 0 0 0 0 aab 'a[]-]b'
+match 1 1 1 1 aab 'a[]a-]b'
+match 1 1 1 1 ']' ']'
# Extended slash-matching features
-match 0 0 'foo/baz/bar' 'foo*bar'
-match 0 0 'foo/baz/bar' 'foo**bar'
-match 0 1 'foobazbar' 'foo**bar'
-match 1 1 'foo/baz/bar' 'foo/**/bar'
-match 1 0 'foo/baz/bar' 'foo/**/**/bar'
-match 1 0 'foo/b/a/z/bar' 'foo/**/bar'
-match 1 0 'foo/b/a/z/bar' 'foo/**/**/bar'
-match 1 0 'foo/bar' 'foo/**/bar'
-match 1 0 'foo/bar' 'foo/**/**/bar'
-match 0 0 'foo/bar' 'foo?bar'
-match 0 0 'foo/bar' 'foo[/]bar'
-match 0 0 'foo/bar' 'foo[^a-z]bar'
-match 0 0 'foo/bar' 'f[^eiu][^eiu][^eiu][^eiu][^eiu]r'
-match 1 1 'foo-bar' 'f[^eiu][^eiu][^eiu][^eiu][^eiu]r'
-match 1 0 'foo' '**/foo'
-match 1 x 'XXX/foo' '**/foo'
-match 1 0 'bar/baz/foo' '**/foo'
-match 0 0 'bar/baz/foo' '*/foo'
-match 0 0 'foo/bar/baz' '**/bar*'
-match 1 0 'deep/foo/bar/baz' '**/bar/*'
-match 0 0 'deep/foo/bar/baz/' '**/bar/*'
-match 1 0 'deep/foo/bar/baz/' '**/bar/**'
-match 0 0 'deep/foo/bar' '**/bar/*'
-match 1 0 'deep/foo/bar/' '**/bar/**'
-match 0 0 'foo/bar/baz' '**/bar**'
-match 1 0 'foo/bar/baz/x' '*/bar/**'
-match 0 0 'deep/foo/bar/baz/x' '*/bar/**'
-match 1 0 'deep/foo/bar/baz/x' '**/bar/*/*'
+match 0 0 1 1 'foo/baz/bar' 'foo*bar'
+match 0 0 1 1 'foo/baz/bar' 'foo**bar'
+match 0 0 1 1 'foobazbar' 'foo**bar'
+match 1 1 1 1 'foo/baz/bar' 'foo/**/bar'
+match 1 1 0 0 'foo/baz/bar' 'foo/**/**/bar'
+match 1 1 1 1 'foo/b/a/z/bar' 'foo/**/bar'
+match 1 1 1 1 'foo/b/a/z/bar' 'foo/**/**/bar'
+match 1 1 0 0 'foo/bar' 'foo/**/bar'
+match 1 1 0 0 'foo/bar' 'foo/**/**/bar'
+match 0 0 1 1 'foo/bar' 'foo?bar'
+match 0 0 1 1 'foo/bar' 'foo[/]bar'
+match 0 0 1 1 'foo/bar' 'foo[^a-z]bar'
+match 0 0 1 1 'foo/bar' 'f[^eiu][^eiu][^eiu][^eiu][^eiu]r'
+match 1 1 1 1 'foo-bar' 'f[^eiu][^eiu][^eiu][^eiu][^eiu]r'
+match 1 1 0 0 'foo' '**/foo'
+match 1 1 1 1 'XXX/foo' '**/foo'
+match 1 1 1 1 'bar/baz/foo' '**/foo'
+match 0 0 1 1 'bar/baz/foo' '*/foo'
+match 0 0 1 1 'foo/bar/baz' '**/bar*'
+match 1 1 1 1 'deep/foo/bar/baz' '**/bar/*'
+match 0 0 1 1 'deep/foo/bar/baz/' '**/bar/*'
+match 1 1 1 1 'deep/foo/bar/baz/' '**/bar/**'
+match 0 0 0 0 'deep/foo/bar' '**/bar/*'
+match 1 1 1 1 'deep/foo/bar/' '**/bar/**'
+match 0 0 1 1 'foo/bar/baz' '**/bar**'
+match 1 1 1 1 'foo/bar/baz/x' '*/bar/**'
+match 0 0 1 1 'deep/foo/bar/baz/x' '*/bar/**'
+match 1 1 1 1 'deep/foo/bar/baz/x' '**/bar/*/*'
# Various additional tests
-match 0 0 'acrt' 'a[c-c]st'
-match 1 1 'acrt' 'a[c-c]rt'
-match 0 0 ']' '[!]-]'
-match 1 x 'a' '[!]-]'
-match 0 0 '' '\'
-match 0 x '\' '\'
-match 0 x 'XXX/\' '*/\'
-match 1 x 'XXX/\' '*/\\'
-match 1 1 'foo' 'foo'
-match 1 1 '@foo' '@foo'
-match 0 0 'foo' '@foo'
-match 1 1 '[ab]' '\[ab]'
-match 1 1 '[ab]' '[[]ab]'
-match 1 x '[ab]' '[[:]ab]'
-match 0 x '[ab]' '[[::]ab]'
-match 1 x '[ab]' '[[:digit]ab]'
-match 1 x '[ab]' '[\[:]ab]'
-match 1 1 '?a?b' '\??\?b'
-match 1 1 'abc' '\a\b\c'
-match 0 0 'foo' ''
-match 1 0 'foo/bar/baz/to' '**/t[o]'
+match 0 0 0 0 'acrt' 'a[c-c]st'
+match 1 1 1 1 'acrt' 'a[c-c]rt'
+match 0 0 0 0 ']' '[!]-]'
+match 1 1 1 1 'a' '[!]-]'
+match 0 0 0 0 '' '\'
+match 0 0 0 0 \
+ 1 1 1 1 '\' '\'
+match 0 0 0 0 'XXX/\' '*/\'
+match 1 1 1 1 'XXX/\' '*/\\'
+match 1 1 1 1 'foo' 'foo'
+match 1 1 1 1 '@foo' '@foo'
+match 0 0 0 0 'foo' '@foo'
+match 1 1 1 1 '[ab]' '\[ab]'
+match 1 1 1 1 '[ab]' '[[]ab]'
+match 1 1 1 1 '[ab]' '[[:]ab]'
+match 0 0 0 0 '[ab]' '[[::]ab]'
+match 1 1 1 1 '[ab]' '[[:digit]ab]'
+match 1 1 1 1 '[ab]' '[\[:]ab]'
+match 1 1 1 1 '?a?b' '\??\?b'
+match 1 1 1 1 'abc' '\a\b\c'
+match 0 0 0 0 \
+ E E E E 'foo' ''
+match 1 1 1 1 'foo/bar/baz/to' '**/t[o]'
# Character class tests
-match 1 x 'a1B' '[[:alpha:]][[:digit:]][[:upper:]]'
-match 0 x 'a' '[[:digit:][:upper:][:space:]]'
-match 1 x 'A' '[[:digit:][:upper:][:space:]]'
-match 1 x '1' '[[:digit:][:upper:][:space:]]'
-match 0 x '1' '[[:digit:][:upper:][:spaci:]]'
-match 1 x ' ' '[[:digit:][:upper:][:space:]]'
-match 0 x '.' '[[:digit:][:upper:][:space:]]'
-match 1 x '.' '[[:digit:][:punct:][:space:]]'
-match 1 x '5' '[[:xdigit:]]'
-match 1 x 'f' '[[:xdigit:]]'
-match 1 x 'D' '[[:xdigit:]]'
-match 1 x '_' '[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]'
-match 1 x '.' '[^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:lower:][:space:][:upper:][:xdigit:]]'
-match 1 x '5' '[a-c[:digit:]x-z]'
-match 1 x 'b' '[a-c[:digit:]x-z]'
-match 1 x 'y' '[a-c[:digit:]x-z]'
-match 0 x 'q' '[a-c[:digit:]x-z]'
-
-# Additional tests, including some malformed wildmats
-match 1 x ']' '[\\-^]'
-match 0 0 '[' '[\\-^]'
-match 1 x '-' '[\-_]'
-match 1 x ']' '[\]]'
-match 0 0 '\]' '[\]]'
-match 0 0 '\' '[\]]'
-match 0 0 'ab' 'a[]b'
-match 0 x 'a[]b' 'a[]b'
-match 0 x 'ab[' 'ab['
-match 0 0 'ab' '[!'
-match 0 0 'ab' '[-'
-match 1 1 '-' '[-]'
-match 0 0 '-' '[a-'
-match 0 0 '-' '[!a-'
-match 1 x '-' '[--A]'
-match 1 x '5' '[--A]'
-match 1 1 ' ' '[ --]'
-match 1 1 '$' '[ --]'
-match 1 1 '-' '[ --]'
-match 0 0 '0' '[ --]'
-match 1 x '-' '[---]'
-match 1 x '-' '[------]'
-match 0 0 'j' '[a-e-n]'
-match 1 x '-' '[a-e-n]'
-match 1 x 'a' '[!------]'
-match 0 0 '[' '[]-a]'
-match 1 x '^' '[]-a]'
-match 0 0 '^' '[!]-a]'
-match 1 x '[' '[!]-a]'
-match 1 1 '^' '[a^bc]'
-match 1 x '-b]' '[a-]b]'
-match 0 0 '\' '[\]'
-match 1 1 '\' '[\\]'
-match 0 0 '\' '[!\\]'
-match 1 1 'G' '[A-\\]'
-match 0 0 'aaabbb' 'b*a'
-match 0 0 'aabcaa' '*ba*'
-match 1 1 ',' '[,]'
-match 1 1 ',' '[\\,]'
-match 1 1 '\' '[\\,]'
-match 1 1 '-' '[,-.]'
-match 0 0 '+' '[,-.]'
-match 0 0 '-.]' '[,-.]'
-match 1 1 '2' '[\1-\3]'
-match 1 1 '3' '[\1-\3]'
-match 0 0 '4' '[\1-\3]'
-match 1 1 '\' '[[-\]]'
-match 1 1 '[' '[[-\]]'
-match 1 1 ']' '[[-\]]'
-match 0 0 '-' '[[-\]]'
+match 1 1 1 1 'a1B' '[[:alpha:]][[:digit:]][[:upper:]]'
+match 0 1 0 1 'a' '[[:digit:][:upper:][:space:]]'
+match 1 1 1 1 'A' '[[:digit:][:upper:][:space:]]'
+match 1 1 1 1 '1' '[[:digit:][:upper:][:space:]]'
+match 0 0 0 0 '1' '[[:digit:][:upper:][:spaci:]]'
+match 1 1 1 1 ' ' '[[:digit:][:upper:][:space:]]'
+match 0 0 0 0 '.' '[[:digit:][:upper:][:space:]]'
+match 1 1 1 1 '.' '[[:digit:][:punct:][:space:]]'
+match 1 1 1 1 '5' '[[:xdigit:]]'
+match 1 1 1 1 'f' '[[:xdigit:]]'
+match 1 1 1 1 'D' '[[:xdigit:]]'
+match 1 1 1 1 '_' '[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]'
+match 1 1 1 1 '.' '[^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:lower:][:space:][:upper:][:xdigit:]]'
+match 1 1 1 1 '5' '[a-c[:digit:]x-z]'
+match 1 1 1 1 'b' '[a-c[:digit:]x-z]'
+match 1 1 1 1 'y' '[a-c[:digit:]x-z]'
+match 0 0 0 0 'q' '[a-c[:digit:]x-z]'
-# Test recursion and the abort code (use "wildtest -i" to see iteration counts)
-match 1 1 '-adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1' '-*-*-*-*-*-*-12-*-*-*-m-*-*-*'
-match 0 0 '-adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1' '-*-*-*-*-*-*-12-*-*-*-m-*-*-*'
-match 0 0 '-adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1' '-*-*-*-*-*-*-12-*-*-*-m-*-*-*'
-match 1 1 'XXX/adobe/courier/bold/o/normal//12/120/75/75/m/70/iso8859/1' 'XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*'
-match 0 0 'XXX/adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1' 'XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*'
-match 1 0 'abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt' '**/*a*b*g*n*t'
-match 0 0 'abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz' '**/*a*b*g*n*t'
-match 0 x foo '*/*/*'
-match 0 x foo/bar '*/*/*'
-match 1 x foo/bba/arr '*/*/*'
-match 0 x foo/bb/aa/rr '*/*/*'
-match 1 x foo/bb/aa/rr '**/**/**'
-match 1 x abcXdefXghi '*X*i'
-match 0 x ab/cXd/efXg/hi '*X*i'
-match 1 x ab/cXd/efXg/hi '*/*X*/*/*i'
-match 1 x ab/cXd/efXg/hi '**/*X*/**/*i'
+# Additional tests, including some malformed wildmatch patterns
+match 1 1 1 1 ']' '[\\-^]'
+match 0 0 0 0 '[' '[\\-^]'
+match 1 1 1 1 '-' '[\-_]'
+match 1 1 1 1 ']' '[\]]'
+match 0 0 0 0 '\]' '[\]]'
+match 0 0 0 0 '\' '[\]]'
+match 0 0 0 0 'ab' 'a[]b'
+match 0 0 0 0 \
+ 1 1 1 1 'a[]b' 'a[]b'
+match 0 0 0 0 \
+ 1 1 1 1 'ab[' 'ab['
+match 0 0 0 0 'ab' '[!'
+match 0 0 0 0 'ab' '[-'
+match 1 1 1 1 '-' '[-]'
+match 0 0 0 0 '-' '[a-'
+match 0 0 0 0 '-' '[!a-'
+match 1 1 1 1 '-' '[--A]'
+match 1 1 1 1 '5' '[--A]'
+match 1 1 1 1 ' ' '[ --]'
+match 1 1 1 1 '$' '[ --]'
+match 1 1 1 1 '-' '[ --]'
+match 0 0 0 0 '0' '[ --]'
+match 1 1 1 1 '-' '[---]'
+match 1 1 1 1 '-' '[------]'
+match 0 0 0 0 'j' '[a-e-n]'
+match 1 1 1 1 '-' '[a-e-n]'
+match 1 1 1 1 'a' '[!------]'
+match 0 0 0 0 '[' '[]-a]'
+match 1 1 1 1 '^' '[]-a]'
+match 0 0 0 0 '^' '[!]-a]'
+match 1 1 1 1 '[' '[!]-a]'
+match 1 1 1 1 '^' '[a^bc]'
+match 1 1 1 1 '-b]' '[a-]b]'
+match 0 0 0 0 '\' '[\]'
+match 1 1 1 1 '\' '[\\]'
+match 0 0 0 0 '\' '[!\\]'
+match 1 1 1 1 'G' '[A-\\]'
+match 0 0 0 0 'aaabbb' 'b*a'
+match 0 0 0 0 'aabcaa' '*ba*'
+match 1 1 1 1 ',' '[,]'
+match 1 1 1 1 ',' '[\\,]'
+match 1 1 1 1 '\' '[\\,]'
+match 1 1 1 1 '-' '[,-.]'
+match 0 0 0 0 '+' '[,-.]'
+match 0 0 0 0 '-.]' '[,-.]'
+match 1 1 1 1 '2' '[\1-\3]'
+match 1 1 1 1 '3' '[\1-\3]'
+match 0 0 0 0 '4' '[\1-\3]'
+match 1 1 1 1 '\' '[[-\]]'
+match 1 1 1 1 '[' '[[-\]]'
+match 1 1 1 1 ']' '[[-\]]'
+match 0 0 0 0 '-' '[[-\]]'
-pathmatch 1 foo foo
-pathmatch 0 foo fo
-pathmatch 1 foo/bar foo/bar
-pathmatch 1 foo/bar 'foo/*'
-pathmatch 1 foo/bba/arr 'foo/*'
-pathmatch 1 foo/bba/arr 'foo/**'
-pathmatch 1 foo/bba/arr 'foo*'
-pathmatch 1 foo/bba/arr 'foo**'
-pathmatch 1 foo/bba/arr 'foo/*arr'
-pathmatch 1 foo/bba/arr 'foo/**arr'
-pathmatch 0 foo/bba/arr 'foo/*z'
-pathmatch 0 foo/bba/arr 'foo/**z'
-pathmatch 1 foo/bar 'foo?bar'
-pathmatch 1 foo/bar 'foo[/]bar'
-pathmatch 1 foo/bar 'foo[^a-z]bar'
-pathmatch 0 foo '*/*/*'
-pathmatch 0 foo/bar '*/*/*'
-pathmatch 1 foo/bba/arr '*/*/*'
-pathmatch 1 foo/bb/aa/rr '*/*/*'
-pathmatch 1 abcXdefXghi '*X*i'
-pathmatch 1 ab/cXd/efXg/hi '*/*X*/*/*i'
-pathmatch 1 ab/cXd/efXg/hi '*Xg*i'
+# Test recursion
+match 1 1 1 1 '-adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1' '-*-*-*-*-*-*-12-*-*-*-m-*-*-*'
+match 0 0 0 0 '-adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1' '-*-*-*-*-*-*-12-*-*-*-m-*-*-*'
+match 0 0 0 0 '-adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1' '-*-*-*-*-*-*-12-*-*-*-m-*-*-*'
+match 1 1 1 1 'XXX/adobe/courier/bold/o/normal//12/120/75/75/m/70/iso8859/1' 'XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*'
+match 0 0 0 0 'XXX/adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1' 'XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*'
+match 1 1 1 1 'abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt' '**/*a*b*g*n*t'
+match 0 0 0 0 'abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz' '**/*a*b*g*n*t'
+match 0 0 0 0 foo '*/*/*'
+match 0 0 0 0 foo/bar '*/*/*'
+match 1 1 1 1 foo/bba/arr '*/*/*'
+match 0 0 1 1 foo/bb/aa/rr '*/*/*'
+match 1 1 1 1 foo/bb/aa/rr '**/**/**'
+match 1 1 1 1 abcXdefXghi '*X*i'
+match 0 0 1 1 ab/cXd/efXg/hi '*X*i'
+match 1 1 1 1 ab/cXd/efXg/hi '*/*X*/*/*i'
+match 1 1 1 1 ab/cXd/efXg/hi '**/*X*/**/*i'
-# Case-sensitivity features
-match 0 x 'a' '[A-Z]'
-match 1 x 'A' '[A-Z]'
-match 0 x 'A' '[a-z]'
-match 1 x 'a' '[a-z]'
-match 0 x 'a' '[[:upper:]]'
-match 1 x 'A' '[[:upper:]]'
-match 0 x 'A' '[[:lower:]]'
-match 1 x 'a' '[[:lower:]]'
-match 0 x 'A' '[B-Za]'
-match 1 x 'a' '[B-Za]'
-match 0 x 'A' '[B-a]'
-match 1 x 'a' '[B-a]'
-match 0 x 'z' '[Z-y]'
-match 1 x 'Z' '[Z-y]'
+# Extra pathmatch tests
+match 0 0 0 0 foo fo
+match 1 1 1 1 foo/bar foo/bar
+match 1 1 1 1 foo/bar 'foo/*'
+match 0 0 1 1 foo/bba/arr 'foo/*'
+match 1 1 1 1 foo/bba/arr 'foo/**'
+match 0 0 1 1 foo/bba/arr 'foo*'
+match 0 0 1 1 \
+ 1 1 1 1 foo/bba/arr 'foo**'
+match 0 0 1 1 foo/bba/arr 'foo/*arr'
+match 0 0 1 1 foo/bba/arr 'foo/**arr'
+match 0 0 0 0 foo/bba/arr 'foo/*z'
+match 0 0 0 0 foo/bba/arr 'foo/**z'
+match 0 0 1 1 foo/bar 'foo?bar'
+match 0 0 1 1 foo/bar 'foo[/]bar'
+match 0 0 1 1 foo/bar 'foo[^a-z]bar'
+match 0 0 1 1 ab/cXd/efXg/hi '*Xg*i'
-imatch 1 'a' '[A-Z]'
-imatch 1 'A' '[A-Z]'
-imatch 1 'A' '[a-z]'
-imatch 1 'a' '[a-z]'
-imatch 1 'a' '[[:upper:]]'
-imatch 1 'A' '[[:upper:]]'
-imatch 1 'A' '[[:lower:]]'
-imatch 1 'a' '[[:lower:]]'
-imatch 1 'A' '[B-Za]'
-imatch 1 'a' '[B-Za]'
-imatch 1 'A' '[B-a]'
-imatch 1 'a' '[B-a]'
-imatch 1 'z' '[Z-y]'
-imatch 1 'Z' '[Z-y]'
+# Extra case-sensitivity tests
+match 0 1 0 1 'a' '[A-Z]'
+match 1 1 1 1 'A' '[A-Z]'
+match 0 1 0 1 'A' '[a-z]'
+match 1 1 1 1 'a' '[a-z]'
+match 0 1 0 1 'a' '[[:upper:]]'
+match 1 1 1 1 'A' '[[:upper:]]'
+match 0 1 0 1 'A' '[[:lower:]]'
+match 1 1 1 1 'a' '[[:lower:]]'
+match 0 1 0 1 'A' '[B-Za]'
+match 1 1 1 1 'a' '[B-Za]'
+match 0 1 0 1 'A' '[B-a]'
+match 1 1 1 1 'a' '[B-a]'
+match 0 1 0 1 'z' '[Z-y]'
+match 1 1 1 1 'Z' '[Z-y]'
test_done
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index 503a88d029..6c0b7ea4ad 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -528,7 +528,7 @@ test_expect_success 'git branch -c -f o/q o/p should work when o/p exists' '
git branch -c -f o/q o/p
'
-test_expect_success 'git branch -c qq rr/qq should fail when r exists' '
+test_expect_success 'git branch -c qq rr/qq should fail when rr exists' '
git branch qq &&
git branch rr &&
test_must_fail git branch -c qq rr/qq
diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh
index 8ac58d5ea5..72d9564747 100755
--- a/t/t3400-rebase.sh
+++ b/t/t3400-rebase.sh
@@ -277,4 +277,38 @@ EOF
test_cmp From_.msg out
'
+test_expect_success 'rebase--am.sh and --show-current-patch' '
+ test_create_repo conflict-apply &&
+ (
+ cd conflict-apply &&
+ test_commit init &&
+ echo one >>init.t &&
+ git commit -a -m one &&
+ echo two >>init.t &&
+ git commit -a -m two &&
+ git tag two &&
+ test_must_fail git rebase --onto init HEAD^ &&
+ GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&
+ grep "show.*$(git rev-parse two)" stderr
+ )
+'
+
+test_expect_success 'rebase--merge.sh and --show-current-patch' '
+ test_create_repo conflict-merge &&
+ (
+ cd conflict-merge &&
+ test_commit init &&
+ echo one >>init.t &&
+ git commit -a -m one &&
+ echo two >>init.t &&
+ git commit -a -m two &&
+ git tag two &&
+ test_must_fail git rebase --merge --onto init HEAD^ &&
+ git rebase --show-current-patch >actual.patch &&
+ GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&
+ grep "show.*REBASE_HEAD" stderr &&
+ test "$(git rev-parse REBASE_HEAD)" = "$(git rev-parse two)"
+ )
+'
+
test_done
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index ef2887bd85..c59d0384fd 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -225,6 +225,14 @@ test_expect_success 'stop on conflicting pick' '
test 0 = $(grep -c "^[^#]" < .git/rebase-merge/git-rebase-todo)
'
+test_expect_success 'show conflicted patch' '
+ GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&
+ grep "show.*REBASE_HEAD" stderr &&
+ # the original stopped-sha1 is abbreviated
+ stopped_sha1="$(git rev-parse $(cat ".git/rebase-merge/stopped-sha"))" &&
+ test "$(git rev-parse REBASE_HEAD)" = "$stopped_sha1"
+'
+
test_expect_success 'abort' '
git rebase --abort &&
test $(git rev-parse new-branch1) = $(git rev-parse HEAD) &&
@@ -919,10 +927,8 @@ test_expect_success 'rebase --exec works without -i ' '
test_expect_success 'rebase -i --exec without <CMD>' '
git reset --hard execute &&
set_fake_editor &&
- test_must_fail git rebase -i --exec 2>tmp &&
- sed -e "1d" tmp >actual &&
- test_must_fail git rebase -h >expected &&
- test_cmp expected actual &&
+ test_must_fail git rebase -i --exec 2>actual &&
+ test_i18ngrep "requires a value" actual &&
git checkout master
'
diff --git a/t/t3405-rebase-malformed.sh b/t/t3405-rebase-malformed.sh
index ff8c360cd5..cb7c6de84a 100755
--- a/t/t3405-rebase-malformed.sh
+++ b/t/t3405-rebase-malformed.sh
@@ -3,6 +3,7 @@
test_description='rebase should handle arbitrary git message'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-rebase.sh
cat >F <<\EOF
This is an example of a commit log message
@@ -25,6 +26,7 @@ test_expect_success setup '
test_tick &&
git commit -m "Initial commit" &&
git branch diff-in-message &&
+ git branch empty-message-merge &&
git checkout -b multi-line-subject &&
cat F >file2 &&
@@ -45,6 +47,11 @@ test_expect_success setup '
git cat-file commit HEAD | sed -e "1,/^\$/d" >G0 &&
+ git checkout empty-message-merge &&
+ echo file3 >file3 &&
+ git add file3 &&
+ git commit --allow-empty-message -m "" &&
+
git checkout master &&
echo One >file1 &&
@@ -69,4 +76,20 @@ test_expect_success 'rebase commit with diff in message' '
test_cmp G G0
'
+test_expect_success 'rebase -m commit with empty message' '
+ test_must_fail git rebase -m master empty-message-merge &&
+ git rebase --abort &&
+ git rebase -m --allow-empty-message master empty-message-merge
+'
+
+test_expect_success 'rebase -i commit with empty message' '
+ git checkout diff-in-message &&
+ set_fake_editor &&
+ test_must_fail env FAKE_COMMIT_MESSAGE=" " FAKE_LINES="reword 1" \
+ git rebase -i HEAD^ &&
+ git rebase --abort &&
+ FAKE_COMMIT_MESSAGE=" " FAKE_LINES="reword 1" \
+ git rebase -i --allow-empty-message HEAD^
+'
+
test_done
diff --git a/t/t3408-rebase-multi-line.sh b/t/t3408-rebase-multi-line.sh
index 6b84e6042a..e7292f5b9b 100755
--- a/t/t3408-rebase-multi-line.sh
+++ b/t/t3408-rebase-multi-line.sh
@@ -24,8 +24,23 @@ But otherwise with a sane description." &&
>elif &&
git add elif &&
test_tick &&
- git commit -m second
+ git commit -m second &&
+ git checkout -b side2 &&
+ >afile &&
+ git add afile &&
+ test_tick &&
+ git commit -m third &&
+ echo hello >afile &&
+ test_tick &&
+ git commit -a -m fourth &&
+ git checkout -b side-merge &&
+ git reset --hard HEAD^^ &&
+ git merge --no-ff -m "A merge commit log message that has a long
+summary that spills over multiple lines.
+
+But otherwise with a sane description." side2 &&
+ git branch side-merge-original
'
test_expect_success rebase '
@@ -37,5 +52,14 @@ test_expect_success rebase '
test_cmp expect actual
'
+test_expect_success rebasep '
+
+ git checkout side-merge &&
+ git rebase -p side &&
+ git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
+ git cat-file commit side-merge-original | sed -e "1,/^\$/d" >expect &&
+ test_cmp expect actual
+
+'
test_done
diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh
index 4f2a263b63..0d89f6d0f6 100755
--- a/t/t3501-revert-cherry-pick.sh
+++ b/t/t3501-revert-cherry-pick.sh
@@ -150,7 +150,10 @@ test_expect_success 'cherry-pick works with dirty renamed file' '
test_tick &&
git commit -m renamed &&
echo modified >renamed &&
- git cherry-pick refs/heads/unrelated
+ test_must_fail git cherry-pick refs/heads/unrelated >out &&
+ test_i18ngrep "Refusing to lose dirty file at renamed" out &&
+ test $(git rev-parse :0:renamed) = $(git rev-parse HEAD^:to-rename.t) &&
+ grep -q "^modified$" renamed
'
test_done
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index 058698df6a..b170fb02b8 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -10,6 +10,19 @@ then
test_done
fi
+diff_cmp () {
+ for x
+ do
+ sed -e '/^index/s/[0-9a-f]*[1-9a-f][0-9a-f]*\.\./1234567../' \
+ -e '/^index/s/\.\.[0-9a-f]*[1-9a-f][0-9a-f]*/..9abcdef/' \
+ -e '/^index/s/ 00*\.\./ 0000000../' \
+ -e '/^index/s/\.\.00*$/..0000000/' \
+ -e '/^index/s/\.\.00* /..0000000 /' \
+ "$x" >"$x.filtered"
+ done
+ test_cmp "$1.filtered" "$2.filtered"
+}
+
test_expect_success 'setup (initial)' '
echo content >file &&
git add file &&
@@ -22,20 +35,20 @@ test_expect_success 'status works (initial)' '
'
test_expect_success 'setup expected' '
-cat >expected <<EOF
-new file mode 100644
-index 0000000..d95f3ad
---- /dev/null
-+++ b/file
-@@ -0,0 +1 @@
-+content
-EOF
+ cat >expected <<-\EOF
+ new file mode 100644
+ index 0000000..d95f3ad
+ --- /dev/null
+ +++ b/file
+ @@ -0,0 +1 @@
+ +content
+ EOF
'
test_expect_success 'diff works (initial)' '
(echo d; echo 1) | git add -i >output &&
sed -ne "/new file/,/content/p" <output >diff &&
- test_cmp expected diff
+ diff_cmp expected diff
'
test_expect_success 'revert works (initial)' '
git add file &&
@@ -59,20 +72,20 @@ test_expect_success 'status works (commit)' '
'
test_expect_success 'setup expected' '
-cat >expected <<EOF
-index 180b47c..b6f2c08 100644
---- a/file
-+++ b/file
-@@ -1 +1,2 @@
- baseline
-+content
-EOF
+ cat >expected <<-\EOF
+ index 180b47c..b6f2c08 100644
+ --- a/file
+ +++ b/file
+ @@ -1 +1,2 @@
+ baseline
+ +content
+ EOF
'
test_expect_success 'diff works (commit)' '
(echo d; echo 1) | git add -i >output &&
sed -ne "/^index/,/content/p" <output >diff &&
- test_cmp expected diff
+ diff_cmp expected diff
'
test_expect_success 'revert works (commit)' '
git add file &&
@@ -83,39 +96,32 @@ test_expect_success 'revert works (commit)' '
test_expect_success 'setup expected' '
-cat >expected <<EOF
-EOF
-'
-
-test_expect_success 'setup fake editor' '
- >fake_editor.sh &&
- chmod a+x fake_editor.sh &&
- test_set_editor "$(pwd)/fake_editor.sh"
+ cat >expected <<-\EOF
+ EOF
'
test_expect_success 'dummy edit works' '
+ test_set_editor : &&
(echo e; echo a) | git add -p &&
git diff > diff &&
- test_cmp expected diff
+ diff_cmp expected diff
'
test_expect_success 'setup patch' '
-cat >patch <<EOF
-@@ -1,1 +1,4 @@
- this
-+patch
--does not
- apply
-EOF
+ cat >patch <<-\EOF
+ @@ -1,1 +1,4 @@
+ this
+ +patch
+ -does not
+ apply
+ EOF
'
test_expect_success 'setup fake editor' '
- echo "#!$SHELL_PATH" >fake_editor.sh &&
- cat >>fake_editor.sh <<\EOF &&
-mv -f "$1" oldpatch &&
-mv -f patch "$1"
-EOF
- chmod a+x fake_editor.sh &&
+ write_script "fake_editor.sh" <<-\EOF &&
+ mv -f "$1" oldpatch &&
+ mv -f patch "$1"
+ EOF
test_set_editor "$(pwd)/fake_editor.sh"
'
@@ -126,10 +132,10 @@ test_expect_success 'bad edit rejected' '
'
test_expect_success 'setup patch' '
-cat >patch <<EOF
-this patch
-is garbage
-EOF
+ cat >patch <<-\EOF
+ this patch
+ is garbage
+ EOF
'
test_expect_success 'garbage edit rejected' '
@@ -139,34 +145,34 @@ test_expect_success 'garbage edit rejected' '
'
test_expect_success 'setup patch' '
-cat >patch <<EOF
-@@ -1,0 +1,0 @@
- baseline
-+content
-+newcontent
-+lines
-EOF
+ cat >patch <<-\EOF
+ @@ -1,0 +1,0 @@
+ baseline
+ +content
+ +newcontent
+ +lines
+ EOF
'
test_expect_success 'setup expected' '
-cat >expected <<EOF
-diff --git a/file b/file
-index b5dd6c9..f910ae9 100644
---- a/file
-+++ b/file
-@@ -1,4 +1,4 @@
- baseline
- content
--newcontent
-+more
- lines
-EOF
+ cat >expected <<-\EOF
+ diff --git a/file b/file
+ index b5dd6c9..f910ae9 100644
+ --- a/file
+ +++ b/file
+ @@ -1,4 +1,4 @@
+ baseline
+ content
+ -newcontent
+ +more
+ lines
+ EOF
'
test_expect_success 'real edit works' '
(echo e; echo n; echo d) | git add -p &&
git diff >output &&
- test_cmp expected output
+ diff_cmp expected output
'
test_expect_success 'skip files similarly as commit -a' '
@@ -178,7 +184,7 @@ test_expect_success 'skip files similarly as commit -a' '
git reset &&
git commit -am commit &&
git diff >expected &&
- test_cmp expected output &&
+ diff_cmp expected output &&
git reset --hard HEAD^
'
rm -f .gitignore
@@ -222,52 +228,67 @@ test_expect_success 'setup again' '
# Write the patch file with a new line at the top and bottom
test_expect_success 'setup patch' '
-cat >patch <<EOF
-index 180b47c..b6f2c08 100644
---- a/file
-+++ b/file
-@@ -1,2 +1,4 @@
-+firstline
- baseline
- content
-+lastline
-EOF
-'
-
-# Expected output, similar to the patch but w/ diff at the top
+ cat >patch <<-\EOF
+ index 180b47c..b6f2c08 100644
+ --- a/file
+ +++ b/file
+ @@ -1,2 +1,4 @@
+ +firstline
+ baseline
+ content
+ +lastline
+ \ No newline at end of file
+ EOF
+'
+
+# Expected output, diff is similar to the patch but w/ diff at the top
test_expect_success 'setup expected' '
-cat >expected <<EOF
-diff --git a/file b/file
-index b6f2c08..61b9053 100755
---- a/file
-+++ b/file
-@@ -1,2 +1,4 @@
-+firstline
- baseline
- content
-+lastline
-EOF
+ echo diff --git a/file b/file >expected &&
+ cat patch |sed "/^index/s/ 100644/ 100755/" >>expected &&
+ cat >expected-output <<-\EOF
+ --- a/file
+ +++ b/file
+ @@ -1,2 +1,4 @@
+ +firstline
+ baseline
+ content
+ +lastline
+ \ No newline at end of file
+ @@ -1,2 +1,3 @@
+ +firstline
+ baseline
+ content
+ @@ -1,2 +2,3 @@
+ baseline
+ content
+ +lastline
+ \ No newline at end of file
+ EOF
'
# Test splitting the first patch, then adding both
-test_expect_success 'add first line works' '
+test_expect_success C_LOCALE_OUTPUT 'add first line works' '
git commit -am "clear local changes" &&
git apply patch &&
- (echo s; echo y; echo y) | git add -p file &&
- git diff --cached > diff &&
- test_cmp expected diff
+ printf "%s\n" s y y | git add -p file 2>error |
+ sed -n -e "s/^Stage this hunk[^@]*\(@@ .*\)/\1/" \
+ -e "/^[-+@ \\\\]"/p >output &&
+ test_must_be_empty error &&
+ git diff --cached >diff &&
+ diff_cmp expected diff &&
+ test_cmp expected-output output
'
test_expect_success 'setup expected' '
-cat >expected <<EOF
-diff --git a/non-empty b/non-empty
-deleted file mode 100644
-index d95f3ad..0000000
---- a/non-empty
-+++ /dev/null
-@@ -1 +0,0 @@
--content
-EOF
+ cat >expected <<-\EOF
+ diff --git a/non-empty b/non-empty
+ deleted file mode 100644
+ index d95f3ad..0000000
+ --- a/non-empty
+ +++ /dev/null
+ @@ -1 +0,0 @@
+ -content
+ EOF
'
test_expect_success 'deleting a non-empty file' '
@@ -278,15 +299,15 @@ test_expect_success 'deleting a non-empty file' '
rm non-empty &&
echo y | git add -p non-empty &&
git diff --cached >diff &&
- test_cmp expected diff
+ diff_cmp expected diff
'
test_expect_success 'setup expected' '
-cat >expected <<EOF
-diff --git a/empty b/empty
-deleted file mode 100644
-index e69de29..0000000
-EOF
+ cat >expected <<-\EOF
+ diff --git a/empty b/empty
+ deleted file mode 100644
+ index e69de29..0000000
+ EOF
'
test_expect_success 'deleting an empty file' '
@@ -297,23 +318,17 @@ test_expect_success 'deleting an empty file' '
rm empty &&
echo y | git add -p empty &&
git diff --cached >diff &&
- test_cmp expected diff
+ diff_cmp expected diff
'
test_expect_success 'split hunk setup' '
git reset --hard &&
- for i in 10 20 30 40 50 60
- do
- echo $i
- done >test &&
+ test_write_lines 10 20 30 40 50 60 >test &&
git add test &&
test_tick &&
git commit -m test &&
- for i in 10 15 20 21 22 23 24 30 40 50 60
- do
- echo $i
- done >test
+ test_write_lines 10 15 20 21 22 23 24 30 40 50 60 >test
'
test_expect_success 'split hunk "add -p (edit)"' '
@@ -334,17 +349,7 @@ test_expect_success 'split hunk "add -p (edit)"' '
'
test_expect_failure 'split hunk "add -p (no, yes, edit)"' '
- cat >test <<-\EOF &&
- 5
- 10
- 20
- 21
- 30
- 31
- 40
- 50
- 60
- EOF
+ test_write_lines 5 10 20 21 30 31 40 50 60 >test &&
git reset &&
# test sequence is s(plit), n(o), y(es), e(dit)
# q n q q is there to make sure we exit at the end.
@@ -378,7 +383,7 @@ test_expect_success 'patch mode ignores unmerged entries' '
+changed
EOF
git diff --cached >diff &&
- test_cmp expected diff
+ diff_cmp expected diff
'
test_expect_success TTY 'diffs can be colorized' '
@@ -392,6 +397,26 @@ test_expect_success TTY 'diffs can be colorized' '
grep "$(printf "\\033")" output
'
+test_expect_success TTY 'diffFilter filters diff' '
+ git reset --hard &&
+
+ echo content >test &&
+ test_config interactive.diffFilter "sed s/^/foo:/" &&
+ printf y | test_terminal git add -p >output 2>&1 &&
+
+ # avoid depending on the exact coloring or content of the prompts,
+ # and just make sure we saw our diff prefixed
+ grep foo:.*content output
+'
+
+test_expect_success TTY 'detect bogus diffFilter output' '
+ git reset --hard &&
+
+ echo content >test &&
+ test_config interactive.diffFilter "echo too-short" &&
+ printf y | test_must_fail test_terminal git add -p
+'
+
test_expect_success 'patch-mode via -i prompts for files' '
git reset --hard &&
@@ -407,7 +432,7 @@ test_expect_success 'patch-mode via -i prompts for files' '
echo test >expect &&
git diff --cached --name-only >actual &&
- test_cmp expect actual
+ diff_cmp expect actual
'
test_expect_success 'add -p handles globs' '
@@ -541,4 +566,34 @@ test_expect_success 'status ignores dirty submodules (except HEAD)' '
! grep dirty-otherwise output
'
+test_expect_success 'set up pathological context' '
+ git reset --hard &&
+ test_write_lines a a a a a a a a a a a >a &&
+ git add a &&
+ git commit -m a &&
+ test_write_lines c b a a a a a a a b a a a a >a &&
+ test_write_lines a a a a a a a b a a a a >expected-1 &&
+ test_write_lines b a a a a a a a b a a a a >expected-2 &&
+ # check editing can cope with missing header and deleted context lines
+ # as well as changes to other lines
+ test_write_lines +b " a" >patch
+'
+
+test_expect_success 'add -p works with pathological context lines' '
+ git reset &&
+ printf "%s\n" n y |
+ git add -p &&
+ git cat-file blob :a >actual &&
+ test_cmp expected-1 actual
+'
+
+test_expect_success 'add -p patch editing works with pathological context lines' '
+ git reset &&
+ # n q q below is in case edit fails
+ printf "%s\n" e y n q q |
+ git add -p &&
+ git cat-file blob :a >actual &&
+ test_cmp expected-2 actual
+'
+
test_done
diff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh
index bfde4057ad..3ea5b9bb3f 100755
--- a/t/t3905-stash-include-untracked.sh
+++ b/t/t3905-stash-include-untracked.sh
@@ -228,4 +228,56 @@ test_expect_success 'stash previously ignored file' '
test_path_is_file ignored.d/foo
'
+test_expect_success 'stash -u -- <untracked> doesnt print error' '
+ >untracked &&
+ git stash push -u -- untracked 2>actual &&
+ test_path_is_missing untracked &&
+ test_line_count = 0 actual
+'
+
+test_expect_success 'stash -u -- <untracked> leaves rest of working tree in place' '
+ >tracked &&
+ git add tracked &&
+ >untracked &&
+ git stash push -u -- untracked &&
+ test_path_is_missing untracked &&
+ test_path_is_file tracked
+'
+
+test_expect_success 'stash -u -- <tracked> <untracked> clears changes in both' '
+ >tracked &&
+ git add tracked &&
+ >untracked &&
+ git stash push -u -- tracked untracked &&
+ test_path_is_missing tracked &&
+ test_path_is_missing untracked
+'
+
+test_expect_success 'stash --all -- <ignored> stashes ignored file' '
+ >ignored.d/bar &&
+ git stash push --all -- ignored.d/bar &&
+ test_path_is_missing ignored.d/bar
+'
+
+test_expect_success 'stash --all -- <tracked> <ignored> clears changes in both' '
+ >tracked &&
+ git add tracked &&
+ >ignored.d/bar &&
+ git stash push --all -- tracked ignored.d/bar &&
+ test_path_is_missing tracked &&
+ test_path_is_missing ignored.d/bar
+'
+
+test_expect_success 'stash -u -- <ignored> leaves ignored file alone' '
+ >ignored.d/bar &&
+ git stash push -u -- ignored.d/bar &&
+ test_path_is_file ignored.d/bar
+'
+
+test_expect_success 'stash -u -- <non-existant> shows no changes when there are none' '
+ git stash push -u -- non-existant >actual &&
+ echo "No local changes to save" >expect &&
+ test_i18ncmp expect actual
+'
+
test_done
diff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh
index eadf4f6244..a07816d560 100755
--- a/t/t4001-diff-rename.sh
+++ b/t/t4001-diff-rename.sh
@@ -134,11 +134,15 @@ test_expect_success 'favour same basenames over different ones' '
git rm path1 &&
mkdir subdir &&
git mv another-path subdir/path1 &&
- git status | test_i18ngrep "renamed: .*path1 -> subdir/path1"'
+ git status >out &&
+ test_i18ngrep "renamed: .*path1 -> subdir/path1" out
+'
test_expect_success 'favour same basenames even with minor differences' '
git show HEAD:path1 | sed "s/15/16/" > subdir/path1 &&
- git status | test_i18ngrep "renamed: .*path1 -> subdir/path1"'
+ git status >out &&
+ test_i18ngrep "renamed: .*path1 -> subdir/path1" out
+'
test_expect_success 'two files with same basename and same content' '
git reset --hard &&
@@ -148,7 +152,8 @@ test_expect_success 'two files with same basename and same content' '
git add dir &&
git commit -m 2 &&
git mv dir other-dir &&
- git status | test_i18ngrep "renamed: .*dir/A/file -> other-dir/A/file"
+ git status >out &&
+ test_i18ngrep "renamed: .*dir/A/file -> other-dir/A/file" out
'
test_expect_success 'setup for many rename source candidates' '
diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh
index f10798b2df..3f9a24fd56 100755
--- a/t/t4013-diff-various.sh
+++ b/t/t4013-diff-various.sh
@@ -361,6 +361,11 @@ diff --no-index --raw dir2 dir
diff --no-index --raw --abbrev=4 dir2 dir
:noellipses diff --no-index --raw --abbrev=4 dir2 dir
diff --no-index --raw --no-abbrev dir2 dir
+
+diff-tree --pretty --root --stat --compact-summary initial
+diff-tree --pretty -R --root --stat --compact-summary initial
+diff-tree --stat --compact-summary initial mode
+diff-tree -R --stat --compact-summary initial mode
EOF
test_expect_success 'log -S requires an argument' '
diff --git a/t/t4013/diff.diff-tree_--pretty_--root_--stat_--compact-summary_initial b/t/t4013/diff.diff-tree_--pretty_--root_--stat_--compact-summary_initial
new file mode 100644
index 0000000000..d6451ff7cc
--- /dev/null
+++ b/t/t4013/diff.diff-tree_--pretty_--root_--stat_--compact-summary_initial
@@ -0,0 +1,12 @@
+$ git diff-tree --pretty --root --stat --compact-summary initial
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:00:00 2006 +0000
+
+ Initial
+
+ dir/sub (new) | 2 ++
+ file0 (new) | 3 +++
+ file2 (new) | 3 +++
+ 3 files changed, 8 insertions(+)
+$
diff --git a/t/t4013/diff.diff-tree_--pretty_-R_--root_--stat_--compact-summary_initial b/t/t4013/diff.diff-tree_--pretty_-R_--root_--stat_--compact-summary_initial
new file mode 100644
index 0000000000..1989e55cd0
--- /dev/null
+++ b/t/t4013/diff.diff-tree_--pretty_-R_--root_--stat_--compact-summary_initial
@@ -0,0 +1,12 @@
+$ git diff-tree --pretty -R --root --stat --compact-summary initial
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:00:00 2006 +0000
+
+ Initial
+
+ dir/sub (gone) | 2 --
+ file0 (gone) | 3 ---
+ file2 (gone) | 3 ---
+ 3 files changed, 8 deletions(-)
+$
diff --git a/t/t4013/diff.diff-tree_--stat_--compact-summary_initial_mode b/t/t4013/diff.diff-tree_--stat_--compact-summary_initial_mode
new file mode 100644
index 0000000000..9c7c8f63af
--- /dev/null
+++ b/t/t4013/diff.diff-tree_--stat_--compact-summary_initial_mode
@@ -0,0 +1,4 @@
+$ git diff-tree --stat --compact-summary initial mode
+ file0 (mode +x) | 0
+ 1 file changed, 0 insertions(+), 0 deletions(-)
+$
diff --git a/t/t4013/diff.diff-tree_-R_--stat_--compact-summary_initial_mode b/t/t4013/diff.diff-tree_-R_--stat_--compact-summary_initial_mode
new file mode 100644
index 0000000000..e38f3d3bfb
--- /dev/null
+++ b/t/t4013/diff.diff-tree_-R_--stat_--compact-summary_initial_mode
@@ -0,0 +1,4 @@
+$ git diff-tree -R --stat --compact-summary initial mode
+ file0 (mode -x) | 0
+ 1 file changed, 0 insertions(+), 0 deletions(-)
+$
diff --git a/t/t4018-diff-funcname.sh b/t/t4018-diff-funcname.sh
index 1795ffc3aa..22f9f88f0a 100755
--- a/t/t4018-diff-funcname.sh
+++ b/t/t4018-diff-funcname.sh
@@ -33,6 +33,7 @@ diffpatterns="
css
fortran
fountain
+ golang
html
java
matlab
diff --git a/t/t4018/golang-complex-function b/t/t4018/golang-complex-function
new file mode 100644
index 0000000000..e057dcefed
--- /dev/null
+++ b/t/t4018/golang-complex-function
@@ -0,0 +1,8 @@
+type Test struct {
+ a Type
+}
+
+func (t *Test) RIGHT(a Type) (Type, error) {
+ t.a = a
+ return ChangeMe, nil
+}
diff --git a/t/t4018/golang-func b/t/t4018/golang-func
new file mode 100644
index 0000000000..8e9c9ac7c3
--- /dev/null
+++ b/t/t4018/golang-func
@@ -0,0 +1,4 @@
+func RIGHT() {
+ a := 5
+ b := ChangeMe
+}
diff --git a/t/t4018/golang-interface b/t/t4018/golang-interface
new file mode 100644
index 0000000000..553bedec96
--- /dev/null
+++ b/t/t4018/golang-interface
@@ -0,0 +1,4 @@
+type RIGHT interface {
+ a() Type
+ b() ChangeMe
+}
diff --git a/t/t4018/golang-long-func b/t/t4018/golang-long-func
new file mode 100644
index 0000000000..ac3a77b5c4
--- /dev/null
+++ b/t/t4018/golang-long-func
@@ -0,0 +1,5 @@
+func RIGHT(aVeryVeryVeryLongVariableName AVeryVeryVeryLongType,
+ anotherLongVariableName AnotherLongType) {
+ a := 5
+ b := ChangeMe
+}
diff --git a/t/t4018/golang-struct b/t/t4018/golang-struct
new file mode 100644
index 0000000000..5deda77fee
--- /dev/null
+++ b/t/t4018/golang-struct
@@ -0,0 +1,4 @@
+type RIGHT struct {
+ a Type
+ b ChangeMe
+}
diff --git a/t/t4052-stat-output.sh b/t/t4052-stat-output.sh
index 9f563db20a..6e2cf933f7 100755
--- a/t/t4052-stat-output.sh
+++ b/t/t4052-stat-output.sh
@@ -19,17 +19,33 @@ test_expect_success 'preparation' '
git commit -m message "$name"
'
+cat >expect72 <<-'EOF'
+ ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 +
+EOF
+test_expect_success "format-patch: small change with long name gives more space to the name" '
+ git format-patch -1 --stdout >output &&
+ grep " | " output >actual &&
+ test_cmp expect72 actual
+'
+
while read cmd args
do
- cat >expect <<-'EOF'
+ cat >expect80 <<-'EOF'
...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 +
EOF
test_expect_success "$cmd: small change with long name gives more space to the name" '
git $cmd $args >output &&
grep " | " output >actual &&
- test_cmp expect actual
+ test_cmp expect80 actual
'
+done <<\EOF
+diff HEAD^ HEAD --stat
+show --stat
+log -1 --stat
+EOF
+while read cmd args
+do
cat >expect <<-'EOF'
...aaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1 +
EOF
@@ -79,11 +95,11 @@ test_expect_success 'preparation for big change tests' '
git commit -m message abcd
'
-cat >expect80 <<'EOF'
- abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+cat >expect72 <<'EOF'
+ abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
EOF
-cat >expect80-graph <<'EOF'
-| abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+cat >expect72-graph <<'EOF'
+| abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
EOF
cat >expect200 <<'EOF'
abcd | 1000 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
@@ -107,7 +123,7 @@ do
test_cmp "$expect-graph" actual
'
done <<\EOF
-ignores expect80 format-patch -1 --stdout
+ignores expect72 format-patch -1 --stdout
respects expect200 diff HEAD^ HEAD --stat
respects expect200 show --stat
respects expect200 log -1 --stat
@@ -135,7 +151,7 @@ do
test_cmp "$expect-graph" actual
'
done <<\EOF
-ignores expect80 format-patch -1 --stdout
+ignores expect72 format-patch -1 --stdout
respects expect40 diff HEAD^ HEAD --stat
respects expect40 show --stat
respects expect40 log -1 --stat
@@ -163,7 +179,7 @@ do
test_cmp "$expect-graph" actual
'
done <<\EOF
-ignores expect80 format-patch -1 --stdout
+ignores expect72 format-patch -1 --stdout
respects expect40 diff HEAD^ HEAD --stat
respects expect40 show --stat
respects expect40 log -1 --stat
@@ -250,11 +266,11 @@ show --stat
log -1 --stat
EOF
-cat >expect80 <<'EOF'
- ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 ++++++++++++++++++++
+cat >expect72 <<'EOF'
+ ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 +++++++++++++++++
EOF
-cat >expect80-graph <<'EOF'
-| ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 ++++++++++++++++++++
+cat >expect72-graph <<'EOF'
+| ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 +++++++++++++++++
EOF
cat >expect200 <<'EOF'
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | 1000 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
@@ -278,7 +294,7 @@ do
test_cmp "$expect-graph" actual
'
done <<\EOF
-ignores expect80 format-patch -1 --stdout
+ignores expect72 format-patch -1 --stdout
respects expect200 diff HEAD^ HEAD --stat
respects expect200 show --stat
respects expect200 log -1 --stat
@@ -308,7 +324,7 @@ do
test_cmp "$expect-graph" actual
'
done <<\EOF
-ignores expect80 format-patch -1 --stdout
+ignores expect72 format-patch -1 --stdout
respects expect1 diff HEAD^ HEAD --stat
respects expect1 show --stat
respects expect1 log -1 --stat
diff --git a/t/t4135-apply-weird-filenames.sh b/t/t4135-apply-weird-filenames.sh
index 27cb0009fb..c7c688fcc4 100755
--- a/t/t4135-apply-weird-filenames.sh
+++ b/t/t4135-apply-weird-filenames.sh
@@ -89,4 +89,21 @@ test_expect_success 'traditional, whitespace-damaged, colon in timezone' '
test_cmp expected "post image.txt"
'
+cat >diff-from-svn <<\EOF
+Index: Makefile
+===================================================================
+diff --git a/branches/Makefile
+deleted file mode 100644
+--- a/branches/Makefile (revision 13)
++++ /dev/null (nonexistent)
+@@ +1 0,0 @@
+-
+EOF
+
+test_expect_success 'apply handles a diff generated by Subversion' '
+ >Makefile &&
+ git apply -p2 diff-from-svn &&
+ test_path_is_missing Makefile
+'
+
test_done
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 73b67b4280..1eccfb71d0 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -662,6 +662,11 @@ test_expect_success 'am pauses on conflict' '
test -d .git/rebase-apply
'
+test_expect_success 'am --show-current-patch' '
+ git am --show-current-patch >actual.patch &&
+ test_cmp .git/rebase-apply/0001 actual.patch
+'
+
test_expect_success 'am --skip works' '
echo goodbye >expected &&
git am --skip &&
@@ -1045,4 +1050,16 @@ test_expect_success 'am works with multi-line in-body headers' '
git cat-file commit HEAD | grep "^$LONG$"
'
+test_expect_success 'am --quit keeps HEAD where it is' '
+ mkdir .git/rebase-apply &&
+ >.git/rebase-apply/last &&
+ >.git/rebase-apply/next &&
+ git rev-parse HEAD^ >.git/ORIG_HEAD &&
+ git rev-parse HEAD >expected &&
+ git am --quit &&
+ test_path_is_missing .git/rebase-apply &&
+ git rev-parse HEAD >actual &&
+ test_cmp expected actual
+'
+
test_done
diff --git a/t/t4151-am-abort.sh b/t/t4151-am-abort.sh
index 9473c2779e..16432781d2 100755
--- a/t/t4151-am-abort.sh
+++ b/t/t4151-am-abort.sh
@@ -46,9 +46,8 @@ do
test_expect_success "am$with3 --skip continue after failed am$with3" '
test_must_fail git am$with3 --skip >output &&
- test_i18ngrep "^Applying" output >output.applying &&
- test_i18ngrep "^Applying: 6$" output.applying &&
- test_i18ncmp file-2-expect file-2 &&
+ test_i18ngrep "^Applying: 6$" output &&
+ test_cmp file-2-expect file-2 &&
test ! -f .git/MERGE_RR
'
diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh
index da10478f59..ff6649ed9a 100755
--- a/t/t4201-shortlog.sh
+++ b/t/t4201-shortlog.sh
@@ -127,6 +127,11 @@ test_expect_success !MINGW 'shortlog can read --format=raw output' '
test_cmp expect out
'
+test_expect_success 'shortlog from non-git directory refuses extra arguments' '
+ test_must_fail env GIT_DIR=non-existing git shortlog foo 2>out &&
+ test_i18ngrep "too many arguments" out
+'
+
test_expect_success 'shortlog should add newline when input line matches wraplen' '
cat >expect <<\EOF &&
A U Thor (2):
diff --git a/t/t5302-pack-index.sh b/t/t5302-pack-index.sh
index c2fc584dac..d695a6082e 100755
--- a/t/t5302-pack-index.sh
+++ b/t/t5302-pack-index.sh
@@ -262,4 +262,9 @@ EOF
grep "^warning:.* expected .tagger. line" err
'
+test_expect_success 'index-pack --fsck-objects also warns upon missing tagger in tag' '
+ git index-pack --fsck-objects tag-test-${pack1}.pack 2>err &&
+ grep "^warning:.* expected .tagger. line" err
+'
+
test_done
diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh
index ec9ba9bf6e..0680dec808 100755
--- a/t/t5500-fetch-pack.sh
+++ b/t/t5500-fetch-pack.sh
@@ -482,24 +482,24 @@ test_expect_success 'set up tests of missing reference' '
test_expect_success 'test lonely missing ref' '
(
cd client &&
- test_must_fail git fetch-pack --no-progress .. refs/heads/xyzzy
- ) >/dev/null 2>error-m &&
+ test_must_fail git fetch-pack --no-progress .. refs/heads/xyzzy 2>../error-m
+ ) &&
test_i18ncmp expect-error error-m
'
test_expect_success 'test missing ref after existing' '
(
cd client &&
- test_must_fail git fetch-pack --no-progress .. refs/heads/A refs/heads/xyzzy
- ) >/dev/null 2>error-em &&
+ test_must_fail git fetch-pack --no-progress .. refs/heads/A refs/heads/xyzzy 2>../error-em
+ ) &&
test_i18ncmp expect-error error-em
'
test_expect_success 'test missing ref before existing' '
(
cd client &&
- test_must_fail git fetch-pack --no-progress .. refs/heads/xyzzy refs/heads/A
- ) >/dev/null 2>error-me &&
+ test_must_fail git fetch-pack --no-progress .. refs/heads/xyzzy refs/heads/A 2>../error-me
+ ) &&
test_i18ncmp expect-error error-me
'
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 668c54be41..da9ac00557 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -222,12 +222,9 @@ test_expect_success 'fetch uses remote ref names to describe new refs' '
(
cd descriptive &&
git fetch o 2>actual &&
- grep " -> refs/crazyheads/descriptive-branch$" actual |
- test_i18ngrep "new branch" &&
- grep " -> descriptive-tag$" actual |
- test_i18ngrep "new tag" &&
- grep " -> crazy$" actual |
- test_i18ngrep "new ref"
+ test_i18ngrep "new branch.* -> refs/crazyheads/descriptive-branch$" actual &&
+ test_i18ngrep "new tag.* -> descriptive-tag$" actual &&
+ test_i18ngrep "new ref.* -> crazy$" actual
) &&
git checkout master
'
@@ -543,82 +540,232 @@ test_expect_success "should be able to fetch with duplicate refspecs" '
set_config_tristate () {
# var=$1 val=$2
case "$2" in
- unset) test_unconfig "$1" ;;
- *) git config "$1" "$2" ;;
+ unset)
+ test_unconfig "$1"
+ ;;
+ *)
+ git config "$1" "$2"
+ key=$(echo $1 | sed -e 's/^remote\.origin/fetch/')
+ git_fetch_c="$git_fetch_c -c $key=$2"
+ ;;
esac
}
test_configured_prune () {
- fetch_prune=$1 remote_origin_prune=$2 cmdline=$3 expected=$4
+ test_configured_prune_type "$@" "name"
+ test_configured_prune_type "$@" "link"
+}
- test_expect_success "prune fetch.prune=$1 remote.origin.prune=$2${3:+ $3}; $4" '
+test_configured_prune_type () {
+ fetch_prune=$1
+ remote_origin_prune=$2
+ fetch_prune_tags=$3
+ remote_origin_prune_tags=$4
+ expected_branch=$5
+ expected_tag=$6
+ cmdline=$7
+ mode=$8
+
+ if test -z "$cmdline_setup"
+ then
+ test_expect_success 'setup cmdline_setup variable for subsequent test' '
+ remote_url="file://$(git -C one config remote.origin.url)" &&
+ remote_fetch="$(git -C one config remote.origin.fetch)" &&
+ cmdline_setup="\"$remote_url\" \"$remote_fetch\""
+ '
+ fi
+
+ if test "$mode" = 'link'
+ then
+ new_cmdline=""
+
+ if test "$cmdline" = ""
+ then
+ new_cmdline=$cmdline_setup
+ else
+ new_cmdline=$(printf "%s" "$cmdline" | perl -pe 's[origin(?!/)]["'"$remote_url"'"]g')
+ fi
+
+ if test "$fetch_prune_tags" = 'true' ||
+ test "$remote_origin_prune_tags" = 'true'
+ then
+ if ! printf '%s' "$cmdline\n" | grep -q refs/remotes/origin/
+ then
+ new_cmdline="$new_cmdline refs/tags/*:refs/tags/*"
+ fi
+ fi
+
+ cmdline="$new_cmdline"
+ fi
+
+ test_expect_success "$mode prune fetch.prune=$1 remote.origin.prune=$2 fetch.pruneTags=$3 remote.origin.pruneTags=$4${7:+ $7}; branch:$5 tag:$6" '
# make sure a newbranch is there in . and also in one
git branch -f newbranch &&
+ git tag -f newtag &&
(
cd one &&
test_unconfig fetch.prune &&
+ test_unconfig fetch.pruneTags &&
test_unconfig remote.origin.prune &&
- git fetch &&
- git rev-parse --verify refs/remotes/origin/newbranch
+ test_unconfig remote.origin.pruneTags &&
+ git fetch '"$cmdline_setup"' &&
+ git rev-parse --verify refs/remotes/origin/newbranch &&
+ git rev-parse --verify refs/tags/newtag
) &&
# now remove it
git branch -d newbranch &&
+ git tag -d newtag &&
# then test
(
cd one &&
+ git_fetch_c="" &&
set_config_tristate fetch.prune $fetch_prune &&
+ set_config_tristate fetch.pruneTags $fetch_prune_tags &&
set_config_tristate remote.origin.prune $remote_origin_prune &&
-
- git fetch $cmdline &&
- case "$expected" in
+ set_config_tristate remote.origin.pruneTags $remote_origin_prune_tags &&
+
+ if test "$mode" != "link"
+ then
+ git_fetch_c=""
+ fi &&
+ git$git_fetch_c fetch '"$cmdline"' &&
+ case "$expected_branch" in
pruned)
test_must_fail git rev-parse --verify refs/remotes/origin/newbranch
;;
kept)
git rev-parse --verify refs/remotes/origin/newbranch
;;
+ esac &&
+ case "$expected_tag" in
+ pruned)
+ test_must_fail git rev-parse --verify refs/tags/newtag
+ ;;
+ kept)
+ git rev-parse --verify refs/tags/newtag
+ ;;
esac
)
'
}
-test_configured_prune unset unset "" kept
-test_configured_prune unset unset "--no-prune" kept
-test_configured_prune unset unset "--prune" pruned
-
-test_configured_prune false unset "" kept
-test_configured_prune false unset "--no-prune" kept
-test_configured_prune false unset "--prune" pruned
-
-test_configured_prune true unset "" pruned
-test_configured_prune true unset "--prune" pruned
-test_configured_prune true unset "--no-prune" kept
-
-test_configured_prune unset false "" kept
-test_configured_prune unset false "--no-prune" kept
-test_configured_prune unset false "--prune" pruned
-
-test_configured_prune false false "" kept
-test_configured_prune false false "--no-prune" kept
-test_configured_prune false false "--prune" pruned
-
-test_configured_prune true false "" kept
-test_configured_prune true false "--prune" pruned
-test_configured_prune true false "--no-prune" kept
-
-test_configured_prune unset true "" pruned
-test_configured_prune unset true "--no-prune" kept
-test_configured_prune unset true "--prune" pruned
-
-test_configured_prune false true "" pruned
-test_configured_prune false true "--no-prune" kept
-test_configured_prune false true "--prune" pruned
-
-test_configured_prune true true "" pruned
-test_configured_prune true true "--prune" pruned
-test_configured_prune true true "--no-prune" kept
+# $1 config: fetch.prune
+# $2 config: remote.<name>.prune
+# $3 config: fetch.pruneTags
+# $4 config: remote.<name>.pruneTags
+# $5 expect: branch to be pruned?
+# $6 expect: tag to be pruned?
+# $7 git-fetch $cmdline:
+#
+# $1 $2 $3 $4 $5 $6 $7
+test_configured_prune unset unset unset unset kept kept ""
+test_configured_prune unset unset unset unset kept kept "--no-prune"
+test_configured_prune unset unset unset unset pruned kept "--prune"
+test_configured_prune unset unset unset unset kept pruned \
+ "--prune origin refs/tags/*:refs/tags/*"
+test_configured_prune unset unset unset unset pruned pruned \
+ "--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*"
+
+test_configured_prune false unset unset unset kept kept ""
+test_configured_prune false unset unset unset kept kept "--no-prune"
+test_configured_prune false unset unset unset pruned kept "--prune"
+
+test_configured_prune true unset unset unset pruned kept ""
+test_configured_prune true unset unset unset pruned kept "--prune"
+test_configured_prune true unset unset unset kept kept "--no-prune"
+
+test_configured_prune unset false unset unset kept kept ""
+test_configured_prune unset false unset unset kept kept "--no-prune"
+test_configured_prune unset false unset unset pruned kept "--prune"
+
+test_configured_prune false false unset unset kept kept ""
+test_configured_prune false false unset unset kept kept "--no-prune"
+test_configured_prune false false unset unset pruned kept "--prune"
+test_configured_prune false false unset unset kept pruned \
+ "--prune origin refs/tags/*:refs/tags/*"
+test_configured_prune false false unset unset pruned pruned \
+ "--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*"
+
+test_configured_prune true false unset unset kept kept ""
+test_configured_prune true false unset unset pruned kept "--prune"
+test_configured_prune true false unset unset kept kept "--no-prune"
+
+test_configured_prune unset true unset unset pruned kept ""
+test_configured_prune unset true unset unset kept kept "--no-prune"
+test_configured_prune unset true unset unset pruned kept "--prune"
+
+test_configured_prune false true unset unset pruned kept ""
+test_configured_prune false true unset unset kept kept "--no-prune"
+test_configured_prune false true unset unset pruned kept "--prune"
+
+test_configured_prune true true unset unset pruned kept ""
+test_configured_prune true true unset unset pruned kept "--prune"
+test_configured_prune true true unset unset kept kept "--no-prune"
+test_configured_prune true true unset unset kept pruned \
+ "--prune origin refs/tags/*:refs/tags/*"
+test_configured_prune true true unset unset pruned pruned \
+ "--prune origin refs/tags/*:refs/tags/* +refs/heads/*:refs/remotes/origin/*"
+
+# --prune-tags on its own does nothing, needs --prune as well, same
+# for for fetch.pruneTags without fetch.prune
+test_configured_prune unset unset unset unset kept kept "--prune-tags"
+test_configured_prune unset unset true unset kept kept ""
+test_configured_prune unset unset unset true kept kept ""
+
+# These will prune the tags
+test_configured_prune unset unset unset unset pruned pruned "--prune --prune-tags"
+test_configured_prune true unset true unset pruned pruned ""
+test_configured_prune unset true unset true pruned pruned ""
+
+# remote.<name>.pruneTags overrides fetch.pruneTags, just like
+# remote.<name>.prune overrides fetch.prune if set.
+test_configured_prune true unset true unset pruned pruned ""
+test_configured_prune false true false true pruned pruned ""
+test_configured_prune true false true false kept kept ""
+
+# When --prune-tags is supplied it's ignored if an explicit refspec is
+# given, same for the configuration options.
+test_configured_prune unset unset unset unset pruned kept \
+ "--prune --prune-tags origin +refs/heads/*:refs/remotes/origin/*"
+test_configured_prune unset unset true unset pruned kept \
+ "--prune origin +refs/heads/*:refs/remotes/origin/*"
+test_configured_prune unset unset unset true pruned kept \
+ "--prune origin +refs/heads/*:refs/remotes/origin/*"
+
+# Pruning that also takes place if a file:// url replaces a named
+# remote. However, because there's no implicit
+# +refs/heads/*:refs/remotes/origin/* refspec and supplying it on the
+# command-line negates --prune-tags, the branches will not be pruned.
+test_configured_prune_type unset unset unset unset kept kept "origin --prune-tags" "name"
+test_configured_prune_type unset unset unset unset kept kept "origin --prune-tags" "link"
+test_configured_prune_type unset unset unset unset pruned pruned "origin --prune --prune-tags" "name"
+test_configured_prune_type unset unset unset unset kept pruned "origin --prune --prune-tags" "link"
+test_configured_prune_type unset unset unset unset pruned pruned "--prune --prune-tags origin" "name"
+test_configured_prune_type unset unset unset unset kept pruned "--prune --prune-tags origin" "link"
+test_configured_prune_type unset unset true unset pruned pruned "--prune origin" "name"
+test_configured_prune_type unset unset true unset kept pruned "--prune origin" "link"
+test_configured_prune_type unset unset unset true pruned pruned "--prune origin" "name"
+test_configured_prune_type unset unset unset true kept pruned "--prune origin" "link"
+test_configured_prune_type true unset true unset pruned pruned "origin" "name"
+test_configured_prune_type true unset true unset kept pruned "origin" "link"
+test_configured_prune_type unset true true unset pruned pruned "origin" "name"
+test_configured_prune_type unset true true unset kept pruned "origin" "link"
+test_configured_prune_type unset true unset true pruned pruned "origin" "name"
+test_configured_prune_type unset true unset true kept pruned "origin" "link"
+
+# When all remote.origin.fetch settings are deleted a --prune
+# --prune-tags still implicitly supplies refs/tags/*:refs/tags/* so
+# tags, but not tracking branches, will be deleted.
+test_expect_success 'remove remote.origin.fetch "one"' '
+ (
+ cd one &&
+ git config --unset-all remote.origin.fetch
+ )
+'
+test_configured_prune_type unset unset unset unset kept pruned "origin --prune --prune-tags" "name"
+test_configured_prune_type unset unset unset unset kept pruned "origin --prune --prune-tags" "link"
test_expect_success 'all boundary commits are excluded' '
test_commit base &&
diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh
index a552ad4ead..9cc4b569c0 100755
--- a/t/t5526-fetch-submodules.sh
+++ b/t/t5526-fetch-submodules.sh
@@ -85,7 +85,7 @@ test_expect_success "fetch --recurse-submodules -j2 has the same output behaviou
add_upstream_commit &&
(
cd downstream &&
- GIT_TRACE=$(pwd)/../trace.out git fetch --recurse-submodules -j2 2>../actual.err
+ GIT_TRACE="$TRASH_DIRECTORY/trace.out" git fetch --recurse-submodules -j2 2>../actual.err
) &&
test_must_be_empty actual.out &&
test_i18ncmp expect.err actual.err &&
@@ -485,7 +485,7 @@ test_expect_success "don't fetch submodule when newly recorded commits are alrea
)
'
-test_expect_success "'fetch.recurseSubmodules=on-demand' works also without .gitmodule entry" '
+test_expect_success "'fetch.recurseSubmodules=on-demand' works also without .gitmodules entry" '
(
cd downstream &&
git fetch --recurse-submodules
diff --git a/t/t5536-fetch-conflicts.sh b/t/t5536-fetch-conflicts.sh
index 2e42cf3316..91f28c2f78 100755
--- a/t/t5536-fetch-conflicts.sh
+++ b/t/t5536-fetch-conflicts.sh
@@ -18,14 +18,6 @@ setup_repository () {
)
}
-verify_stderr () {
- cat >expected &&
- # We're not interested in the error
- # "fatal: The remote end hung up unexpectedly":
- test_i18ngrep -E '^(fatal|warning):' <error | grep -v 'hung up' >actual | sort &&
- test_i18ncmp expected actual
-}
-
test_expect_success 'setup' '
git commit --allow-empty -m "Initial" &&
git branch branch1 &&
@@ -48,9 +40,7 @@ test_expect_success 'fetch conflict: config vs. config' '
"+refs/heads/branch2:refs/remotes/origin/branch1" && (
cd ccc &&
test_must_fail git fetch origin 2>error &&
- verify_stderr <<-\EOF
- fatal: Cannot fetch both refs/heads/branch1 and refs/heads/branch2 to refs/remotes/origin/branch1
- EOF
+ test_i18ngrep "fatal: Cannot fetch both refs/heads/branch1 and refs/heads/branch2 to refs/remotes/origin/branch1" error
)
'
@@ -77,9 +67,7 @@ test_expect_success 'fetch conflict: arg vs. arg' '
test_must_fail git fetch origin \
refs/heads/*:refs/remotes/origin/* \
refs/heads/branch2:refs/remotes/origin/branch1 2>error &&
- verify_stderr <<-\EOF
- fatal: Cannot fetch both refs/heads/branch1 and refs/heads/branch2 to refs/remotes/origin/branch1
- EOF
+ test_i18ngrep "fatal: Cannot fetch both refs/heads/branch1 and refs/heads/branch2 to refs/remotes/origin/branch1" error
)
'
@@ -90,10 +78,8 @@ test_expect_success 'fetch conflict: criss-cross args' '
git fetch origin \
refs/heads/branch1:refs/remotes/origin/branch2 \
refs/heads/branch2:refs/remotes/origin/branch1 2>error &&
- verify_stderr <<-\EOF
- warning: refs/remotes/origin/branch1 usually tracks refs/heads/branch1, not refs/heads/branch2
- warning: refs/remotes/origin/branch2 usually tracks refs/heads/branch2, not refs/heads/branch1
- EOF
+ test_i18ngrep "warning: refs/remotes/origin/branch1 usually tracks refs/heads/branch1, not refs/heads/branch2" error &&
+ test_i18ngrep "warning: refs/remotes/origin/branch2 usually tracks refs/heads/branch2, not refs/heads/branch1" error
)
'
diff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh
index d38bf32470..21340e89c9 100755
--- a/t/t5541-http-push-smart.sh
+++ b/t/t5541-http-push-smart.sh
@@ -234,7 +234,7 @@ test_expect_success TTY 'push --no-progress silences progress but not status' '
test_commit no-progress &&
test_terminal git push --no-progress >output 2>&1 &&
test_i18ngrep "^To http" output &&
- test_i18ngrep ! "^Writing objects"
+ test_i18ngrep ! "^Writing objects" output
'
test_expect_success 'push --progress shows progress to non-tty' '
diff --git a/t/t5545-push-options.sh b/t/t5545-push-options.sh
index 463783789c..b47a95871c 100755
--- a/t/t5545-push-options.sh
+++ b/t/t5545-push-options.sh
@@ -217,17 +217,32 @@ test_expect_success 'invalid push option in config' '
test_refs master HEAD@{1}
'
+test_expect_success 'push options keep quoted characters intact (direct)' '
+ mk_repo_pair &&
+ git -C upstream config receive.advertisePushOptions true &&
+ test_commit -C workbench one &&
+ git -C workbench push --push-option="\"embedded quotes\"" up master &&
+ echo "\"embedded quotes\"" >expect &&
+ test_cmp expect upstream/.git/hooks/pre-receive.push_options
+'
+
. "$TEST_DIRECTORY"/lib-httpd.sh
start_httpd
-test_expect_success 'push option denied properly by http server' '
+# set up http repository for fetching/pushing, with push options config
+# bool set to $1
+mk_http_pair () {
test_when_finished "rm -rf test_http_clone" &&
- test_when_finished "rm -rf \"$HTTPD_DOCUMENT_ROOT_PATH\"/upstream.git" &&
+ test_when_finished 'rm -rf "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git' &&
mk_repo_pair &&
- git -C upstream config receive.advertisePushOptions false &&
+ git -C upstream config receive.advertisePushOptions "$1" &&
git -C upstream config http.receivepack true &&
cp -R upstream/.git "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git &&
- git clone "$HTTPD_URL"/smart/upstream test_http_clone &&
+ git clone "$HTTPD_URL"/smart/upstream test_http_clone
+}
+
+test_expect_success 'push option denied properly by http server' '
+ mk_http_pair false &&
test_commit -C test_http_clone one &&
test_must_fail git -C test_http_clone push --push-option=asdf origin master 2>actual &&
test_i18ngrep "the receiving end does not support push options" actual &&
@@ -235,13 +250,7 @@ test_expect_success 'push option denied properly by http server' '
'
test_expect_success 'push options work properly across http' '
- test_when_finished "rm -rf test_http_clone" &&
- test_when_finished "rm -rf \"$HTTPD_DOCUMENT_ROOT_PATH\"/upstream.git" &&
- mk_repo_pair &&
- git -C upstream config receive.advertisePushOptions true &&
- git -C upstream config http.receivepack true &&
- cp -R upstream/.git "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git &&
- git clone "$HTTPD_URL"/smart/upstream test_http_clone &&
+ mk_http_pair true &&
test_commit -C test_http_clone one &&
git -C test_http_clone push origin master &&
@@ -260,6 +269,15 @@ test_expect_success 'push options work properly across http' '
test_cmp expect actual
'
+test_expect_success 'push options keep quoted characters intact (http)' '
+ mk_http_pair true &&
+
+ test_commit -C test_http_clone one &&
+ git -C test_http_clone push --push-option="\"embedded quotes\"" origin master &&
+ echo "\"embedded quotes\"" >expect &&
+ test_cmp expect "$HTTPD_DOCUMENT_ROOT_PATH"/upstream.git/hooks/pre-receive.push_options
+'
+
stop_httpd
test_done
diff --git a/t/t5570-git-daemon.sh b/t/t5570-git-daemon.sh
index 755b05a8ae..0d4c52016b 100755
--- a/t/t5570-git-daemon.sh
+++ b/t/t5570-git-daemon.sh
@@ -50,7 +50,7 @@ test_expect_success 'no-op fetch -v stderr is as expected' '
'
test_expect_success 'no-op fetch without "-v" is quiet' '
- (cd clone && git fetch) 2>stderr &&
+ (cd clone && git fetch 2>../stderr) &&
! test -s stderr
'
diff --git a/t/t5616-partial-clone.sh b/t/t5616-partial-clone.sh
index 29d8631184..cee5565367 100755
--- a/t/t5616-partial-clone.sh
+++ b/t/t5616-partial-clone.sh
@@ -143,4 +143,15 @@ test_expect_success 'manual prefetch of missing objects' '
test_line_count = 0 observed.oids
'
+test_expect_success 'partial clone with transfer.fsckobjects=1 uses index-pack --fsck-objects' '
+ git init src &&
+ test_commit -C src x &&
+ test_config -C src uploadpack.allowfilter 1 &&
+ test_config -C src uploadpack.allowanysha1inwant 1 &&
+
+ GIT_TRACE="$(pwd)/trace" git -c transfer.fsckobjects=1 \
+ clone --filter="blob:none" "file://$(pwd)/src" dst &&
+ grep "git index-pack.*--fsck-objects" trace
+'
+
test_done
diff --git a/t/t5812-proto-disable-http.sh b/t/t5812-proto-disable-http.sh
index d911afd24c..872788ac8c 100755
--- a/t/t5812-proto-disable-http.sh
+++ b/t/t5812-proto-disable-http.sh
@@ -20,10 +20,7 @@ test_expect_success 'curl redirects respect whitelist' '
test_must_fail env GIT_ALLOW_PROTOCOL=http:https \
GIT_SMART_HTTP=0 \
git clone "$HTTPD_URL/ftp-redir/repo.git" 2>stderr &&
- {
- test_i18ngrep "ftp.*disabled" stderr ||
- test_i18ngrep "your curl version is too old"
- }
+ test_i18ngrep -E "(ftp.*disabled|your curl version is too old)" stderr
'
test_expect_success 'curl limits redirects' '
diff --git a/t/t6015-rev-list-show-all-parents.sh b/t/t6015-rev-list-show-all-parents.sh
deleted file mode 100755
index 3c73c93ba6..0000000000
--- a/t/t6015-rev-list-show-all-parents.sh
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/bin/sh
-
-test_description='--show-all --parents does not rewrite TREESAME commits'
-
-. ./test-lib.sh
-
-test_expect_success 'set up --show-all --parents test' '
- test_commit one foo.txt &&
- commit1=$(git rev-list -1 HEAD) &&
- test_commit two bar.txt &&
- commit2=$(git rev-list -1 HEAD) &&
- test_commit three foo.txt &&
- commit3=$(git rev-list -1 HEAD)
- '
-
-test_expect_success '--parents rewrites TREESAME parents correctly' '
- echo $commit3 $commit1 > expected &&
- echo $commit1 >> expected &&
- git rev-list --parents HEAD -- foo.txt > actual &&
- test_cmp expected actual
- '
-
-test_expect_success '--parents --show-all does not rewrites TREESAME parents' '
- echo $commit3 $commit2 > expected &&
- echo $commit2 $commit1 >> expected &&
- echo $commit1 >> expected &&
- git rev-list --parents --show-all HEAD -- foo.txt > actual &&
- test_cmp expected actual
- '
-
-test_done
diff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh
index 05ebba7afa..c01f721f13 100755
--- a/t/t6022-merge-rename.sh
+++ b/t/t6022-merge-rename.sh
@@ -242,10 +242,12 @@ test_expect_success 'merge of identical changes in a renamed file' '
rm -f A M N &&
git reset --hard &&
git checkout change+rename &&
- GIT_MERGE_VERBOSITY=3 git merge change | test_i18ngrep "^Skipped B" &&
+ GIT_MERGE_VERBOSITY=3 git merge change >out &&
+ test_i18ngrep "^Skipped B" out &&
git reset --hard HEAD^ &&
git checkout change &&
- GIT_MERGE_VERBOSITY=3 git merge change+rename | test_i18ngrep "^Skipped B"
+ GIT_MERGE_VERBOSITY=3 git merge change+rename >out &&
+ test_i18ngrep "^Skipped B" out
'
test_expect_success 'setup for rename + d/f conflicts' '
diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh
index 8f17fd9da8..716283b274 100755
--- a/t/t6040-tracking-info.sh
+++ b/t/t6040-tracking-info.sh
@@ -147,6 +147,48 @@ test_expect_success 'status -s -b (diverged from upstream)' '
'
cat >expect <<\EOF
+## b1...origin/master [different]
+EOF
+
+test_expect_success 'status -s -b --no-ahead-behind (diverged from upstream)' '
+ (
+ cd test &&
+ git checkout b1 >/dev/null &&
+ git status -s -b --no-ahead-behind | head -1
+ ) >actual &&
+ test_i18ncmp expect actual
+'
+
+cat >expect <<\EOF
+On branch b1
+Your branch and 'origin/master' have diverged,
+and have 1 and 1 different commits each, respectively.
+EOF
+
+test_expect_success 'status --long --branch' '
+ (
+ cd test &&
+ git checkout b1 >/dev/null &&
+ git status --long -b | head -3
+ ) >actual &&
+ test_i18ncmp expect actual
+'
+
+cat >expect <<\EOF
+On branch b1
+Your branch and 'origin/master' refer to different commits.
+EOF
+
+test_expect_success 'status --long --branch --no-ahead-behind' '
+ (
+ cd test &&
+ git checkout b1 >/dev/null &&
+ git status --long -b --no-ahead-behind | head -2
+ ) >actual &&
+ test_i18ncmp expect actual
+'
+
+cat >expect <<\EOF
## b5...brokenbase [gone]
EOF
diff --git a/t/t6043-merge-rename-directories.sh b/t/t6043-merge-rename-directories.sh
new file mode 100755
index 0000000000..2e28f2908d
--- /dev/null
+++ b/t/t6043-merge-rename-directories.sh
@@ -0,0 +1,3998 @@
+#!/bin/sh
+
+test_description="recursive merge with directory renames"
+# includes checking of many corner cases, with a similar methodology to:
+# t6042: corner cases with renames but not criss-cross merges
+# t6036: corner cases with both renames and criss-cross merges
+#
+# The setup for all of them, pictorially, is:
+#
+# A
+# o
+# / \
+# O o ?
+# \ /
+# o
+# B
+#
+# To help make it easier to follow the flow of tests, they have been
+# divided into sections and each test will start with a quick explanation
+# of what commits O, A, and B contain.
+#
+# Notation:
+# z/{b,c} means files z/b and z/c both exist
+# x/d_1 means file x/d exists with content d1. (Purpose of the
+# underscore notation is to differentiate different
+# files that might be renamed into each other's paths.)
+
+. ./test-lib.sh
+
+
+###########################################################################
+# SECTION 1: Basic cases we should be able to handle
+###########################################################################
+
+# Testcase 1a, Basic directory rename.
+# Commit O: z/{b,c}
+# Commit A: y/{b,c}
+# Commit B: z/{b,c,d,e/f}
+# Expected: y/{b,c,d,e/f}
+
+test_expect_success '1a-setup: Simple directory rename detection' '
+ test_create_repo 1a &&
+ (
+ cd 1a &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ git add z &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git mv z y &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ echo d >z/d &&
+ mkdir z/e &&
+ echo f >z/e/f &&
+ git add z/d z/e/f &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '1a-check: Simple directory rename detection' '
+ (
+ cd 1a &&
+
+ git checkout A^0 &&
+
+ git merge -s recursive B^0 &&
+
+ git ls-files -s >out &&
+ test_line_count = 4 out &&
+
+ git rev-parse >actual \
+ HEAD:y/b HEAD:y/c HEAD:y/d HEAD:y/e/f &&
+ git rev-parse >expect \
+ O:z/b O:z/c B:z/d B:z/e/f &&
+ test_cmp expect actual &&
+
+ git hash-object y/d >actual &&
+ git rev-parse B:z/d >expect &&
+ test_cmp expect actual &&
+
+ test_must_fail git rev-parse HEAD:z/d &&
+ test_must_fail git rev-parse HEAD:z/e/f &&
+ test_path_is_missing z/d &&
+ test_path_is_missing z/e/f
+ )
+'
+
+# Testcase 1b, Merge a directory with another
+# Commit O: z/{b,c}, y/d
+# Commit A: z/{b,c,e}, y/d
+# Commit B: y/{b,c,d}
+# Expected: y/{b,c,d,e}
+
+test_expect_success '1b-setup: Merge a directory with another' '
+ test_create_repo 1b &&
+ (
+ cd 1b &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ mkdir y &&
+ echo d >y/d &&
+ git add z y &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ echo e >z/e &&
+ git add z/e &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv z/b y &&
+ git mv z/c y &&
+ rmdir z &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '1b-check: Merge a directory with another' '
+ (
+ cd 1b &&
+
+ git checkout A^0 &&
+
+ git merge -s recursive B^0 &&
+
+ git ls-files -s >out &&
+ test_line_count = 4 out &&
+
+ git rev-parse >actual \
+ HEAD:y/b HEAD:y/c HEAD:y/d HEAD:y/e &&
+ git rev-parse >expect \
+ O:z/b O:z/c O:y/d A:z/e &&
+ test_cmp expect actual &&
+ test_must_fail git rev-parse HEAD:z/e
+ )
+'
+
+# Testcase 1c, Transitive renaming
+# (Related to testcases 3a and 6d -- when should a transitive rename apply?)
+# (Related to testcases 9c and 9d -- can transitivity repeat?)
+# (Related to testcase 12b -- joint-transitivity?)
+# Commit O: z/{b,c}, x/d
+# Commit A: y/{b,c}, x/d
+# Commit B: z/{b,c,d}
+# Expected: y/{b,c,d} (because x/d -> z/d -> y/d)
+
+test_expect_success '1c-setup: Transitive renaming' '
+ test_create_repo 1c &&
+ (
+ cd 1c &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ mkdir x &&
+ echo d >x/d &&
+ git add z x &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git mv z y &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv x/d z/d &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '1c-check: Transitive renaming' '
+ (
+ cd 1c &&
+
+ git checkout A^0 &&
+
+ git merge -s recursive B^0 &&
+
+ git ls-files -s >out &&
+ test_line_count = 3 out &&
+
+ git rev-parse >actual \
+ HEAD:y/b HEAD:y/c HEAD:y/d &&
+ git rev-parse >expect \
+ O:z/b O:z/c O:x/d &&
+ test_cmp expect actual &&
+ test_must_fail git rev-parse HEAD:x/d &&
+ test_must_fail git rev-parse HEAD:z/d &&
+ test_path_is_missing z/d
+ )
+'
+
+# Testcase 1d, Directory renames (merging two directories into one new one)
+# cause a rename/rename(2to1) conflict
+# (Related to testcases 1c and 7b)
+# Commit O. z/{b,c}, y/{d,e}
+# Commit A. x/{b,c}, y/{d,e,m,wham_1}
+# Commit B. z/{b,c,n,wham_2}, x/{d,e}
+# Expected: x/{b,c,d,e,m,n}, CONFLICT:(y/wham_1 & z/wham_2 -> x/wham)
+# Note: y/m & z/n should definitely move into x. By the same token, both
+# y/wham_1 & z/wham_2 should too...giving us a conflict.
+
+test_expect_success '1d-setup: Directory renames cause a rename/rename(2to1) conflict' '
+ test_create_repo 1d &&
+ (
+ cd 1d &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ mkdir y &&
+ echo d >y/d &&
+ echo e >y/e &&
+ git add z y &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git mv z x &&
+ echo m >y/m &&
+ echo wham1 >y/wham &&
+ git add y &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv y x &&
+ echo n >z/n &&
+ echo wham2 >z/wham &&
+ git add z &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '1d-check: Directory renames cause a rename/rename(2to1) conflict' '
+ (
+ cd 1d &&
+
+ git checkout A^0 &&
+
+ test_must_fail git merge -s recursive B^0 >out &&
+ test_i18ngrep "CONFLICT (rename/rename)" out &&
+
+ git ls-files -s >out &&
+ test_line_count = 8 out &&
+ git ls-files -u >out &&
+ test_line_count = 2 out &&
+ git ls-files -o >out &&
+ test_line_count = 3 out &&
+
+ git rev-parse >actual \
+ :0:x/b :0:x/c :0:x/d :0:x/e :0:x/m :0:x/n &&
+ git rev-parse >expect \
+ O:z/b O:z/c O:y/d O:y/e A:y/m B:z/n &&
+ test_cmp expect actual &&
+
+ test_must_fail git rev-parse :0:x/wham &&
+ git rev-parse >actual \
+ :2:x/wham :3:x/wham &&
+ git rev-parse >expect \
+ A:y/wham B:z/wham &&
+ test_cmp expect actual &&
+
+ test_path_is_missing x/wham &&
+ test_path_is_file x/wham~HEAD &&
+ test_path_is_file x/wham~B^0 &&
+
+ git hash-object >actual \
+ x/wham~HEAD x/wham~B^0 &&
+ git rev-parse >expect \
+ A:y/wham B:z/wham &&
+ test_cmp expect actual
+ )
+'
+
+# Testcase 1e, Renamed directory, with all filenames being renamed too
+# (Related to testcases 9f & 9g)
+# Commit O: z/{oldb,oldc}
+# Commit A: y/{newb,newc}
+# Commit B: z/{oldb,oldc,d}
+# Expected: y/{newb,newc,d}
+
+test_expect_success '1e-setup: Renamed directory, with all files being renamed too' '
+ test_create_repo 1e &&
+ (
+ cd 1e &&
+
+ mkdir z &&
+ echo b >z/oldb &&
+ echo c >z/oldc &&
+ git add z &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ mkdir y &&
+ git mv z/oldb y/newb &&
+ git mv z/oldc y/newc &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ echo d >z/d &&
+ git add z/d &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '1e-check: Renamed directory, with all files being renamed too' '
+ (
+ cd 1e &&
+
+ git checkout A^0 &&
+
+ git merge -s recursive B^0 &&
+
+ git ls-files -s >out &&
+ test_line_count = 3 out &&
+
+ git rev-parse >actual \
+ HEAD:y/newb HEAD:y/newc HEAD:y/d &&
+ git rev-parse >expect \
+ O:z/oldb O:z/oldc B:z/d &&
+ test_cmp expect actual &&
+ test_must_fail git rev-parse HEAD:z/d
+ )
+'
+
+# Testcase 1f, Split a directory into two other directories
+# (Related to testcases 3a, all of section 2, and all of section 4)
+# Commit O: z/{b,c,d,e,f}
+# Commit A: z/{b,c,d,e,f,g}
+# Commit B: y/{b,c}, x/{d,e,f}
+# Expected: y/{b,c}, x/{d,e,f,g}
+
+test_expect_success '1f-setup: Split a directory into two other directories' '
+ test_create_repo 1f &&
+ (
+ cd 1f &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ echo d >z/d &&
+ echo e >z/e &&
+ echo f >z/f &&
+ git add z &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ echo g >z/g &&
+ git add z/g &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ mkdir y &&
+ mkdir x &&
+ git mv z/b y/ &&
+ git mv z/c y/ &&
+ git mv z/d x/ &&
+ git mv z/e x/ &&
+ git mv z/f x/ &&
+ rmdir z &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '1f-check: Split a directory into two other directories' '
+ (
+ cd 1f &&
+
+ git checkout A^0 &&
+
+ git merge -s recursive B^0 &&
+
+ git ls-files -s >out &&
+ test_line_count = 6 out &&
+
+ git rev-parse >actual \
+ HEAD:y/b HEAD:y/c HEAD:x/d HEAD:x/e HEAD:x/f HEAD:x/g &&
+ git rev-parse >expect \
+ O:z/b O:z/c O:z/d O:z/e O:z/f A:z/g &&
+ test_cmp expect actual &&
+ test_path_is_missing z/g &&
+ test_must_fail git rev-parse HEAD:z/g
+ )
+'
+
+###########################################################################
+# Rules suggested by testcases in section 1:
+#
+# We should still detect the directory rename even if it wasn't just
+# the directory renamed, but the files within it. (see 1b)
+#
+# If renames split a directory into two or more others, the directory
+# with the most renames, "wins" (see 1c). However, see the testcases
+# in section 2, plus testcases 3a and 4a.
+###########################################################################
+
+
+###########################################################################
+# SECTION 2: Split into multiple directories, with equal number of paths
+#
+# Explore the splitting-a-directory rules a bit; what happens in the
+# edge cases?
+#
+# Note that there is a closely related case of a directory not being
+# split on either side of history, but being renamed differently on
+# each side. See testcase 8e for that.
+###########################################################################
+
+# Testcase 2a, Directory split into two on one side, with equal numbers of paths
+# Commit O: z/{b,c}
+# Commit A: y/b, w/c
+# Commit B: z/{b,c,d}
+# Expected: y/b, w/c, z/d, with warning about z/ -> (y/ vs. w/) conflict
+test_expect_success '2a-setup: Directory split into two on one side, with equal numbers of paths' '
+ test_create_repo 2a &&
+ (
+ cd 2a &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ git add z &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ mkdir y &&
+ mkdir w &&
+ git mv z/b y/ &&
+ git mv z/c w/ &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ echo d >z/d &&
+ git add z/d &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '2a-check: Directory split into two on one side, with equal numbers of paths' '
+ (
+ cd 2a &&
+
+ git checkout A^0 &&
+
+ test_must_fail git merge -s recursive B^0 >out &&
+ test_i18ngrep "CONFLICT.*directory rename split" out &&
+
+ git ls-files -s >out &&
+ test_line_count = 3 out &&
+ git ls-files -u >out &&
+ test_line_count = 0 out &&
+ git ls-files -o >out &&
+ test_line_count = 1 out &&
+
+ git rev-parse >actual \
+ :0:y/b :0:w/c :0:z/d &&
+ git rev-parse >expect \
+ O:z/b O:z/c B:z/d &&
+ test_cmp expect actual
+ )
+'
+
+# Testcase 2b, Directory split into two on one side, with equal numbers of paths
+# Commit O: z/{b,c}
+# Commit A: y/b, w/c
+# Commit B: z/{b,c}, x/d
+# Expected: y/b, w/c, x/d; No warning about z/ -> (y/ vs. w/) conflict
+test_expect_success '2b-setup: Directory split into two on one side, with equal numbers of paths' '
+ test_create_repo 2b &&
+ (
+ cd 2b &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ git add z &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ mkdir y &&
+ mkdir w &&
+ git mv z/b y/ &&
+ git mv z/c w/ &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ mkdir x &&
+ echo d >x/d &&
+ git add x/d &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '2b-check: Directory split into two on one side, with equal numbers of paths' '
+ (
+ cd 2b &&
+
+ git checkout A^0 &&
+
+ git merge -s recursive B^0 >out &&
+
+ git ls-files -s >out &&
+ test_line_count = 3 out &&
+ git ls-files -u >out &&
+ test_line_count = 0 out &&
+ git ls-files -o >out &&
+ test_line_count = 1 out &&
+
+ git rev-parse >actual \
+ :0:y/b :0:w/c :0:x/d &&
+ git rev-parse >expect \
+ O:z/b O:z/c B:x/d &&
+ test_cmp expect actual &&
+ test_i18ngrep ! "CONFLICT.*directory rename split" out
+ )
+'
+
+###########################################################################
+# Rules suggested by section 2:
+#
+# None; the rule was already covered in section 1. These testcases are
+# here just to make sure the conflict resolution and necessary warning
+# messages are handled correctly.
+###########################################################################
+
+
+###########################################################################
+# SECTION 3: Path in question is the source path for some rename already
+#
+# Combining cases from Section 1 and trying to handle them could lead to
+# directory renaming detection being over-applied. So, this section
+# provides some good testcases to check that the implementation doesn't go
+# too far.
+###########################################################################
+
+# Testcase 3a, Avoid implicit rename if involved as source on other side
+# (Related to testcases 1c, 1f, and 9h)
+# Commit O: z/{b,c,d}
+# Commit A: z/{b,c,d} (no change)
+# Commit B: y/{b,c}, x/d
+# Expected: y/{b,c}, x/d
+test_expect_success '3a-setup: Avoid implicit rename if involved as source on other side' '
+ test_create_repo 3a &&
+ (
+ cd 3a &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ echo d >z/d &&
+ git add z &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ test_tick &&
+ git commit --allow-empty -m "A" &&
+
+ git checkout B &&
+ mkdir y &&
+ mkdir x &&
+ git mv z/b y/ &&
+ git mv z/c y/ &&
+ git mv z/d x/ &&
+ rmdir z &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '3a-check: Avoid implicit rename if involved as source on other side' '
+ (
+ cd 3a &&
+
+ git checkout A^0 &&
+
+ git merge -s recursive B^0 &&
+
+ git ls-files -s >out &&
+ test_line_count = 3 out &&
+
+ git rev-parse >actual \
+ HEAD:y/b HEAD:y/c HEAD:x/d &&
+ git rev-parse >expect \
+ O:z/b O:z/c O:z/d &&
+ test_cmp expect actual
+ )
+'
+
+# Testcase 3b, Avoid implicit rename if involved as source on other side
+# (Related to testcases 5c and 7c, also kind of 1e and 1f)
+# Commit O: z/{b,c,d}
+# Commit A: y/{b,c}, x/d
+# Commit B: z/{b,c}, w/d
+# Expected: y/{b,c}, CONFLICT:(z/d -> x/d vs. w/d)
+# NOTE: We're particularly checking that since z/d is already involved as
+# a source in a file rename on the same side of history, that we don't
+# get it involved in directory rename detection. If it were, we might
+# end up with CONFLICT:(z/d -> y/d vs. x/d vs. w/d), i.e. a
+# rename/rename/rename(1to3) conflict, which is just weird.
+test_expect_success '3b-setup: Avoid implicit rename if involved as source on current side' '
+ test_create_repo 3b &&
+ (
+ cd 3b &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ echo d >z/d &&
+ git add z &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ mkdir y &&
+ mkdir x &&
+ git mv z/b y/ &&
+ git mv z/c y/ &&
+ git mv z/d x/ &&
+ rmdir z &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ mkdir w &&
+ git mv z/d w/ &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '3b-check: Avoid implicit rename if involved as source on current side' '
+ (
+ cd 3b &&
+
+ git checkout A^0 &&
+
+ test_must_fail git merge -s recursive B^0 >out &&
+ test_i18ngrep CONFLICT.*rename/rename.*z/d.*x/d.*w/d out &&
+ test_i18ngrep ! CONFLICT.*rename/rename.*y/d out &&
+
+ git ls-files -s >out &&
+ test_line_count = 5 out &&
+ git ls-files -u >out &&
+ test_line_count = 3 out &&
+ git ls-files -o >out &&
+ test_line_count = 1 out &&
+
+ git rev-parse >actual \
+ :0:y/b :0:y/c :1:z/d :2:x/d :3:w/d &&
+ git rev-parse >expect \
+ O:z/b O:z/c O:z/d O:z/d O:z/d &&
+ test_cmp expect actual &&
+
+ test_path_is_missing z/d &&
+ git hash-object >actual \
+ x/d w/d &&
+ git rev-parse >expect \
+ O:z/d O:z/d &&
+ test_cmp expect actual
+ )
+'
+
+###########################################################################
+# Rules suggested by section 3:
+#
+# Avoid directory-rename-detection for a path, if that path is the source
+# of a rename on either side of a merge.
+###########################################################################
+
+
+###########################################################################
+# SECTION 4: Partially renamed directory; still exists on both sides of merge
+#
+# What if we were to attempt to do directory rename detection when someone
+# "mostly" moved a directory but still left some files around, or,
+# equivalently, fully renamed a directory in one commmit and then recreated
+# that directory in a later commit adding some new files and then tried to
+# merge?
+#
+# It's hard to divine user intent in these cases, because you can make an
+# argument that, depending on the intermediate history of the side being
+# merged, that some users will want files in that directory to
+# automatically be detected and renamed, while users with a different
+# intermediate history wouldn't want that rename to happen.
+#
+# I think that it is best to simply not have directory rename detection
+# apply to such cases. My reasoning for this is four-fold: (1) it's
+# easiest for users in general to figure out what happened if we don't
+# apply directory rename detection in any such case, (2) it's an easy rule
+# to explain ["We don't do directory rename detection if the directory
+# still exists on both sides of the merge"], (3) we can get some hairy
+# edge/corner cases that would be really confusing and possibly not even
+# representable in the index if we were to even try, and [related to 3] (4)
+# attempting to resolve this issue of divining user intent by examining
+# intermediate history goes against the spirit of three-way merges and is a
+# path towards crazy corner cases that are far more complex than what we're
+# already dealing with.
+#
+# Note that the wording of the rule ("We don't do directory rename
+# detection if the directory still exists on both sides of the merge.")
+# also excludes "renaming" of a directory into a subdirectory of itself
+# (e.g. /some/dir/* -> /some/dir/subdir/*). It may be possible to carve
+# out an exception for "renaming"-beneath-itself cases without opening
+# weird edge/corner cases for other partial directory renames, but for now
+# we are keeping the rule simple.
+#
+# This section contains a test for a partially-renamed-directory case.
+###########################################################################
+
+# Testcase 4a, Directory split, with original directory still present
+# (Related to testcase 1f)
+# Commit O: z/{b,c,d,e}
+# Commit A: y/{b,c,d}, z/e
+# Commit B: z/{b,c,d,e,f}
+# Expected: y/{b,c,d}, z/{e,f}
+# NOTE: Even though most files from z moved to y, we don't want f to follow.
+
+test_expect_success '4a-setup: Directory split, with original directory still present' '
+ test_create_repo 4a &&
+ (
+ cd 4a &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ echo d >z/d &&
+ echo e >z/e &&
+ git add z &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ mkdir y &&
+ git mv z/b y/ &&
+ git mv z/c y/ &&
+ git mv z/d y/ &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ echo f >z/f &&
+ git add z/f &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '4a-check: Directory split, with original directory still present' '
+ (
+ cd 4a &&
+
+ git checkout A^0 &&
+
+ git merge -s recursive B^0 &&
+
+ git ls-files -s >out &&
+ test_line_count = 5 out &&
+ git ls-files -u >out &&
+ test_line_count = 0 out &&
+ git ls-files -o >out &&
+ test_line_count = 1 out &&
+
+ git rev-parse >actual \
+ HEAD:y/b HEAD:y/c HEAD:y/d HEAD:z/e HEAD:z/f &&
+ git rev-parse >expect \
+ O:z/b O:z/c O:z/d O:z/e B:z/f &&
+ test_cmp expect actual
+ )
+'
+
+###########################################################################
+# Rules suggested by section 4:
+#
+# Directory-rename-detection should be turned off for any directories (as
+# a source for renames) that exist on both sides of the merge. (The "as
+# a source for renames" clarification is due to cases like 1c where
+# the target directory exists on both sides and we do want the rename
+# detection.) But, sadly, see testcase 8b.
+###########################################################################
+
+
+###########################################################################
+# SECTION 5: Files/directories in the way of subset of to-be-renamed paths
+#
+# Implicitly renaming files due to a detected directory rename could run
+# into problems if there are files or directories in the way of the paths
+# we want to rename. Explore such cases in this section.
+###########################################################################
+
+# Testcase 5a, Merge directories, other side adds files to original and target
+# Commit O: z/{b,c}, y/d
+# Commit A: z/{b,c,e_1,f}, y/{d,e_2}
+# Commit B: y/{b,c,d}
+# Expected: z/e_1, y/{b,c,d,e_2,f} + CONFLICT warning
+# NOTE: While directory rename detection is active here causing z/f to
+# become y/f, we did not apply this for z/e_1 because that would
+# give us an add/add conflict for y/e_1 vs y/e_2. This problem with
+# this add/add, is that both versions of y/e are from the same side
+# of history, giving us no way to represent this conflict in the
+# index.
+
+test_expect_success '5a-setup: Merge directories, other side adds files to original and target' '
+ test_create_repo 5a &&
+ (
+ cd 5a &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ mkdir y &&
+ echo d >y/d &&
+ git add z y &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ echo e1 >z/e &&
+ echo f >z/f &&
+ echo e2 >y/e &&
+ git add z/e z/f y/e &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv z/b y/ &&
+ git mv z/c y/ &&
+ rmdir z &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '5a-check: Merge directories, other side adds files to original and target' '
+ (
+ cd 5a &&
+
+ git checkout A^0 &&
+
+ test_must_fail git merge -s recursive B^0 >out &&
+ test_i18ngrep "CONFLICT.*implicit dir rename" out &&
+
+ git ls-files -s >out &&
+ test_line_count = 6 out &&
+ git ls-files -u >out &&
+ test_line_count = 0 out &&
+ git ls-files -o >out &&
+ test_line_count = 1 out &&
+
+ git rev-parse >actual \
+ :0:y/b :0:y/c :0:y/d :0:y/e :0:z/e :0:y/f &&
+ git rev-parse >expect \
+ O:z/b O:z/c O:y/d A:y/e A:z/e A:z/f &&
+ test_cmp expect actual
+ )
+'
+
+# Testcase 5b, Rename/delete in order to get add/add/add conflict
+# (Related to testcase 8d; these may appear slightly inconsistent to users;
+# Also related to testcases 7d and 7e)
+# Commit O: z/{b,c,d_1}
+# Commit A: y/{b,c,d_2}
+# Commit B: z/{b,c,d_1,e}, y/d_3
+# Expected: y/{b,c,e}, CONFLICT(add/add: y/d_2 vs. y/d_3)
+# NOTE: If z/d_1 in commit B were to be involved in dir rename detection, as
+# we normaly would since z/ is being renamed to y/, then this would be
+# a rename/delete (z/d_1 -> y/d_1 vs. deleted) AND an add/add/add
+# conflict of y/d_1 vs. y/d_2 vs. y/d_3. Add/add/add is not
+# representable in the index, so the existence of y/d_3 needs to
+# cause us to bail on directory rename detection for that path, falling
+# back to git behavior without the directory rename detection.
+
+test_expect_success '5b-setup: Rename/delete in order to get add/add/add conflict' '
+ test_create_repo 5b &&
+ (
+ cd 5b &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ echo d1 >z/d &&
+ git add z &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git rm z/d &&
+ git mv z y &&
+ echo d2 >y/d &&
+ git add y/d &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ mkdir y &&
+ echo d3 >y/d &&
+ echo e >z/e &&
+ git add y/d z/e &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '5b-check: Rename/delete in order to get add/add/add conflict' '
+ (
+ cd 5b &&
+
+ git checkout A^0 &&
+
+ test_must_fail git merge -s recursive B^0 >out &&
+ test_i18ngrep "CONFLICT (add/add).* y/d" out &&
+
+ git ls-files -s >out &&
+ test_line_count = 5 out &&
+ git ls-files -u >out &&
+ test_line_count = 2 out &&
+ git ls-files -o >out &&
+ test_line_count = 1 out &&
+
+ git rev-parse >actual \
+ :0:y/b :0:y/c :0:y/e :2:y/d :3:y/d &&
+ git rev-parse >expect \
+ O:z/b O:z/c B:z/e A:y/d B:y/d &&
+ test_cmp expect actual &&
+
+ test_must_fail git rev-parse :1:y/d &&
+ test_path_is_file y/d
+ )
+'
+
+# Testcase 5c, Transitive rename would cause rename/rename/rename/add/add/add
+# (Directory rename detection would result in transitive rename vs.
+# rename/rename(1to2) and turn it into a rename/rename(1to3). Further,
+# rename paths conflict with separate adds on the other side)
+# (Related to testcases 3b and 7c)
+# Commit O: z/{b,c}, x/d_1
+# Commit A: y/{b,c,d_2}, w/d_1
+# Commit B: z/{b,c,d_1,e}, w/d_3, y/d_4
+# Expected: A mess, but only a rename/rename(1to2)/add/add mess. Use the
+# presence of y/d_4 in B to avoid doing transitive rename of
+# x/d_1 -> z/d_1 -> y/d_1, so that the only paths we have at
+# y/d are y/d_2 and y/d_4. We still do the move from z/e to y/e,
+# though, because it doesn't have anything in the way.
+
+test_expect_success '5c-setup: Transitive rename would cause rename/rename/rename/add/add/add' '
+ test_create_repo 5c &&
+ (
+ cd 5c &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ mkdir x &&
+ echo d1 >x/d &&
+ git add z x &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git mv z y &&
+ echo d2 >y/d &&
+ git add y/d &&
+ git mv x w &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv x/d z/ &&
+ mkdir w &&
+ mkdir y &&
+ echo d3 >w/d &&
+ echo d4 >y/d &&
+ echo e >z/e &&
+ git add w/ y/ z/e &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '5c-check: Transitive rename would cause rename/rename/rename/add/add/add' '
+ (
+ cd 5c &&
+
+ git checkout A^0 &&
+
+ test_must_fail git merge -s recursive B^0 >out &&
+ test_i18ngrep "CONFLICT (rename/rename).*x/d.*w/d.*z/d" out &&
+ test_i18ngrep "CONFLICT (add/add).* y/d" out &&
+
+ git ls-files -s >out &&
+ test_line_count = 9 out &&
+ git ls-files -u >out &&
+ test_line_count = 6 out &&
+ git ls-files -o >out &&
+ test_line_count = 3 out &&
+
+ git rev-parse >actual \
+ :0:y/b :0:y/c :0:y/e &&
+ git rev-parse >expect \
+ O:z/b O:z/c B:z/e &&
+ test_cmp expect actual &&
+
+ test_must_fail git rev-parse :1:y/d &&
+ git rev-parse >actual \
+ :2:w/d :3:w/d :1:x/d :2:y/d :3:y/d :3:z/d &&
+ git rev-parse >expect \
+ O:x/d B:w/d O:x/d A:y/d B:y/d O:x/d &&
+ test_cmp expect actual &&
+
+ git hash-object >actual \
+ w/d~HEAD w/d~B^0 z/d &&
+ git rev-parse >expect \
+ O:x/d B:w/d O:x/d &&
+ test_cmp expect actual &&
+ test_path_is_missing x/d &&
+ test_path_is_file y/d &&
+ grep -q "<<<<" y/d # conflict markers should be present
+ )
+'
+
+# Testcase 5d, Directory/file/file conflict due to directory rename
+# Commit O: z/{b,c}
+# Commit A: y/{b,c,d_1}
+# Commit B: z/{b,c,d_2,f}, y/d/e
+# Expected: y/{b,c,d/e,f}, z/d_2, CONFLICT(file/directory), y/d_1~HEAD
+# Note: The fact that y/d/ exists in B makes us bail on directory rename
+# detection for z/d_2, but that doesn't prevent us from applying the
+# directory rename detection for z/f -> y/f.
+
+test_expect_success '5d-setup: Directory/file/file conflict due to directory rename' '
+ test_create_repo 5d &&
+ (
+ cd 5d &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ git add z &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git mv z y &&
+ echo d1 >y/d &&
+ git add y/d &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ mkdir -p y/d &&
+ echo e >y/d/e &&
+ echo d2 >z/d &&
+ echo f >z/f &&
+ git add y/d/e z/d z/f &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '5d-check: Directory/file/file conflict due to directory rename' '
+ (
+ cd 5d &&
+
+ git checkout A^0 &&
+
+ test_must_fail git merge -s recursive B^0 >out &&
+ test_i18ngrep "CONFLICT (file/directory).*y/d" out &&
+
+ git ls-files -s >out &&
+ test_line_count = 6 out &&
+ git ls-files -u >out &&
+ test_line_count = 1 out &&
+ git ls-files -o >out &&
+ test_line_count = 2 out &&
+
+ git rev-parse >actual \
+ :0:y/b :0:y/c :0:z/d :0:y/f :2:y/d :0:y/d/e &&
+ git rev-parse >expect \
+ O:z/b O:z/c B:z/d B:z/f A:y/d B:y/d/e &&
+ test_cmp expect actual &&
+
+ git hash-object y/d~HEAD >actual &&
+ git rev-parse A:y/d >expect &&
+ test_cmp expect actual
+ )
+'
+
+###########################################################################
+# Rules suggested by section 5:
+#
+# If a subset of to-be-renamed files have a file or directory in the way,
+# "turn off" the directory rename for those specific sub-paths, falling
+# back to old handling. But, sadly, see testcases 8a and 8b.
+###########################################################################
+
+
+###########################################################################
+# SECTION 6: Same side of the merge was the one that did the rename
+#
+# It may sound obvious that you only want to apply implicit directory
+# renames to directories if the _other_ side of history did the renaming.
+# If you did make an implementation that didn't explicitly enforce this
+# rule, the majority of cases that would fall under this section would
+# also be solved by following the rules from the above sections. But
+# there are still a few that stick out, so this section covers them just
+# to make sure we also get them right.
+###########################################################################
+
+# Testcase 6a, Tricky rename/delete
+# Commit O: z/{b,c,d}
+# Commit A: z/b
+# Commit B: y/{b,c}, z/d
+# Expected: y/b, CONFLICT(rename/delete, z/c -> y/c vs. NULL)
+# Note: We're just checking here that the rename of z/b and z/c to put
+# them under y/ doesn't accidentally catch z/d and make it look like
+# it is also involved in a rename/delete conflict.
+
+test_expect_success '6a-setup: Tricky rename/delete' '
+ test_create_repo 6a &&
+ (
+ cd 6a &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ echo d >z/d &&
+ git add z &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git rm z/c &&
+ git rm z/d &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ mkdir y &&
+ git mv z/b y/ &&
+ git mv z/c y/ &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '6a-check: Tricky rename/delete' '
+ (
+ cd 6a &&
+
+ git checkout A^0 &&
+
+ test_must_fail git merge -s recursive B^0 >out &&
+ test_i18ngrep "CONFLICT (rename/delete).*z/c.*y/c" out &&
+
+ git ls-files -s >out &&
+ test_line_count = 2 out &&
+ git ls-files -u >out &&
+ test_line_count = 1 out &&
+ git ls-files -o >out &&
+ test_line_count = 1 out &&
+
+ git rev-parse >actual \
+ :0:y/b :3:y/c &&
+ git rev-parse >expect \
+ O:z/b O:z/c &&
+ test_cmp expect actual
+ )
+'
+
+# Testcase 6b, Same rename done on both sides
+# (Related to testcases 6c and 8e)
+# Commit O: z/{b,c}
+# Commit A: y/{b,c}
+# Commit B: y/{b,c}, z/d
+# Expected: y/{b,c}, z/d
+# Note: If we did directory rename detection here, we'd move z/d into y/,
+# but B did that rename and still decided to put the file into z/,
+# so we probably shouldn't apply directory rename detection for it.
+
+test_expect_success '6b-setup: Same rename done on both sides' '
+ test_create_repo 6b &&
+ (
+ cd 6b &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ git add z &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git mv z y &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv z y &&
+ mkdir z &&
+ echo d >z/d &&
+ git add z/d &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '6b-check: Same rename done on both sides' '
+ (
+ cd 6b &&
+
+ git checkout A^0 &&
+
+ git merge -s recursive B^0 &&
+
+ git ls-files -s >out &&
+ test_line_count = 3 out &&
+ git ls-files -u >out &&
+ test_line_count = 0 out &&
+ git ls-files -o >out &&
+ test_line_count = 1 out &&
+
+ git rev-parse >actual \
+ HEAD:y/b HEAD:y/c HEAD:z/d &&
+ git rev-parse >expect \
+ O:z/b O:z/c B:z/d &&
+ test_cmp expect actual
+ )
+'
+
+# Testcase 6c, Rename only done on same side
+# (Related to testcases 6b and 8e)
+# Commit O: z/{b,c}
+# Commit A: z/{b,c} (no change)
+# Commit B: y/{b,c}, z/d
+# Expected: y/{b,c}, z/d
+# NOTE: Seems obvious, but just checking that the implementation doesn't
+# "accidentally detect a rename" and give us y/{b,c,d}.
+
+test_expect_success '6c-setup: Rename only done on same side' '
+ test_create_repo 6c &&
+ (
+ cd 6c &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ git add z &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ test_tick &&
+ git commit --allow-empty -m "A" &&
+
+ git checkout B &&
+ git mv z y &&
+ mkdir z &&
+ echo d >z/d &&
+ git add z/d &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '6c-check: Rename only done on same side' '
+ (
+ cd 6c &&
+
+ git checkout A^0 &&
+
+ git merge -s recursive B^0 &&
+
+ git ls-files -s >out &&
+ test_line_count = 3 out &&
+ git ls-files -u >out &&
+ test_line_count = 0 out &&
+ git ls-files -o >out &&
+ test_line_count = 1 out &&
+
+ git rev-parse >actual \
+ HEAD:y/b HEAD:y/c HEAD:z/d &&
+ git rev-parse >expect \
+ O:z/b O:z/c B:z/d &&
+ test_cmp expect actual
+ )
+'
+
+# Testcase 6d, We don't always want transitive renaming
+# (Related to testcase 1c)
+# Commit O: z/{b,c}, x/d
+# Commit A: z/{b,c}, x/d (no change)
+# Commit B: y/{b,c}, z/d
+# Expected: y/{b,c}, z/d
+# NOTE: Again, this seems obvious but just checking that the implementation
+# doesn't "accidentally detect a rename" and give us y/{b,c,d}.
+
+test_expect_success '6d-setup: We do not always want transitive renaming' '
+ test_create_repo 6d &&
+ (
+ cd 6d &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ mkdir x &&
+ echo d >x/d &&
+ git add z x &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ test_tick &&
+ git commit --allow-empty -m "A" &&
+
+ git checkout B &&
+ git mv z y &&
+ git mv x z &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '6d-check: We do not always want transitive renaming' '
+ (
+ cd 6d &&
+
+ git checkout A^0 &&
+
+ git merge -s recursive B^0 &&
+
+ git ls-files -s >out &&
+ test_line_count = 3 out &&
+ git ls-files -u >out &&
+ test_line_count = 0 out &&
+ git ls-files -o >out &&
+ test_line_count = 1 out &&
+
+ git rev-parse >actual \
+ HEAD:y/b HEAD:y/c HEAD:z/d &&
+ git rev-parse >expect \
+ O:z/b O:z/c O:x/d &&
+ test_cmp expect actual
+ )
+'
+
+# Testcase 6e, Add/add from one-side
+# Commit O: z/{b,c}
+# Commit A: z/{b,c} (no change)
+# Commit B: y/{b,c,d_1}, z/d_2
+# Expected: y/{b,c,d_1}, z/d_2
+# NOTE: Again, this seems obvious but just checking that the implementation
+# doesn't "accidentally detect a rename" and give us y/{b,c} +
+# add/add conflict on y/d_1 vs y/d_2.
+
+test_expect_success '6e-setup: Add/add from one side' '
+ test_create_repo 6e &&
+ (
+ cd 6e &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ git add z &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ test_tick &&
+ git commit --allow-empty -m "A" &&
+
+ git checkout B &&
+ git mv z y &&
+ echo d1 > y/d &&
+ mkdir z &&
+ echo d2 > z/d &&
+ git add y/d z/d &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '6e-check: Add/add from one side' '
+ (
+ cd 6e &&
+
+ git checkout A^0 &&
+
+ git merge -s recursive B^0 &&
+
+ git ls-files -s >out &&
+ test_line_count = 4 out &&
+ git ls-files -u >out &&
+ test_line_count = 0 out &&
+ git ls-files -o >out &&
+ test_line_count = 1 out &&
+
+ git rev-parse >actual \
+ HEAD:y/b HEAD:y/c HEAD:y/d HEAD:z/d &&
+ git rev-parse >expect \
+ O:z/b O:z/c B:y/d B:z/d &&
+ test_cmp expect actual
+ )
+'
+
+###########################################################################
+# Rules suggested by section 6:
+#
+# Only apply implicit directory renames to directories if the other
+# side of history is the one doing the renaming.
+###########################################################################
+
+
+###########################################################################
+# SECTION 7: More involved Edge/Corner cases
+#
+# The ruleset we have generated in the above sections seems to provide
+# well-defined merges. But can we find edge/corner cases that either (a)
+# are harder for users to understand, or (b) have a resolution that is
+# non-intuitive or suboptimal?
+#
+# The testcases in this section dive into cases that I've tried to craft in
+# a way to find some that might be surprising to users or difficult for
+# them to understand (the next section will look at non-intuitive or
+# suboptimal merge results). Some of the testcases are similar to ones
+# from past sections, but have been simplified to try to highlight error
+# messages using a "modified" path (due to the directory rename). Are
+# users okay with these?
+#
+# In my opinion, testcases that are difficult to understand from this
+# section is due to difficulty in the testcase rather than the directory
+# renaming (similar to how t6042 and t6036 have difficult resolutions due
+# to the problem setup itself being complex). And I don't think the
+# error messages are a problem.
+#
+# On the other hand, the testcases in section 8 worry me slightly more...
+###########################################################################
+
+# Testcase 7a, rename-dir vs. rename-dir (NOT split evenly) PLUS add-other-file
+# Commit O: z/{b,c}
+# Commit A: y/{b,c}
+# Commit B: w/b, x/c, z/d
+# Expected: y/d, CONFLICT(rename/rename for both z/b and z/c)
+# NOTE: There's a rename of z/ here, y/ has more renames, so z/d -> y/d.
+
+test_expect_success '7a-setup: rename-dir vs. rename-dir (NOT split evenly) PLUS add-other-file' '
+ test_create_repo 7a &&
+ (
+ cd 7a &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ git add z &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git mv z y &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ mkdir w &&
+ mkdir x &&
+ git mv z/b w/ &&
+ git mv z/c x/ &&
+ echo d > z/d &&
+ git add z/d &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '7a-check: rename-dir vs. rename-dir (NOT split evenly) PLUS add-other-file' '
+ (
+ cd 7a &&
+
+ git checkout A^0 &&
+
+ test_must_fail git merge -s recursive B^0 >out &&
+ test_i18ngrep "CONFLICT (rename/rename).*z/b.*y/b.*w/b" out &&
+ test_i18ngrep "CONFLICT (rename/rename).*z/c.*y/c.*x/c" out &&
+
+ git ls-files -s >out &&
+ test_line_count = 7 out &&
+ git ls-files -u >out &&
+ test_line_count = 6 out &&
+ git ls-files -o >out &&
+ test_line_count = 1 out &&
+
+ git rev-parse >actual \
+ :1:z/b :2:y/b :3:w/b :1:z/c :2:y/c :3:x/c :0:y/d &&
+ git rev-parse >expect \
+ O:z/b O:z/b O:z/b O:z/c O:z/c O:z/c B:z/d &&
+ test_cmp expect actual &&
+
+ git hash-object >actual \
+ y/b w/b y/c x/c &&
+ git rev-parse >expect \
+ O:z/b O:z/b O:z/c O:z/c &&
+ test_cmp expect actual
+ )
+'
+
+# Testcase 7b, rename/rename(2to1), but only due to transitive rename
+# (Related to testcase 1d)
+# Commit O: z/{b,c}, x/d_1, w/d_2
+# Commit A: y/{b,c,d_2}, x/d_1
+# Commit B: z/{b,c,d_1}, w/d_2
+# Expected: y/{b,c}, CONFLICT(rename/rename(2to1): x/d_1, w/d_2 -> y_d)
+
+test_expect_success '7b-setup: rename/rename(2to1), but only due to transitive rename' '
+ test_create_repo 7b &&
+ (
+ cd 7b &&
+
+ mkdir z &&
+ mkdir x &&
+ mkdir w &&
+ echo b >z/b &&
+ echo c >z/c &&
+ echo d1 > x/d &&
+ echo d2 > w/d &&
+ git add z x w &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git mv z y &&
+ git mv w/d y/ &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv x/d z/ &&
+ rmdir x &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '7b-check: rename/rename(2to1), but only due to transitive rename' '
+ (
+ cd 7b &&
+
+ git checkout A^0 &&
+
+ test_must_fail git merge -s recursive B^0 >out &&
+ test_i18ngrep "CONFLICT (rename/rename)" out &&
+
+ git ls-files -s >out &&
+ test_line_count = 4 out &&
+ git ls-files -u >out &&
+ test_line_count = 2 out &&
+ git ls-files -o >out &&
+ test_line_count = 3 out &&
+
+ git rev-parse >actual \
+ :0:y/b :0:y/c :2:y/d :3:y/d &&
+ git rev-parse >expect \
+ O:z/b O:z/c O:w/d O:x/d &&
+ test_cmp expect actual &&
+
+ test_path_is_missing y/d &&
+ test_path_is_file y/d~HEAD &&
+ test_path_is_file y/d~B^0 &&
+
+ git hash-object >actual \
+ y/d~HEAD y/d~B^0 &&
+ git rev-parse >expect \
+ O:w/d O:x/d &&
+ test_cmp expect actual
+ )
+'
+
+# Testcase 7c, rename/rename(1to...2or3); transitive rename may add complexity
+# (Related to testcases 3b and 5c)
+# Commit O: z/{b,c}, x/d
+# Commit A: y/{b,c}, w/d
+# Commit B: z/{b,c,d}
+# Expected: y/{b,c}, CONFLICT(x/d -> w/d vs. y/d)
+# NOTE: z/ was renamed to y/ so we do want to report
+# neither CONFLICT(x/d -> w/d vs. z/d)
+# nor CONFLiCT x/d -> w/d vs. y/d vs. z/d)
+
+test_expect_success '7c-setup: rename/rename(1to...2or3); transitive rename may add complexity' '
+ test_create_repo 7c &&
+ (
+ cd 7c &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ mkdir x &&
+ echo d >x/d &&
+ git add z x &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git mv z y &&
+ git mv x w &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv x/d z/ &&
+ rmdir x &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '7c-check: rename/rename(1to...2or3); transitive rename may add complexity' '
+ (
+ cd 7c &&
+
+ git checkout A^0 &&
+
+ test_must_fail git merge -s recursive B^0 >out &&
+ test_i18ngrep "CONFLICT (rename/rename).*x/d.*w/d.*y/d" out &&
+
+ git ls-files -s >out &&
+ test_line_count = 5 out &&
+ git ls-files -u >out &&
+ test_line_count = 3 out &&
+ git ls-files -o >out &&
+ test_line_count = 1 out &&
+
+ git rev-parse >actual \
+ :0:y/b :0:y/c :1:x/d :2:w/d :3:y/d &&
+ git rev-parse >expect \
+ O:z/b O:z/c O:x/d O:x/d O:x/d &&
+ test_cmp expect actual
+ )
+'
+
+# Testcase 7d, transitive rename involved in rename/delete; how is it reported?
+# (Related somewhat to testcases 5b and 8d)
+# Commit O: z/{b,c}, x/d
+# Commit A: y/{b,c}
+# Commit B: z/{b,c,d}
+# Expected: y/{b,c}, CONFLICT(delete x/d vs rename to y/d)
+# NOTE: z->y so NOT CONFLICT(delete x/d vs rename to z/d)
+
+test_expect_success '7d-setup: transitive rename involved in rename/delete; how is it reported?' '
+ test_create_repo 7d &&
+ (
+ cd 7d &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ mkdir x &&
+ echo d >x/d &&
+ git add z x &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git mv z y &&
+ git rm -rf x &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv x/d z/ &&
+ rmdir x &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '7d-check: transitive rename involved in rename/delete; how is it reported?' '
+ (
+ cd 7d &&
+
+ git checkout A^0 &&
+
+ test_must_fail git merge -s recursive B^0 >out &&
+ test_i18ngrep "CONFLICT (rename/delete).*x/d.*y/d" out &&
+
+ git ls-files -s >out &&
+ test_line_count = 3 out &&
+ git ls-files -u >out &&
+ test_line_count = 1 out &&
+ git ls-files -o >out &&
+ test_line_count = 1 out &&
+
+ git rev-parse >actual \
+ :0:y/b :0:y/c :3:y/d &&
+ git rev-parse >expect \
+ O:z/b O:z/c O:x/d &&
+ test_cmp expect actual
+ )
+'
+
+# Testcase 7e, transitive rename in rename/delete AND dirs in the way
+# (Very similar to 'both rename source and destination involved in D/F conflict' from t6022-merge-rename.sh)
+# (Also related to testcases 9c and 9d)
+# Commit O: z/{b,c}, x/d_1
+# Commit A: y/{b,c,d/g}, x/d/f
+# Commit B: z/{b,c,d_1}
+# Expected: rename/delete(x/d_1->y/d_1 vs. None) + D/F conflict on y/d
+# y/{b,c,d/g}, y/d_1~B^0, x/d/f
+
+# NOTE: The main path of interest here is d_1 and where it ends up, but
+# this is actually a case that has two potential directory renames
+# involved and D/F conflict(s), so it makes sense to walk through
+# each step.
+#
+# Commit A renames z/ -> y/. Thus everything that B adds to z/
+# should be instead moved to y/. This gives us the D/F conflict on
+# y/d because x/d_1 -> z/d_1 -> y/d_1 conflicts with y/d/g.
+#
+# Further, commit B renames x/ -> z/, thus everything A adds to x/
+# should instead be moved to z/...BUT we removed z/ and renamed it
+# to y/, so maybe everything should move not from x/ to z/, but
+# from x/ to z/ to y/. Doing so might make sense from the logic so
+# far, but note that commit A had both an x/ and a y/; it did the
+# renaming of z/ to y/ and created x/d/f and it clearly made these
+# things separate, so it doesn't make much sense to push these
+# together. Doing so is what I'd call a doubly transitive rename;
+# see testcases 9c and 9d for further discussion of this issue and
+# how it's resolved.
+
+test_expect_success '7e-setup: transitive rename in rename/delete AND dirs in the way' '
+ test_create_repo 7e &&
+ (
+ cd 7e &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ mkdir x &&
+ echo d1 >x/d &&
+ git add z x &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git mv z y &&
+ git rm x/d &&
+ mkdir -p x/d &&
+ mkdir -p y/d &&
+ echo f >x/d/f &&
+ echo g >y/d/g &&
+ git add x/d/f y/d/g &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv x/d z/ &&
+ rmdir x &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '7e-check: transitive rename in rename/delete AND dirs in the way' '
+ (
+ cd 7e &&
+
+ git checkout A^0 &&
+
+ test_must_fail git merge -s recursive B^0 >out &&
+ test_i18ngrep "CONFLICT (rename/delete).*x/d.*y/d" out &&
+
+ git ls-files -s >out &&
+ test_line_count = 5 out &&
+ git ls-files -u >out &&
+ test_line_count = 1 out &&
+ git ls-files -o >out &&
+ test_line_count = 2 out &&
+
+ git rev-parse >actual \
+ :0:x/d/f :0:y/d/g :0:y/b :0:y/c :3:y/d &&
+ git rev-parse >expect \
+ A:x/d/f A:y/d/g O:z/b O:z/c O:x/d &&
+ test_cmp expect actual &&
+
+ git hash-object y/d~B^0 >actual &&
+ git rev-parse O:x/d >expect &&
+ test_cmp expect actual
+ )
+'
+
+###########################################################################
+# SECTION 8: Suboptimal merges
+#
+# As alluded to in the last section, the ruleset we have built up for
+# detecting directory renames unfortunately has some special cases where it
+# results in slightly suboptimal or non-intuitive behavior. This section
+# explores these cases.
+#
+# To be fair, we already had non-intuitive or suboptimal behavior for most
+# of these cases in git before introducing implicit directory rename
+# detection, but it'd be nice if there was a modified ruleset out there
+# that handled these cases a bit better.
+###########################################################################
+
+# Testcase 8a, Dual-directory rename, one into the others' way
+# Commit O. x/{a,b}, y/{c,d}
+# Commit A. x/{a,b,e}, y/{c,d,f}
+# Commit B. y/{a,b}, z/{c,d}
+#
+# Possible Resolutions:
+# w/o dir-rename detection: y/{a,b,f}, z/{c,d}, x/e
+# Currently expected: y/{a,b,e,f}, z/{c,d}
+# Optimal: y/{a,b,e}, z/{c,d,f}
+#
+# Note: Both x and y got renamed and it'd be nice to detect both, and we do
+# better with directory rename detection than git did without, but the
+# simple rule from section 5 prevents me from handling this as optimally as
+# we potentially could.
+
+test_expect_success '8a-setup: Dual-directory rename, one into the others way' '
+ test_create_repo 8a &&
+ (
+ cd 8a &&
+
+ mkdir x &&
+ mkdir y &&
+ echo a >x/a &&
+ echo b >x/b &&
+ echo c >y/c &&
+ echo d >y/d &&
+ git add x y &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ echo e >x/e &&
+ echo f >y/f &&
+ git add x/e y/f &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv y z &&
+ git mv x y &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '8a-check: Dual-directory rename, one into the others way' '
+ (
+ cd 8a &&
+
+ git checkout A^0 &&
+
+ git merge -s recursive B^0 &&
+
+ git ls-files -s >out &&
+ test_line_count = 6 out &&
+ git ls-files -u >out &&
+ test_line_count = 0 out &&
+ git ls-files -o >out &&
+ test_line_count = 1 out &&
+
+ git rev-parse >actual \
+ HEAD:y/a HEAD:y/b HEAD:y/e HEAD:y/f HEAD:z/c HEAD:z/d &&
+ git rev-parse >expect \
+ O:x/a O:x/b A:x/e A:y/f O:y/c O:y/d &&
+ test_cmp expect actual
+ )
+'
+
+# Testcase 8b, Dual-directory rename, one into the others' way, with conflicting filenames
+# Commit O. x/{a_1,b_1}, y/{a_2,b_2}
+# Commit A. x/{a_1,b_1,e_1}, y/{a_2,b_2,e_2}
+# Commit B. y/{a_1,b_1}, z/{a_2,b_2}
+#
+# w/o dir-rename detection: y/{a_1,b_1,e_2}, z/{a_2,b_2}, x/e_1
+# Currently expected: <same>
+# Scary: y/{a_1,b_1}, z/{a_2,b_2}, CONFLICT(add/add, e_1 vs. e_2)
+# Optimal: y/{a_1,b_1,e_1}, z/{a_2,b_2,e_2}
+#
+# Note: Very similar to 8a, except instead of 'e' and 'f' in directories x and
+# y, both are named 'e'. Without directory rename detection, neither file
+# moves directories. Implement directory rename detection suboptimally, and
+# you get an add/add conflict, but both files were added in commit A, so this
+# is an add/add conflict where one side of history added both files --
+# something we can't represent in the index. Obviously, we'd prefer the last
+# resolution, but our previous rules are too coarse to allow it. Using both
+# the rules from section 4 and section 5 save us from the Scary resolution,
+# making us fall back to pre-directory-rename-detection behavior for both
+# e_1 and e_2.
+
+test_expect_success '8b-setup: Dual-directory rename, one into the others way, with conflicting filenames' '
+ test_create_repo 8b &&
+ (
+ cd 8b &&
+
+ mkdir x &&
+ mkdir y &&
+ echo a1 >x/a &&
+ echo b1 >x/b &&
+ echo a2 >y/a &&
+ echo b2 >y/b &&
+ git add x y &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ echo e1 >x/e &&
+ echo e2 >y/e &&
+ git add x/e y/e &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv y z &&
+ git mv x y &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '8b-check: Dual-directory rename, one into the others way, with conflicting filenames' '
+ (
+ cd 8b &&
+
+ git checkout A^0 &&
+
+ git merge -s recursive B^0 &&
+
+ git ls-files -s >out &&
+ test_line_count = 6 out &&
+ git ls-files -u >out &&
+ test_line_count = 0 out &&
+ git ls-files -o >out &&
+ test_line_count = 1 out &&
+
+ git rev-parse >actual \
+ HEAD:y/a HEAD:y/b HEAD:z/a HEAD:z/b HEAD:x/e HEAD:y/e &&
+ git rev-parse >expect \
+ O:x/a O:x/b O:y/a O:y/b A:x/e A:y/e &&
+ test_cmp expect actual
+ )
+'
+
+# Testcase 8c, modify/delete or rename+modify/delete?
+# (Related to testcases 5b, 8d, and 9h)
+# Commit O: z/{b,c,d}
+# Commit A: y/{b,c}
+# Commit B: z/{b,c,d_modified,e}
+# Expected: y/{b,c,e}, CONFLICT(modify/delete: on z/d)
+#
+# Note: It could easily be argued that the correct resolution here is
+# y/{b,c,e}, CONFLICT(rename/delete: z/d -> y/d vs deleted)
+# and that the modifed version of d should be present in y/ after
+# the merge, just marked as conflicted. Indeed, I previously did
+# argue that. But applying directory renames to the side of
+# history where a file is merely modified results in spurious
+# rename/rename(1to2) conflicts -- see testcase 9h. See also
+# notes in 8d.
+
+test_expect_success '8c-setup: modify/delete or rename+modify/delete?' '
+ test_create_repo 8c &&
+ (
+ cd 8c &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ test_seq 1 10 >z/d &&
+ git add z &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git rm z/d &&
+ git mv z y &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ echo 11 >z/d &&
+ test_chmod +x z/d &&
+ echo e >z/e &&
+ git add z/d z/e &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '8c-check: modify/delete or rename+modify/delete' '
+ (
+ cd 8c &&
+
+ git checkout A^0 &&
+
+ test_must_fail git merge -s recursive B^0 >out &&
+ test_i18ngrep "CONFLICT (modify/delete).* z/d" out &&
+
+ git ls-files -s >out &&
+ test_line_count = 5 out &&
+ git ls-files -u >out &&
+ test_line_count = 2 out &&
+ git ls-files -o >out &&
+ test_line_count = 1 out &&
+
+ git rev-parse >actual \
+ :0:y/b :0:y/c :0:y/e :1:z/d :3:z/d &&
+ git rev-parse >expect \
+ O:z/b O:z/c B:z/e O:z/d B:z/d &&
+ test_cmp expect actual &&
+
+ test_must_fail git rev-parse :2:z/d &&
+ git ls-files -s z/d | grep ^100755 &&
+ test_path_is_file z/d &&
+ test_path_is_missing y/d
+ )
+'
+
+# Testcase 8d, rename/delete...or not?
+# (Related to testcase 5b; these may appear slightly inconsistent to users;
+# Also related to testcases 7d and 7e)
+# Commit O: z/{b,c,d}
+# Commit A: y/{b,c}
+# Commit B: z/{b,c,d,e}
+# Expected: y/{b,c,e}
+#
+# Note: It would also be somewhat reasonable to resolve this as
+# y/{b,c,e}, CONFLICT(rename/delete: x/d -> y/d or deleted)
+#
+# In this case, I'm leaning towards: commit A was the one that deleted z/d
+# and it did the rename of z to y, so the two "conflicts" (rename vs.
+# delete) are both coming from commit A, which is illogical. Conflicts
+# during merging are supposed to be about opposite sides doing things
+# differently.
+
+test_expect_success '8d-setup: rename/delete...or not?' '
+ test_create_repo 8d &&
+ (
+ cd 8d &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ test_seq 1 10 >z/d &&
+ git add z &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git rm z/d &&
+ git mv z y &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ echo e >z/e &&
+ git add z/e &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '8d-check: rename/delete...or not?' '
+ (
+ cd 8d &&
+
+ git checkout A^0 &&
+
+ git merge -s recursive B^0 &&
+
+ git ls-files -s >out &&
+ test_line_count = 3 out &&
+
+ git rev-parse >actual \
+ HEAD:y/b HEAD:y/c HEAD:y/e &&
+ git rev-parse >expect \
+ O:z/b O:z/c B:z/e &&
+ test_cmp expect actual
+ )
+'
+
+# Testcase 8e, Both sides rename, one side adds to original directory
+# Commit O: z/{b,c}
+# Commit A: y/{b,c}
+# Commit B: w/{b,c}, z/d
+#
+# Possible Resolutions:
+# w/o dir-rename detection: z/d, CONFLICT(z/b -> y/b vs. w/b),
+# CONFLICT(z/c -> y/c vs. w/c)
+# Currently expected: y/d, CONFLICT(z/b -> y/b vs. w/b),
+# CONFLICT(z/c -> y/c vs. w/c)
+# Optimal: ??
+#
+# Notes: In commit A, directory z got renamed to y. In commit B, directory z
+# did NOT get renamed; the directory is still present; instead it is
+# considered to have just renamed a subset of paths in directory z
+# elsewhere. Therefore, the directory rename done in commit A to z/
+# applies to z/d and maps it to y/d.
+#
+# It's possible that users would get confused about this, but what
+# should we do instead? Silently leaving at z/d seems just as bad or
+# maybe even worse. Perhaps we could print a big warning about z/d
+# and how we're moving to y/d in this case, but when I started thinking
+# about the ramifications of doing that, I didn't know how to rule out
+# that opening other weird edge and corner cases so I just punted.
+
+test_expect_success '8e-setup: Both sides rename, one side adds to original directory' '
+ test_create_repo 8e &&
+ (
+ cd 8e &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ git add z &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git mv z y &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv z w &&
+ mkdir z &&
+ echo d >z/d &&
+ git add z/d &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '8e-check: Both sides rename, one side adds to original directory' '
+ (
+ cd 8e &&
+
+ git checkout A^0 &&
+
+ test_must_fail git merge -s recursive B^0 >out 2>err &&
+ test_i18ngrep CONFLICT.*rename/rename.*z/c.*y/c.*w/c out &&
+ test_i18ngrep CONFLICT.*rename/rename.*z/b.*y/b.*w/b out &&
+
+ git ls-files -s >out &&
+ test_line_count = 7 out &&
+ git ls-files -u >out &&
+ test_line_count = 6 out &&
+ git ls-files -o >out &&
+ test_line_count = 2 out &&
+
+ git rev-parse >actual \
+ :1:z/b :2:y/b :3:w/b :1:z/c :2:y/c :3:w/c :0:y/d &&
+ git rev-parse >expect \
+ O:z/b O:z/b O:z/b O:z/c O:z/c O:z/c B:z/d &&
+ test_cmp expect actual &&
+
+ git hash-object >actual \
+ y/b w/b y/c w/c &&
+ git rev-parse >expect \
+ O:z/b O:z/b O:z/c O:z/c &&
+ test_cmp expect actual &&
+
+ test_path_is_missing z/b &&
+ test_path_is_missing z/c
+ )
+'
+
+###########################################################################
+# SECTION 9: Other testcases
+#
+# This section consists of miscellaneous testcases I thought of during
+# the implementation which round out the testing.
+###########################################################################
+
+# Testcase 9a, Inner renamed directory within outer renamed directory
+# (Related to testcase 1f)
+# Commit O: z/{b,c,d/{e,f,g}}
+# Commit A: y/{b,c}, x/w/{e,f,g}
+# Commit B: z/{b,c,d/{e,f,g,h},i}
+# Expected: y/{b,c,i}, x/w/{e,f,g,h}
+# NOTE: The only reason this one is interesting is because when a directory
+# is split into multiple other directories, we determine by the weight
+# of which one had the most paths going to it. A naive implementation
+# of that could take the new file in commit B at z/i to x/w/i or x/i.
+
+test_expect_success '9a-setup: Inner renamed directory within outer renamed directory' '
+ test_create_repo 9a &&
+ (
+ cd 9a &&
+
+ mkdir -p z/d &&
+ echo b >z/b &&
+ echo c >z/c &&
+ echo e >z/d/e &&
+ echo f >z/d/f &&
+ echo g >z/d/g &&
+ git add z &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ mkdir x &&
+ git mv z/d x/w &&
+ git mv z y &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ echo h >z/d/h &&
+ echo i >z/i &&
+ git add z &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '9a-check: Inner renamed directory within outer renamed directory' '
+ (
+ cd 9a &&
+
+ git checkout A^0 &&
+
+ git merge -s recursive B^0 &&
+
+ git ls-files -s >out &&
+ test_line_count = 7 out &&
+ git ls-files -u >out &&
+ test_line_count = 0 out &&
+ git ls-files -o >out &&
+ test_line_count = 1 out &&
+
+ git rev-parse >actual \
+ HEAD:y/b HEAD:y/c HEAD:y/i &&
+ git rev-parse >expect \
+ O:z/b O:z/c B:z/i &&
+ test_cmp expect actual &&
+
+ git rev-parse >actual \
+ HEAD:x/w/e HEAD:x/w/f HEAD:x/w/g HEAD:x/w/h &&
+ git rev-parse >expect \
+ O:z/d/e O:z/d/f O:z/d/g B:z/d/h &&
+ test_cmp expect actual
+ )
+'
+
+# Testcase 9b, Transitive rename with content merge
+# (Related to testcase 1c)
+# Commit O: z/{b,c}, x/d_1
+# Commit A: y/{b,c}, x/d_2
+# Commit B: z/{b,c,d_3}
+# Expected: y/{b,c,d_merged}
+
+test_expect_success '9b-setup: Transitive rename with content merge' '
+ test_create_repo 9b &&
+ (
+ cd 9b &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ mkdir x &&
+ test_seq 1 10 >x/d &&
+ git add z x &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git mv z y &&
+ test_seq 1 11 >x/d &&
+ git add x/d &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ test_seq 0 10 >x/d &&
+ git mv x/d z/d &&
+ git add z/d &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '9b-check: Transitive rename with content merge' '
+ (
+ cd 9b &&
+
+ git checkout A^0 &&
+
+ git merge -s recursive B^0 &&
+
+ git ls-files -s >out &&
+ test_line_count = 3 out &&
+
+ test_seq 0 11 >expected &&
+ test_cmp expected y/d &&
+ git add expected &&
+ git rev-parse >actual \
+ HEAD:y/b HEAD:y/c HEAD:y/d &&
+ git rev-parse >expect \
+ O:z/b O:z/c :0:expected &&
+ test_cmp expect actual &&
+ test_must_fail git rev-parse HEAD:x/d &&
+ test_must_fail git rev-parse HEAD:z/d &&
+ test_path_is_missing z/d &&
+
+ test $(git rev-parse HEAD:y/d) != $(git rev-parse O:x/d) &&
+ test $(git rev-parse HEAD:y/d) != $(git rev-parse A:x/d) &&
+ test $(git rev-parse HEAD:y/d) != $(git rev-parse B:z/d)
+ )
+'
+
+# Testcase 9c, Doubly transitive rename?
+# (Related to testcase 1c, 7e, and 9d)
+# Commit O: z/{b,c}, x/{d,e}, w/f
+# Commit A: y/{b,c}, x/{d,e,f,g}
+# Commit B: z/{b,c,d,e}, w/f
+# Expected: y/{b,c,d,e}, x/{f,g}
+#
+# NOTE: x/f and x/g may be slightly confusing here. The rename from w/f to
+# x/f is clear. Let's look beyond that. Here's the logic:
+# Commit B renamed x/ -> z/
+# Commit A renamed z/ -> y/
+# So, we could possibly further rename x/f to z/f to y/f, a doubly
+# transient rename. However, where does it end? We can chain these
+# indefinitely (see testcase 9d). What if there is a D/F conflict
+# at z/f/ or y/f/? Or just another file conflict at one of those
+# paths? In the case of an N-long chain of transient renamings,
+# where do we "abort" the rename at? Can the user make sense of
+# the resulting conflict and resolve it?
+#
+# To avoid this confusion I use the simple rule that if the other side
+# of history did a directory rename to a path that your side renamed
+# away, then ignore that particular rename from the other side of
+# history for any implicit directory renames.
+
+test_expect_success '9c-setup: Doubly transitive rename?' '
+ test_create_repo 9c &&
+ (
+ cd 9c &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ mkdir x &&
+ echo d >x/d &&
+ echo e >x/e &&
+ mkdir w &&
+ echo f >w/f &&
+ git add z x w &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git mv z y &&
+ git mv w/f x/ &&
+ echo g >x/g &&
+ git add x/g &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv x/d z/d &&
+ git mv x/e z/e &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '9c-check: Doubly transitive rename?' '
+ (
+ cd 9c &&
+
+ git checkout A^0 &&
+
+ git merge -s recursive B^0 >out &&
+ test_i18ngrep "WARNING: Avoiding applying x -> z rename to x/f" out &&
+
+ git ls-files -s >out &&
+ test_line_count = 6 out &&
+ git ls-files -o >out &&
+ test_line_count = 1 out &&
+
+ git rev-parse >actual \
+ HEAD:y/b HEAD:y/c HEAD:y/d HEAD:y/e HEAD:x/f HEAD:x/g &&
+ git rev-parse >expect \
+ O:z/b O:z/c O:x/d O:x/e O:w/f A:x/g &&
+ test_cmp expect actual
+ )
+'
+
+# Testcase 9d, N-fold transitive rename?
+# (Related to testcase 9c...and 1c and 7e)
+# Commit O: z/a, y/b, x/c, w/d, v/e, u/f
+# Commit A: y/{a,b}, w/{c,d}, u/{e,f}
+# Commit B: z/{a,t}, x/{b,c}, v/{d,e}, u/f
+# Expected: <see NOTE first>
+#
+# NOTE: z/ -> y/ (in commit A)
+# y/ -> x/ (in commit B)
+# x/ -> w/ (in commit A)
+# w/ -> v/ (in commit B)
+# v/ -> u/ (in commit A)
+# So, if we add a file to z, say z/t, where should it end up? In u?
+# What if there's another file or directory named 't' in one of the
+# intervening directories and/or in u itself? Also, shouldn't the
+# same logic that places 't' in u/ also move ALL other files to u/?
+# What if there are file or directory conflicts in any of them? If
+# we attempted to do N-way (N-fold? N-ary? N-uple?) transitive renames
+# like this, would the user have any hope of understanding any
+# conflicts or how their working tree ended up? I think not, so I'm
+# ruling out N-ary transitive renames for N>1.
+#
+# Therefore our expected result is:
+# z/t, y/a, x/b, w/c, u/d, u/e, u/f
+# The reason that v/d DOES get transitively renamed to u/d is that u/ isn't
+# renamed somewhere. A slightly sub-optimal result, but it uses fairly
+# simple rules that are consistent with what we need for all the other
+# testcases and simplifies things for the user.
+
+test_expect_success '9d-setup: N-way transitive rename?' '
+ test_create_repo 9d &&
+ (
+ cd 9d &&
+
+ mkdir z y x w v u &&
+ echo a >z/a &&
+ echo b >y/b &&
+ echo c >x/c &&
+ echo d >w/d &&
+ echo e >v/e &&
+ echo f >u/f &&
+ git add z y x w v u &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git mv z/a y/ &&
+ git mv x/c w/ &&
+ git mv v/e u/ &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ echo t >z/t &&
+ git mv y/b x/ &&
+ git mv w/d v/ &&
+ git add z/t &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '9d-check: N-way transitive rename?' '
+ (
+ cd 9d &&
+
+ git checkout A^0 &&
+
+ git merge -s recursive B^0 >out &&
+ test_i18ngrep "WARNING: Avoiding applying z -> y rename to z/t" out &&
+ test_i18ngrep "WARNING: Avoiding applying y -> x rename to y/a" out &&
+ test_i18ngrep "WARNING: Avoiding applying x -> w rename to x/b" out &&
+ test_i18ngrep "WARNING: Avoiding applying w -> v rename to w/c" out &&
+
+ git ls-files -s >out &&
+ test_line_count = 7 out &&
+ git ls-files -o >out &&
+ test_line_count = 1 out &&
+
+ git rev-parse >actual \
+ HEAD:z/t \
+ HEAD:y/a HEAD:x/b HEAD:w/c \
+ HEAD:u/d HEAD:u/e HEAD:u/f &&
+ git rev-parse >expect \
+ B:z/t \
+ O:z/a O:y/b O:x/c \
+ O:w/d O:v/e A:u/f &&
+ test_cmp expect actual
+ )
+'
+
+# Testcase 9e, N-to-1 whammo
+# (Related to testcase 9c...and 1c and 7e)
+# Commit O: dir1/{a,b}, dir2/{d,e}, dir3/{g,h}, dirN/{j,k}
+# Commit A: dir1/{a,b,c,yo}, dir2/{d,e,f,yo}, dir3/{g,h,i,yo}, dirN/{j,k,l,yo}
+# Commit B: combined/{a,b,d,e,g,h,j,k}
+# Expected: combined/{a,b,c,d,e,f,g,h,i,j,k,l}, CONFLICT(Nto1) warnings,
+# dir1/yo, dir2/yo, dir3/yo, dirN/yo
+
+test_expect_success '9e-setup: N-to-1 whammo' '
+ test_create_repo 9e &&
+ (
+ cd 9e &&
+
+ mkdir dir1 dir2 dir3 dirN &&
+ echo a >dir1/a &&
+ echo b >dir1/b &&
+ echo d >dir2/d &&
+ echo e >dir2/e &&
+ echo g >dir3/g &&
+ echo h >dir3/h &&
+ echo j >dirN/j &&
+ echo k >dirN/k &&
+ git add dir* &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ echo c >dir1/c &&
+ echo yo >dir1/yo &&
+ echo f >dir2/f &&
+ echo yo >dir2/yo &&
+ echo i >dir3/i &&
+ echo yo >dir3/yo &&
+ echo l >dirN/l &&
+ echo yo >dirN/yo &&
+ git add dir* &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv dir1 combined &&
+ git mv dir2/* combined/ &&
+ git mv dir3/* combined/ &&
+ git mv dirN/* combined/ &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success C_LOCALE_OUTPUT '9e-check: N-to-1 whammo' '
+ (
+ cd 9e &&
+
+ git checkout A^0 &&
+
+ test_must_fail git merge -s recursive B^0 >out &&
+ grep "CONFLICT (implicit dir rename): Cannot map more than one path to combined/yo" out >error_line &&
+ grep -q dir1/yo error_line &&
+ grep -q dir2/yo error_line &&
+ grep -q dir3/yo error_line &&
+ grep -q dirN/yo error_line &&
+
+ git ls-files -s >out &&
+ test_line_count = 16 out &&
+ git ls-files -u >out &&
+ test_line_count = 0 out &&
+ git ls-files -o >out &&
+ test_line_count = 2 out &&
+
+ git rev-parse >actual \
+ :0:combined/a :0:combined/b :0:combined/c \
+ :0:combined/d :0:combined/e :0:combined/f \
+ :0:combined/g :0:combined/h :0:combined/i \
+ :0:combined/j :0:combined/k :0:combined/l &&
+ git rev-parse >expect \
+ O:dir1/a O:dir1/b A:dir1/c \
+ O:dir2/d O:dir2/e A:dir2/f \
+ O:dir3/g O:dir3/h A:dir3/i \
+ O:dirN/j O:dirN/k A:dirN/l &&
+ test_cmp expect actual &&
+
+ git rev-parse >actual \
+ :0:dir1/yo :0:dir2/yo :0:dir3/yo :0:dirN/yo &&
+ git rev-parse >expect \
+ A:dir1/yo A:dir2/yo A:dir3/yo A:dirN/yo &&
+ test_cmp expect actual
+ )
+'
+
+# Testcase 9f, Renamed directory that only contained immediate subdirs
+# (Related to testcases 1e & 9g)
+# Commit O: goal/{a,b}/$more_files
+# Commit A: priority/{a,b}/$more_files
+# Commit B: goal/{a,b}/$more_files, goal/c
+# Expected: priority/{a,b}/$more_files, priority/c
+
+test_expect_success '9f-setup: Renamed directory that only contained immediate subdirs' '
+ test_create_repo 9f &&
+ (
+ cd 9f &&
+
+ mkdir -p goal/a &&
+ mkdir -p goal/b &&
+ echo foo >goal/a/foo &&
+ echo bar >goal/b/bar &&
+ echo baz >goal/b/baz &&
+ git add goal &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git mv goal/ priority &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ echo c >goal/c &&
+ git add goal/c &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '9f-check: Renamed directory that only contained immediate subdirs' '
+ (
+ cd 9f &&
+
+ git checkout A^0 &&
+
+ git merge -s recursive B^0 &&
+
+ git ls-files -s >out &&
+ test_line_count = 4 out &&
+
+ git rev-parse >actual \
+ HEAD:priority/a/foo \
+ HEAD:priority/b/bar \
+ HEAD:priority/b/baz \
+ HEAD:priority/c &&
+ git rev-parse >expect \
+ O:goal/a/foo \
+ O:goal/b/bar \
+ O:goal/b/baz \
+ B:goal/c &&
+ test_cmp expect actual &&
+ test_must_fail git rev-parse HEAD:goal/c
+ )
+'
+
+# Testcase 9g, Renamed directory that only contained immediate subdirs, immediate subdirs renamed
+# (Related to testcases 1e & 9f)
+# Commit O: goal/{a,b}/$more_files
+# Commit A: priority/{alpha,bravo}/$more_files
+# Commit B: goal/{a,b}/$more_files, goal/c
+# Expected: priority/{alpha,bravo}/$more_files, priority/c
+
+test_expect_success '9g-setup: Renamed directory that only contained immediate subdirs, immediate subdirs renamed' '
+ test_create_repo 9g &&
+ (
+ cd 9g &&
+
+ mkdir -p goal/a &&
+ mkdir -p goal/b &&
+ echo foo >goal/a/foo &&
+ echo bar >goal/b/bar &&
+ echo baz >goal/b/baz &&
+ git add goal &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ mkdir priority &&
+ git mv goal/a/ priority/alpha &&
+ git mv goal/b/ priority/beta &&
+ rmdir goal/ &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ echo c >goal/c &&
+ git add goal/c &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_failure '9g-check: Renamed directory that only contained immediate subdirs, immediate subdirs renamed' '
+ (
+ cd 9g &&
+
+ git checkout A^0 &&
+
+ git merge -s recursive B^0 &&
+
+ git ls-files -s >out &&
+ test_line_count = 4 out &&
+
+ git rev-parse >actual \
+ HEAD:priority/alpha/foo \
+ HEAD:priority/beta/bar \
+ HEAD:priority/beta/baz \
+ HEAD:priority/c &&
+ git rev-parse >expect \
+ O:goal/a/foo \
+ O:goal/b/bar \
+ O:goal/b/baz \
+ B:goal/c &&
+ test_cmp expect actual &&
+ test_must_fail git rev-parse HEAD:goal/c
+ )
+'
+
+# Testcase 9h, Avoid implicit rename if involved as source on other side
+# (Extremely closely related to testcase 3a)
+# Commit O: z/{b,c,d_1}
+# Commit A: z/{b,c,d_2}
+# Commit B: y/{b,c}, x/d_1
+# Expected: y/{b,c}, x/d_2
+# NOTE: If we applied the z/ -> y/ rename to z/d, then we'd end up with
+# a rename/rename(1to2) conflict (z/d -> y/d vs. x/d)
+test_expect_success '9h-setup: Avoid dir rename on merely modified path' '
+ test_create_repo 9h &&
+ (
+ cd 9h &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ printf "1\n2\n3\n4\n5\n6\n7\n8\nd\n" >z/d &&
+ git add z &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ test_tick &&
+ echo more >>z/d &&
+ git add z/d &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ mkdir y &&
+ mkdir x &&
+ git mv z/b y/ &&
+ git mv z/c y/ &&
+ git mv z/d x/ &&
+ rmdir z &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '9h-check: Avoid dir rename on merely modified path' '
+ (
+ cd 9h &&
+
+ git checkout A^0 &&
+
+ git merge -s recursive B^0 &&
+
+ git ls-files -s >out &&
+ test_line_count = 3 out &&
+
+ git rev-parse >actual \
+ HEAD:y/b HEAD:y/c HEAD:x/d &&
+ git rev-parse >expect \
+ O:z/b O:z/c A:z/d &&
+ test_cmp expect actual
+ )
+'
+
+###########################################################################
+# Rules suggested by section 9:
+#
+# If the other side of history did a directory rename to a path that your
+# side renamed away, then ignore that particular rename from the other
+# side of history for any implicit directory renames.
+###########################################################################
+
+###########################################################################
+# SECTION 10: Handling untracked files
+#
+# unpack_trees(), upon which the recursive merge algorithm is based, aborts
+# the operation if untracked or dirty files would be deleted or overwritten
+# by the merge. Unfortunately, unpack_trees() does not understand renames,
+# and if it doesn't abort, then it muddies up the working directory before
+# we even get to the point of detecting renames, so we need some special
+# handling, at least in the case of directory renames.
+###########################################################################
+
+# Testcase 10a, Overwrite untracked: normal rename/delete
+# Commit O: z/{b,c_1}
+# Commit A: z/b + untracked z/c + untracked z/d
+# Commit B: z/{b,d_1}
+# Expected: Aborted Merge +
+# ERROR_MSG(untracked working tree files would be overwritten by merge)
+
+test_expect_success '10a-setup: Overwrite untracked with normal rename/delete' '
+ test_create_repo 10a &&
+ (
+ cd 10a &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ git add z &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git rm z/c &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv z/c z/d &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '10a-check: Overwrite untracked with normal rename/delete' '
+ (
+ cd 10a &&
+
+ git checkout A^0 &&
+ echo very >z/c &&
+ echo important >z/d &&
+
+ test_must_fail git merge -s recursive B^0 >out 2>err &&
+ test_i18ngrep "The following untracked working tree files would be overwritten by merge" err &&
+
+ git ls-files -s >out &&
+ test_line_count = 1 out &&
+ git ls-files -o >out &&
+ test_line_count = 4 out &&
+
+ echo very >expect &&
+ test_cmp expect z/c &&
+
+ echo important >expect &&
+ test_cmp expect z/d &&
+
+ git rev-parse HEAD:z/b >actual &&
+ git rev-parse O:z/b >expect &&
+ test_cmp expect actual
+ )
+'
+
+# Testcase 10b, Overwrite untracked: dir rename + delete
+# Commit O: z/{b,c_1}
+# Commit A: y/b + untracked y/{c,d,e}
+# Commit B: z/{b,d_1,e}
+# Expected: Failed Merge; y/b + untracked y/c + untracked y/d on disk +
+# z/c_1 -> z/d_1 rename recorded at stage 3 for y/d +
+# ERROR_MSG(refusing to lose untracked file at 'y/d')
+
+test_expect_success '10b-setup: Overwrite untracked with dir rename + delete' '
+ test_create_repo 10b &&
+ (
+ cd 10b &&
+
+ mkdir z &&
+ echo b >z/b &&
+ echo c >z/c &&
+ git add z &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git rm z/c &&
+ git mv z/ y/ &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv z/c z/d &&
+ echo e >z/e &&
+ git add z/e &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '10b-check: Overwrite untracked with dir rename + delete' '
+ (
+ cd 10b &&
+
+ git checkout A^0 &&
+ echo very >y/c &&
+ echo important >y/d &&
+ echo contents >y/e &&
+
+ test_must_fail git merge -s recursive B^0 >out 2>err &&
+ test_i18ngrep "CONFLICT (rename/delete).*Version B\^0 of y/d left in tree at y/d~B\^0" out &&
+ test_i18ngrep "Error: Refusing to lose untracked file at y/e; writing to y/e~B\^0 instead" out &&
+
+ git ls-files -s >out &&
+ test_line_count = 3 out &&
+ git ls-files -u >out &&
+ test_line_count = 2 out &&
+ git ls-files -o >out &&
+ test_line_count = 5 out &&
+
+ git rev-parse >actual \
+ :0:y/b :3:y/d :3:y/e &&
+ git rev-parse >expect \
+ O:z/b O:z/c B:z/e &&
+ test_cmp expect actual &&
+
+ echo very >expect &&
+ test_cmp expect y/c &&
+
+ echo important >expect &&
+ test_cmp expect y/d &&
+
+ echo contents >expect &&
+ test_cmp expect y/e
+ )
+'
+
+# Testcase 10c, Overwrite untracked: dir rename/rename(1to2)
+# Commit O: z/{a,b}, x/{c,d}
+# Commit A: y/{a,b}, w/c, x/d + different untracked y/c
+# Commit B: z/{a,b,c}, x/d
+# Expected: Failed Merge; y/{a,b} + x/d + untracked y/c +
+# CONFLICT(rename/rename) x/c -> w/c vs y/c +
+# y/c~B^0 +
+# ERROR_MSG(Refusing to lose untracked file at y/c)
+
+test_expect_success '10c-setup: Overwrite untracked with dir rename/rename(1to2)' '
+ test_create_repo 10c &&
+ (
+ cd 10c &&
+
+ mkdir z x &&
+ echo a >z/a &&
+ echo b >z/b &&
+ echo c >x/c &&
+ echo d >x/d &&
+ git add z x &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ mkdir w &&
+ git mv x/c w/c &&
+ git mv z/ y/ &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv x/c z/ &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2)' '
+ (
+ cd 10c &&
+
+ git checkout A^0 &&
+ echo important >y/c &&
+
+ test_must_fail git merge -s recursive B^0 >out 2>err &&
+ test_i18ngrep "CONFLICT (rename/rename)" out &&
+ test_i18ngrep "Refusing to lose untracked file at y/c; adding as y/c~B\^0 instead" out &&
+
+ git ls-files -s >out &&
+ test_line_count = 6 out &&
+ git ls-files -u >out &&
+ test_line_count = 3 out &&
+ git ls-files -o >out &&
+ test_line_count = 3 out &&
+
+ git rev-parse >actual \
+ :0:y/a :0:y/b :0:x/d :1:x/c :2:w/c :3:y/c &&
+ git rev-parse >expect \
+ O:z/a O:z/b O:x/d O:x/c O:x/c O:x/c &&
+ test_cmp expect actual &&
+
+ git hash-object y/c~B^0 >actual &&
+ git rev-parse O:x/c >expect &&
+ test_cmp expect actual &&
+
+ echo important >expect &&
+ test_cmp expect y/c
+ )
+'
+
+# Testcase 10d, Delete untracked w/ dir rename/rename(2to1)
+# Commit O: z/{a,b,c_1}, x/{d,e,f_2}
+# Commit A: y/{a,b}, x/{d,e,f_2,wham_1} + untracked y/wham
+# Commit B: z/{a,b,c_1,wham_2}, y/{d,e}
+# Expected: Failed Merge; y/{a,b,d,e} + untracked y/{wham,wham~B^0,wham~HEAD}+
+# CONFLICT(rename/rename) z/c_1 vs x/f_2 -> y/wham
+# ERROR_MSG(Refusing to lose untracked file at y/wham)
+
+test_expect_success '10d-setup: Delete untracked with dir rename/rename(2to1)' '
+ test_create_repo 10d &&
+ (
+ cd 10d &&
+
+ mkdir z x &&
+ echo a >z/a &&
+ echo b >z/b &&
+ echo c >z/c &&
+ echo d >x/d &&
+ echo e >x/e &&
+ echo f >x/f &&
+ git add z x &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git mv z/c x/wham &&
+ git mv z/ y/ &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv x/f z/wham &&
+ git mv x/ y/ &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '10d-check: Delete untracked with dir rename/rename(2to1)' '
+ (
+ cd 10d &&
+
+ git checkout A^0 &&
+ echo important >y/wham &&
+
+ test_must_fail git merge -s recursive B^0 >out 2>err &&
+ test_i18ngrep "CONFLICT (rename/rename)" out &&
+ test_i18ngrep "Refusing to lose untracked file at y/wham" out &&
+
+ git ls-files -s >out &&
+ test_line_count = 6 out &&
+ git ls-files -u >out &&
+ test_line_count = 2 out &&
+ git ls-files -o >out &&
+ test_line_count = 4 out &&
+
+ git rev-parse >actual \
+ :0:y/a :0:y/b :0:y/d :0:y/e :2:y/wham :3:y/wham &&
+ git rev-parse >expect \
+ O:z/a O:z/b O:x/d O:x/e O:z/c O:x/f &&
+ test_cmp expect actual &&
+
+ test_must_fail git rev-parse :1:y/wham &&
+
+ echo important >expect &&
+ test_cmp expect y/wham &&
+
+ git hash-object >actual \
+ y/wham~B^0 y/wham~HEAD &&
+ git rev-parse >expect \
+ O:x/f O:z/c &&
+ test_cmp expect actual
+ )
+'
+
+# Testcase 10e, Does git complain about untracked file that's not in the way?
+# Commit O: z/{a,b}
+# Commit A: y/{a,b} + untracked z/c
+# Commit B: z/{a,b,c}
+# Expected: y/{a,b,c} + untracked z/c
+
+test_expect_success '10e-setup: Does git complain about untracked file that is not really in the way?' '
+ test_create_repo 10e &&
+ (
+ cd 10e &&
+
+ mkdir z &&
+ echo a >z/a &&
+ echo b >z/b &&
+ git add z &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git mv z/ y/ &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ echo c >z/c &&
+ git add z/c &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_failure '10e-check: Does git complain about untracked file that is not really in the way?' '
+ (
+ cd 10e &&
+
+ git checkout A^0 &&
+ mkdir z &&
+ echo random >z/c &&
+
+ git merge -s recursive B^0 >out 2>err &&
+ test_i18ngrep ! "following untracked working tree files would be overwritten by merge" err &&
+
+ git ls-files -s >out &&
+ test_line_count = 3 out &&
+ git ls-files -u >out &&
+ test_line_count = 0 out &&
+ git ls-files -o >out &&
+ test_line_count = 3 out &&
+
+ git rev-parse >actual \
+ :0:y/a :0:y/b :0:y/c &&
+ git rev-parse >expect \
+ O:z/a O:z/b B:z/c &&
+ test_cmp expect actual &&
+
+ echo random >expect &&
+ test_cmp expect z/c
+ )
+'
+
+###########################################################################
+# SECTION 11: Handling dirty (not up-to-date) files
+#
+# unpack_trees(), upon which the recursive merge algorithm is based, aborts
+# the operation if untracked or dirty files would be deleted or overwritten
+# by the merge. Unfortunately, unpack_trees() does not understand renames,
+# and if it doesn't abort, then it muddies up the working directory before
+# we even get to the point of detecting renames, so we need some special
+# handling. This was true even of normal renames, but there are additional
+# codepaths that need special handling with directory renames. Add
+# testcases for both renamed-by-directory-rename-detection and standard
+# rename cases.
+###########################################################################
+
+# Testcase 11a, Avoid losing dirty contents with simple rename
+# Commit O: z/{a,b_v1},
+# Commit A: z/{a,c_v1}, and z/c_v1 has uncommitted mods
+# Commit B: z/{a,b_v2}
+# Expected: ERROR_MSG(Refusing to lose dirty file at z/c) +
+# z/a, staged version of z/c has sha1sum matching B:z/b_v2,
+# z/c~HEAD with contents of B:z/b_v2,
+# z/c with uncommitted mods on top of A:z/c_v1
+
+test_expect_success '11a-setup: Avoid losing dirty contents with simple rename' '
+ test_create_repo 11a &&
+ (
+ cd 11a &&
+
+ mkdir z &&
+ echo a >z/a &&
+ test_seq 1 10 >z/b &&
+ git add z &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git mv z/b z/c &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ echo 11 >>z/b &&
+ git add z/b &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '11a-check: Avoid losing dirty contents with simple rename' '
+ (
+ cd 11a &&
+
+ git checkout A^0 &&
+ echo stuff >>z/c &&
+
+ test_must_fail git merge -s recursive B^0 >out 2>err &&
+ test_i18ngrep "Refusing to lose dirty file at z/c" out &&
+
+ test_seq 1 10 >expected &&
+ echo stuff >>expected &&
+ test_cmp expected z/c &&
+
+ git ls-files -s >out &&
+ test_line_count = 2 out &&
+ git ls-files -u >out &&
+ test_line_count = 1 out &&
+ git ls-files -o >out &&
+ test_line_count = 4 out &&
+
+ git rev-parse >actual \
+ :0:z/a :2:z/c &&
+ git rev-parse >expect \
+ O:z/a B:z/b &&
+ test_cmp expect actual &&
+
+ git hash-object z/c~HEAD >actual &&
+ git rev-parse B:z/b >expect &&
+ test_cmp expect actual
+ )
+'
+
+# Testcase 11b, Avoid losing dirty file involved in directory rename
+# Commit O: z/a, x/{b,c_v1}
+# Commit A: z/{a,c_v1}, x/b, and z/c_v1 has uncommitted mods
+# Commit B: y/a, x/{b,c_v2}
+# Expected: y/{a,c_v2}, x/b, z/c_v1 with uncommitted mods untracked,
+# ERROR_MSG(Refusing to lose dirty file at z/c)
+
+
+test_expect_success '11b-setup: Avoid losing dirty file involved in directory rename' '
+ test_create_repo 11b &&
+ (
+ cd 11b &&
+
+ mkdir z x &&
+ echo a >z/a &&
+ echo b >x/b &&
+ test_seq 1 10 >x/c &&
+ git add z x &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git mv x/c z/c &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv z y &&
+ echo 11 >>x/c &&
+ git add x/c &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '11b-check: Avoid losing dirty file involved in directory rename' '
+ (
+ cd 11b &&
+
+ git checkout A^0 &&
+ echo stuff >>z/c &&
+
+ git merge -s recursive B^0 >out 2>err &&
+ test_i18ngrep "Refusing to lose dirty file at z/c" out &&
+
+ grep -q stuff z/c &&
+ test_seq 1 10 >expected &&
+ echo stuff >>expected &&
+ test_cmp expected z/c &&
+
+ git ls-files -s >out &&
+ test_line_count = 3 out &&
+ git ls-files -u >out &&
+ test_line_count = 0 out &&
+ git ls-files -m >out &&
+ test_line_count = 0 out &&
+ git ls-files -o >out &&
+ test_line_count = 4 out &&
+
+ git rev-parse >actual \
+ :0:x/b :0:y/a :0:y/c &&
+ git rev-parse >expect \
+ O:x/b O:z/a B:x/c &&
+ test_cmp expect actual &&
+
+ git hash-object y/c >actual &&
+ git rev-parse B:x/c >expect &&
+ test_cmp expect actual
+ )
+'
+
+# Testcase 11c, Avoid losing not-up-to-date with rename + D/F conflict
+# Commit O: y/a, x/{b,c_v1}
+# Commit A: y/{a,c_v1}, x/b, and y/c_v1 has uncommitted mods
+# Commit B: y/{a,c/d}, x/{b,c_v2}
+# Expected: Abort_msg("following files would be overwritten by merge") +
+# y/c left untouched (still has uncommitted mods)
+
+test_expect_success '11c-setup: Avoid losing not-uptodate with rename + D/F conflict' '
+ test_create_repo 11c &&
+ (
+ cd 11c &&
+
+ mkdir y x &&
+ echo a >y/a &&
+ echo b >x/b &&
+ test_seq 1 10 >x/c &&
+ git add y x &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git mv x/c y/c &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ mkdir y/c &&
+ echo d >y/c/d &&
+ echo 11 >>x/c &&
+ git add x/c y/c/d &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '11c-check: Avoid losing not-uptodate with rename + D/F conflict' '
+ (
+ cd 11c &&
+
+ git checkout A^0 &&
+ echo stuff >>y/c &&
+
+ test_must_fail git merge -s recursive B^0 >out 2>err &&
+ test_i18ngrep "following files would be overwritten by merge" err &&
+
+ grep -q stuff y/c &&
+ test_seq 1 10 >expected &&
+ echo stuff >>expected &&
+ test_cmp expected y/c &&
+
+ git ls-files -s >out &&
+ test_line_count = 3 out &&
+ git ls-files -u >out &&
+ test_line_count = 0 out &&
+ git ls-files -m >out &&
+ test_line_count = 1 out &&
+ git ls-files -o >out &&
+ test_line_count = 3 out
+ )
+'
+
+# Testcase 11d, Avoid losing not-up-to-date with rename + D/F conflict
+# Commit O: z/a, x/{b,c_v1}
+# Commit A: z/{a,c_v1}, x/b, and z/c_v1 has uncommitted mods
+# Commit B: y/{a,c/d}, x/{b,c_v2}
+# Expected: D/F: y/c_v2 vs y/c/d) +
+# Warning_Msg("Refusing to lose dirty file at z/c) +
+# y/{a,c~HEAD,c/d}, x/b, now-untracked z/c_v1 with uncommitted mods
+
+test_expect_success '11d-setup: Avoid losing not-uptodate with rename + D/F conflict' '
+ test_create_repo 11d &&
+ (
+ cd 11d &&
+
+ mkdir z x &&
+ echo a >z/a &&
+ echo b >x/b &&
+ test_seq 1 10 >x/c &&
+ git add z x &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git mv x/c z/c &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv z y &&
+ mkdir y/c &&
+ echo d >y/c/d &&
+ echo 11 >>x/c &&
+ git add x/c y/c/d &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '11d-check: Avoid losing not-uptodate with rename + D/F conflict' '
+ (
+ cd 11d &&
+
+ git checkout A^0 &&
+ echo stuff >>z/c &&
+
+ test_must_fail git merge -s recursive B^0 >out 2>err &&
+ test_i18ngrep "Refusing to lose dirty file at z/c" out &&
+
+ grep -q stuff z/c &&
+ test_seq 1 10 >expected &&
+ echo stuff >>expected &&
+ test_cmp expected z/c
+
+ git ls-files -s >out &&
+ test_line_count = 4 out &&
+ git ls-files -u >out &&
+ test_line_count = 1 out &&
+ git ls-files -o >out &&
+ test_line_count = 5 out &&
+
+ git rev-parse >actual \
+ :0:x/b :0:y/a :0:y/c/d :3:y/c &&
+ git rev-parse >expect \
+ O:x/b O:z/a B:y/c/d B:x/c &&
+ test_cmp expect actual &&
+
+ git hash-object y/c~HEAD >actual &&
+ git rev-parse B:x/c >expect &&
+ test_cmp expect actual
+ )
+'
+
+# Testcase 11e, Avoid deleting not-up-to-date with dir rename/rename(1to2)/add
+# Commit O: z/{a,b}, x/{c_1,d}
+# Commit A: y/{a,b,c_2}, x/d, w/c_1, and y/c_2 has uncommitted mods
+# Commit B: z/{a,b,c_1}, x/d
+# Expected: Failed Merge; y/{a,b} + x/d +
+# CONFLICT(rename/rename) x/c_1 -> w/c_1 vs y/c_1 +
+# ERROR_MSG(Refusing to lose dirty file at y/c)
+# y/c~B^0 has O:x/c_1 contents
+# y/c~HEAD has A:y/c_2 contents
+# y/c has dirty file from before merge
+
+test_expect_success '11e-setup: Avoid deleting not-uptodate with dir rename/rename(1to2)/add' '
+ test_create_repo 11e &&
+ (
+ cd 11e &&
+
+ mkdir z x &&
+ echo a >z/a &&
+ echo b >z/b &&
+ echo c >x/c &&
+ echo d >x/d &&
+ git add z x &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git mv z/ y/ &&
+ echo different >y/c &&
+ mkdir w &&
+ git mv x/c w/ &&
+ git add y/c &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv x/c z/ &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rename(1to2)/add' '
+ (
+ cd 11e &&
+
+ git checkout A^0 &&
+ echo mods >>y/c &&
+
+ test_must_fail git merge -s recursive B^0 >out 2>err &&
+ test_i18ngrep "CONFLICT (rename/rename)" out &&
+ test_i18ngrep "Refusing to lose dirty file at y/c" out &&
+
+ git ls-files -s >out &&
+ test_line_count = 7 out &&
+ git ls-files -u >out &&
+ test_line_count = 4 out &&
+ git ls-files -o >out &&
+ test_line_count = 4 out &&
+
+ echo different >expected &&
+ echo mods >>expected &&
+ test_cmp expected y/c &&
+
+ git rev-parse >actual \
+ :0:y/a :0:y/b :0:x/d :1:x/c :2:w/c :2:y/c :3:y/c &&
+ git rev-parse >expect \
+ O:z/a O:z/b O:x/d O:x/c O:x/c A:y/c O:x/c &&
+ test_cmp expect actual &&
+
+ git hash-object >actual \
+ y/c~B^0 y/c~HEAD &&
+ git rev-parse >expect \
+ O:x/c A:y/c &&
+ test_cmp expect actual
+ )
+'
+
+# Testcase 11f, Avoid deleting not-up-to-date w/ dir rename/rename(2to1)
+# Commit O: z/{a,b}, x/{c_1,d_2}
+# Commit A: y/{a,b,wham_1}, x/d_2, except y/wham has uncommitted mods
+# Commit B: z/{a,b,wham_2}, x/c_1
+# Expected: Failed Merge; y/{a,b} + untracked y/{wham~B^0,wham~B^HEAD} +
+# y/wham with dirty changes from before merge +
+# CONFLICT(rename/rename) x/c vs x/d -> y/wham
+# ERROR_MSG(Refusing to lose dirty file at y/wham)
+
+test_expect_success '11f-setup: Avoid deleting not-uptodate with dir rename/rename(2to1)' '
+ test_create_repo 11f &&
+ (
+ cd 11f &&
+
+ mkdir z x &&
+ echo a >z/a &&
+ echo b >z/b &&
+ test_seq 1 10 >x/c &&
+ echo d >x/d &&
+ git add z x &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git mv z/ y/ &&
+ git mv x/c y/wham &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv x/d z/wham &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '11f-check: Avoid deleting not-uptodate with dir rename/rename(2to1)' '
+ (
+ cd 11f &&
+
+ git checkout A^0 &&
+ echo important >>y/wham &&
+
+ test_must_fail git merge -s recursive B^0 >out 2>err &&
+ test_i18ngrep "CONFLICT (rename/rename)" out &&
+ test_i18ngrep "Refusing to lose dirty file at y/wham" out &&
+
+ git ls-files -s >out &&
+ test_line_count = 4 out &&
+ git ls-files -u >out &&
+ test_line_count = 2 out &&
+ git ls-files -o >out &&
+ test_line_count = 4 out &&
+
+ test_seq 1 10 >expected &&
+ echo important >>expected &&
+ test_cmp expected y/wham &&
+
+ test_must_fail git rev-parse :1:y/wham &&
+ git hash-object >actual \
+ y/wham~B^0 y/wham~HEAD &&
+ git rev-parse >expect \
+ O:x/d O:x/c &&
+ test_cmp expect actual &&
+
+ git rev-parse >actual \
+ :0:y/a :0:y/b :2:y/wham :3:y/wham &&
+ git rev-parse >expect \
+ O:z/a O:z/b O:x/c O:x/d &&
+ test_cmp expect actual
+ )
+'
+
+###########################################################################
+# SECTION 12: Everything else
+#
+# Tests suggested by others. Tests added after implementation completed
+# and submitted. Grab bag.
+###########################################################################
+
+# Testcase 12a, Moving one directory hierarchy into another
+# (Related to testcase 9a)
+# Commit O: node1/{leaf1,leaf2}, node2/{leaf3,leaf4}
+# Commit A: node1/{leaf1,leaf2,node2/{leaf3,leaf4}}
+# Commit B: node1/{leaf1,leaf2,leaf5}, node2/{leaf3,leaf4,leaf6}
+# Expected: node1/{leaf1,leaf2,leaf5,node2/{leaf3,leaf4,leaf6}}
+
+test_expect_success '12a-setup: Moving one directory hierarchy into another' '
+ test_create_repo 12a &&
+ (
+ cd 12a &&
+
+ mkdir -p node1 node2 &&
+ echo leaf1 >node1/leaf1 &&
+ echo leaf2 >node1/leaf2 &&
+ echo leaf3 >node2/leaf3 &&
+ echo leaf4 >node2/leaf4 &&
+ git add node1 node2 &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git mv node2/ node1/ &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ echo leaf5 >node1/leaf5 &&
+ echo leaf6 >node2/leaf6 &&
+ git add node1 node2 &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '12a-check: Moving one directory hierarchy into another' '
+ (
+ cd 12a &&
+
+ git checkout A^0 &&
+
+ git merge -s recursive B^0 &&
+
+ git ls-files -s >out &&
+ test_line_count = 6 out &&
+
+ git rev-parse >actual \
+ HEAD:node1/leaf1 HEAD:node1/leaf2 HEAD:node1/leaf5 \
+ HEAD:node1/node2/leaf3 \
+ HEAD:node1/node2/leaf4 \
+ HEAD:node1/node2/leaf6 &&
+ git rev-parse >expect \
+ O:node1/leaf1 O:node1/leaf2 B:node1/leaf5 \
+ O:node2/leaf3 \
+ O:node2/leaf4 \
+ B:node2/leaf6 &&
+ test_cmp expect actual
+ )
+'
+
+# Testcase 12b, Moving two directory hierarchies into each other
+# (Related to testcases 1c and 12c)
+# Commit O: node1/{leaf1, leaf2}, node2/{leaf3, leaf4}
+# Commit A: node1/{leaf1, leaf2, node2/{leaf3, leaf4}}
+# Commit B: node2/{leaf3, leaf4, node1/{leaf1, leaf2}}
+# Expected: node1/node2/node1/{leaf1, leaf2},
+# node2/node1/node2/{leaf3, leaf4}
+# NOTE: Without directory renames, we would expect
+# node2/node1/{leaf1, leaf2},
+# node1/node2/{leaf3, leaf4}
+# with directory rename detection, we note that
+# commit A renames node2/ -> node1/node2/
+# commit B renames node1/ -> node2/node1/
+# therefore, applying those directory renames to the initial result
+# (making all four paths experience a transitive renaming), yields
+# the expected result.
+#
+# You may ask, is it weird to have two directories rename each other?
+# To which, I can do no more than shrug my shoulders and say that
+# even simple rules give weird results when given weird inputs.
+
+test_expect_success '12b-setup: Moving one directory hierarchy into another' '
+ test_create_repo 12b &&
+ (
+ cd 12b &&
+
+ mkdir -p node1 node2 &&
+ echo leaf1 >node1/leaf1 &&
+ echo leaf2 >node1/leaf2 &&
+ echo leaf3 >node2/leaf3 &&
+ echo leaf4 >node2/leaf4 &&
+ git add node1 node2 &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git mv node2/ node1/ &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv node1/ node2/ &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '12b-check: Moving one directory hierarchy into another' '
+ (
+ cd 12b &&
+
+ git checkout A^0 &&
+
+ git merge -s recursive B^0 &&
+
+ git ls-files -s >out &&
+ test_line_count = 4 out &&
+
+ git rev-parse >actual \
+ HEAD:node1/node2/node1/leaf1 \
+ HEAD:node1/node2/node1/leaf2 \
+ HEAD:node2/node1/node2/leaf3 \
+ HEAD:node2/node1/node2/leaf4 &&
+ git rev-parse >expect \
+ O:node1/leaf1 \
+ O:node1/leaf2 \
+ O:node2/leaf3 \
+ O:node2/leaf4 &&
+ test_cmp expect actual
+ )
+'
+
+# Testcase 12c, Moving two directory hierarchies into each other w/ content merge
+# (Related to testcase 12b)
+# Commit O: node1/{ leaf1_1, leaf2_1}, node2/{leaf3_1, leaf4_1}
+# Commit A: node1/{ leaf1_2, leaf2_2, node2/{leaf3_2, leaf4_2}}
+# Commit B: node2/{node1/{leaf1_3, leaf2_3}, leaf3_3, leaf4_3}
+# Expected: Content merge conflicts for each of:
+# node1/node2/node1/{leaf1, leaf2},
+# node2/node1/node2/{leaf3, leaf4}
+# NOTE: This is *exactly* like 12c, except that every path is modified on
+# each side of the merge.
+
+test_expect_success '12c-setup: Moving one directory hierarchy into another w/ content merge' '
+ test_create_repo 12c &&
+ (
+ cd 12c &&
+
+ mkdir -p node1 node2 &&
+ printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf1\n" >node1/leaf1 &&
+ printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf2\n" >node1/leaf2 &&
+ printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf3\n" >node2/leaf3 &&
+ printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf4\n" >node2/leaf4 &&
+ git add node1 node2 &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git mv node2/ node1/ &&
+ for i in `git ls-files`; do echo side A >>$i; done &&
+ git add -u &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv node1/ node2/ &&
+ for i in `git ls-files`; do echo side B >>$i; done &&
+ git add -u &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '12c-check: Moving one directory hierarchy into another w/ content merge' '
+ (
+ cd 12c &&
+
+ git checkout A^0 &&
+
+ test_must_fail git merge -s recursive B^0 &&
+
+ git ls-files -u >out &&
+ test_line_count = 12 out &&
+
+ git rev-parse >actual \
+ :1:node1/node2/node1/leaf1 \
+ :1:node1/node2/node1/leaf2 \
+ :1:node2/node1/node2/leaf3 \
+ :1:node2/node1/node2/leaf4 \
+ :2:node1/node2/node1/leaf1 \
+ :2:node1/node2/node1/leaf2 \
+ :2:node2/node1/node2/leaf3 \
+ :2:node2/node1/node2/leaf4 \
+ :3:node1/node2/node1/leaf1 \
+ :3:node1/node2/node1/leaf2 \
+ :3:node2/node1/node2/leaf3 \
+ :3:node2/node1/node2/leaf4 &&
+ git rev-parse >expect \
+ O:node1/leaf1 \
+ O:node1/leaf2 \
+ O:node2/leaf3 \
+ O:node2/leaf4 \
+ A:node1/leaf1 \
+ A:node1/leaf2 \
+ A:node1/node2/leaf3 \
+ A:node1/node2/leaf4 \
+ B:node2/node1/leaf1 \
+ B:node2/node1/leaf2 \
+ B:node2/leaf3 \
+ B:node2/leaf4 &&
+ test_cmp expect actual
+ )
+'
+
+test_done
diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh
index a5d9015024..bae78c4e89 100755
--- a/t/t6120-describe.sh
+++ b/t/t6120-describe.sh
@@ -378,4 +378,12 @@ check_describe tags/A --all A
check_describe tags/c --all c
check_describe heads/branch_A --all --match='branch_*' branch_A
+test_expect_success 'describe complains about tree object' '
+ test_must_fail git describe HEAD^{tree}
+'
+
+test_expect_success 'describe complains about missing object' '
+ test_must_fail git describe $_z40
+'
+
test_done
diff --git a/t/t6200-fmt-merge-msg.sh b/t/t6200-fmt-merge-msg.sh
index 2e2fb0e957..a54a52aaa4 100755
--- a/t/t6200-fmt-merge-msg.sh
+++ b/t/t6200-fmt-merge-msg.sh
@@ -512,7 +512,7 @@ test_expect_success 'merge-msg with "merging" an annotated tag' '
test_when_finished "git reset --hard" &&
annote=$(git rev-parse annote) &&
- git merge --no-commit $annote &&
+ git merge --no-commit --no-ff $annote &&
{
cat <<-EOF
Merge tag '\''$annote'\''
diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
index c128dfc579..295d1475bd 100755
--- a/t/t6300-for-each-ref.sh
+++ b/t/t6300-for-each-ref.sh
@@ -373,11 +373,8 @@ test_expect_success 'Quoting style: tcl' '
for i in "--perl --shell" "-s --python" "--python --tcl" "--tcl --perl"; do
test_expect_success "more than one quoting style: $i" "
- git for-each-ref $i 2>&1 | (read line &&
- case \$line in
- \"error: more than one quoting style\"*) : happy;;
- *) false
- esac)
+ test_must_fail git for-each-ref $i 2>err &&
+ grep '^error: more than one quoting style' err
"
done
diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh
index 6e5031f56f..d4e6485a26 100755
--- a/t/t7001-mv.sh
+++ b/t/t7001-mv.sh
@@ -39,6 +39,12 @@ test_expect_success \
grep "^R100..*path1/COPYING..*path0/COPYING"'
test_expect_success \
+ 'mv --dry-run does not move file' \
+ 'git mv -n path0/COPYING MOVED &&
+ test -f path0/COPYING &&
+ test ! -f MOVED'
+
+test_expect_success \
'checking -k on non-existing file' \
'git mv -k idontexist path0'
diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh
index 7cb60799be..04f79f32b4 100755
--- a/t/t7003-filter-branch.sh
+++ b/t/t7003-filter-branch.sh
@@ -470,4 +470,18 @@ test_expect_success 'tree-filter deals with object name vs pathname ambiguity' '
git show HEAD:$ambiguous
'
+test_expect_success 'rewrite repository including refs that point at non-commit object' '
+ test_when_finished "git reset --hard original" &&
+ tree=$(git rev-parse HEAD^{tree}) &&
+ test_when_finished "git replace -d $tree" &&
+ echo A >new &&
+ git add new &&
+ new_tree=$(git write-tree) &&
+ git replace $tree $new_tree &&
+ git tag -a -m "tag to a tree" treetag $new_tree &&
+ git reset --hard HEAD &&
+ git filter-branch -f -- --all >filter-output 2>&1 &&
+ ! fgrep fatal filter-output
+'
+
test_done
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index a9af2de996..2aac77af70 100755
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -452,6 +452,21 @@ test_expect_success \
test_cmp expect actual
'
+get_tag_header annotated-tag-edit $commit commit $time >expect
+echo "An edited message" >>expect
+test_expect_success 'set up editor' '
+ write_script fakeeditor <<-\EOF
+ sed -e "s/A message/An edited message/g" <"$1" >"$1-"
+ mv "$1-" "$1"
+ EOF
+'
+test_expect_success \
+ 'creating an annotated tag with -m message --edit should succeed' '
+ GIT_EDITOR=./fakeeditor git tag -m "A message" --edit annotated-tag-edit &&
+ get_tag_msg annotated-tag-edit >actual &&
+ test_cmp expect actual
+'
+
cat >msgfile <<EOF
Another message
in a file.
@@ -465,6 +480,21 @@ test_expect_success \
test_cmp expect actual
'
+get_tag_header file-annotated-tag-edit $commit commit $time >expect
+sed -e "s/Another message/Another edited message/g" msgfile >>expect
+test_expect_success 'set up editor' '
+ write_script fakeeditor <<-\EOF
+ sed -e "s/Another message/Another edited message/g" <"$1" >"$1-"
+ mv "$1-" "$1"
+ EOF
+'
+test_expect_success \
+ 'creating an annotated tag with -F messagefile --edit should succeed' '
+ GIT_EDITOR=./fakeeditor git tag -F msgfile --edit file-annotated-tag-edit &&
+ get_tag_msg file-annotated-tag-edit >actual &&
+ test_cmp expect actual
+'
+
cat >inputmsg <<EOF
A message from the
standard input
diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh
index f5f46a95b4..7541ba5edb 100755
--- a/t/t7006-pager.sh
+++ b/t/t7006-pager.sh
@@ -110,13 +110,6 @@ test_expect_success TTY 'configuration can disable pager' '
! test -e paginated.out
'
-test_expect_success TTY 'git config uses a pager if configured to' '
- rm -f paginated.out &&
- test_config pager.config true &&
- test_terminal git config --list &&
- test -e paginated.out
-'
-
test_expect_success TTY 'configuration can enable pager (from subdir)' '
rm -f paginated.out &&
mkdir -p subdir &&
@@ -252,6 +245,48 @@ test_expect_success TTY 'git branch --set-upstream-to ignores pager.branch' '
! test -e paginated.out
'
+test_expect_success TTY 'git config ignores pager.config when setting' '
+ rm -f paginated.out &&
+ test_terminal git -c pager.config config foo.bar bar &&
+ ! test -e paginated.out
+'
+
+test_expect_success TTY 'git config --edit ignores pager.config' '
+ rm -f paginated.out editor.used &&
+ write_script editor <<-\EOF &&
+ touch editor.used
+ EOF
+ EDITOR=./editor test_terminal git -c pager.config config --edit &&
+ ! test -e paginated.out &&
+ test -e editor.used
+'
+
+test_expect_success TTY 'git config --get ignores pager.config' '
+ rm -f paginated.out &&
+ test_terminal git -c pager.config config --get foo.bar &&
+ ! test -e paginated.out
+'
+
+test_expect_success TTY 'git config --get-urlmatch defaults to paging' '
+ rm -f paginated.out &&
+ test_terminal git -c http."https://foo.com/".bar=foo \
+ config --get-urlmatch http https://foo.com &&
+ test -e paginated.out
+'
+
+test_expect_success TTY 'git config --get-all respects pager.config' '
+ rm -f paginated.out &&
+ test_terminal git -c pager.config=false config --get-all foo.bar &&
+ ! test -e paginated.out
+'
+
+test_expect_success TTY 'git config --list defaults to paging' '
+ rm -f paginated.out &&
+ test_terminal git config --list &&
+ test -e paginated.out
+'
+
+
# A colored commit log will begin with an appropriate ANSI escape
# for the first color; the text "commit" comes later.
colorful() {
diff --git a/t/t7063-status-untracked-cache.sh b/t/t7063-status-untracked-cache.sh
index e5fb892f95..c61e304e97 100755
--- a/t/t7063-status-untracked-cache.sh
+++ b/t/t7063-status-untracked-cache.sh
@@ -14,6 +14,9 @@ test_description='test untracked cache'
# See <20160803174522.5571-1-pclouds@gmail.com> if you want to know
# more.
+GIT_FORCE_UNTRACKED_CACHE=true
+export GIT_FORCE_UNTRACKED_CACHE
+
sync_mtime () {
find . -type d -ls >/dev/null
}
@@ -22,6 +25,12 @@ avoid_racy() {
sleep 1
}
+status_is_clean() {
+ >../status.expect &&
+ git status --porcelain >../status.actual &&
+ test_cmp ../status.expect ../status.actual
+}
+
test_lazy_prereq UNTRACKED_CACHE '
{ git update-index --test-untracked-cache; ret=$?; } &&
test $ret -ne 1
@@ -683,4 +692,85 @@ test_expect_success 'untracked cache survives a commit' '
test_cmp ../before ../after
'
+test_expect_success 'teardown worktree' '
+ cd ..
+'
+
+test_expect_success SYMLINKS 'setup worktree for symlink test' '
+ git init worktree-symlink &&
+ cd worktree-symlink &&
+ git config core.untrackedCache true &&
+ mkdir one two &&
+ touch one/file two/file &&
+ git add one/file two/file &&
+ git commit -m"first commit" &&
+ git rm -rf one &&
+ ln -s two one &&
+ git add one &&
+ git commit -m"second commit"
+'
+
+test_expect_success SYMLINKS '"status" after symlink replacement should be clean with UC=true' '
+ git checkout HEAD~ &&
+ status_is_clean &&
+ status_is_clean &&
+ git checkout master &&
+ avoid_racy &&
+ status_is_clean &&
+ status_is_clean
+'
+
+test_expect_success SYMLINKS '"status" after symlink replacement should be clean with UC=false' '
+ git config core.untrackedCache false &&
+ git checkout HEAD~ &&
+ status_is_clean &&
+ status_is_clean &&
+ git checkout master &&
+ avoid_racy &&
+ status_is_clean &&
+ status_is_clean
+'
+
+test_expect_success 'setup worktree for non-symlink test' '
+ git init worktree-non-symlink &&
+ cd worktree-non-symlink &&
+ git config core.untrackedCache true &&
+ mkdir one two &&
+ touch one/file two/file &&
+ git add one/file two/file &&
+ git commit -m"first commit" &&
+ git rm -rf one &&
+ cp two/file one &&
+ git add one &&
+ git commit -m"second commit"
+'
+
+test_expect_success '"status" after file replacement should be clean with UC=true' '
+ git checkout HEAD~ &&
+ status_is_clean &&
+ status_is_clean &&
+ git checkout master &&
+ avoid_racy &&
+ status_is_clean &&
+ test-dump-untracked-cache >../actual &&
+ grep -F "recurse valid" ../actual >../actual.grep &&
+ cat >../expect.grep <<EOF &&
+/ 0000000000000000000000000000000000000000 recurse valid
+/two/ 0000000000000000000000000000000000000000 recurse valid
+EOF
+ status_is_clean &&
+ test_cmp ../expect.grep ../actual.grep
+'
+
+test_expect_success '"status" after file replacement should be clean with UC=false' '
+ git config core.untrackedCache false &&
+ git checkout HEAD~ &&
+ status_is_clean &&
+ status_is_clean &&
+ git checkout master &&
+ avoid_racy &&
+ status_is_clean &&
+ status_is_clean
+'
+
test_done
diff --git a/t/t7064-wtstatus-pv2.sh b/t/t7064-wtstatus-pv2.sh
index e319fa2e84..8f795327a0 100755
--- a/t/t7064-wtstatus-pv2.sh
+++ b/t/t7064-wtstatus-pv2.sh
@@ -390,6 +390,68 @@ test_expect_success 'verify upstream fields in branch header' '
)
'
+test_expect_success 'verify --[no-]ahead-behind with V2 format' '
+ git checkout master &&
+ test_when_finished "rm -rf sub_repo" &&
+ git clone . sub_repo &&
+ (
+ ## Confirm local master tracks remote master.
+ cd sub_repo &&
+ HUF=$(git rev-parse HEAD) &&
+
+ # Confirm --no-ahead-behind reports traditional branch.ab with 0/0 for equal branches.
+ cat >expect <<-EOF &&
+ # branch.oid $HUF
+ # branch.head master
+ # branch.upstream origin/master
+ # branch.ab +0 -0
+ EOF
+
+ git status --no-ahead-behind --porcelain=v2 --branch --untracked-files=all >actual &&
+ test_cmp expect actual &&
+
+ # Confirm --ahead-behind reports traditional branch.ab with 0/0.
+ cat >expect <<-EOF &&
+ # branch.oid $HUF
+ # branch.head master
+ # branch.upstream origin/master
+ # branch.ab +0 -0
+ EOF
+
+ git status --ahead-behind --porcelain=v2 --branch --untracked-files=all >actual &&
+ test_cmp expect actual &&
+
+ ## Test non-equal ahead/behind.
+ echo xyz >file_xyz &&
+ git add file_xyz &&
+ git commit -m xyz &&
+
+ HUF=$(git rev-parse HEAD) &&
+
+ # Confirm --no-ahead-behind reports branch.ab with ?/? for non-equal branches.
+ cat >expect <<-EOF &&
+ # branch.oid $HUF
+ # branch.head master
+ # branch.upstream origin/master
+ # branch.ab +? -?
+ EOF
+
+ git status --no-ahead-behind --porcelain=v2 --branch --untracked-files=all >actual &&
+ test_cmp expect actual &&
+
+ # Confirm --ahead-behind reports traditional branch.ab with 1/0.
+ cat >expect <<-EOF &&
+ # branch.oid $HUF
+ # branch.head master
+ # branch.upstream origin/master
+ # branch.ab +1 -0
+ EOF
+
+ git status --ahead-behind --porcelain=v2 --branch --untracked-files=all >actual &&
+ test_cmp expect actual
+ )
+'
+
test_expect_success 'create and add submodule, submodule appears clean (A. S...)' '
git checkout master &&
git clone . sub_repo &&
diff --git a/t/t7409-submodule-detached-work-tree.sh b/t/t7409-submodule-detached-work-tree.sh
index c20717181e..fc018e3638 100755
--- a/t/t7409-submodule-detached-work-tree.sh
+++ b/t/t7409-submodule-detached-work-tree.sh
@@ -6,7 +6,7 @@
test_description='Test submodules on detached working tree
This test verifies that "git submodule" initialization, update and addition works
-on detahced working trees
+on detached working trees
'
TEST_NO_CREATE_REPO=1
diff --git a/t/t7519-status-fsmonitor.sh b/t/t7519-status-fsmonitor.sh
index eb2d13bbcf..756beb0d8e 100755
--- a/t/t7519-status-fsmonitor.sh
+++ b/t/t7519-status-fsmonitor.sh
@@ -314,4 +314,43 @@ test_expect_success 'splitting the index results in the same state' '
test_cmp expect actual
'
+test_expect_success UNTRACKED_CACHE 'ignore .git changes when invalidating UNTR' '
+ test_create_repo dot-git &&
+ (
+ cd dot-git &&
+ mkdir -p .git/hooks &&
+ : >tracked &&
+ : >modified &&
+ mkdir dir1 &&
+ : >dir1/tracked &&
+ : >dir1/modified &&
+ mkdir dir2 &&
+ : >dir2/tracked &&
+ : >dir2/modified &&
+ write_integration_script &&
+ git config core.fsmonitor .git/hooks/fsmonitor-test &&
+ git update-index --untracked-cache &&
+ git update-index --fsmonitor &&
+ GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace-before" \
+ git status &&
+ test-dump-untracked-cache >../before
+ ) &&
+ cat >>dot-git/.git/hooks/fsmonitor-test <<-\EOF &&
+ printf ".git\0"
+ printf ".git/index\0"
+ printf "dir1/.git\0"
+ printf "dir1/.git/index\0"
+ EOF
+ (
+ cd dot-git &&
+ GIT_TRACE_UNTRACKED_STATS="$TRASH_DIRECTORY/trace-after" \
+ git status &&
+ test-dump-untracked-cache >../after
+ ) &&
+ grep "directory invalidation" trace-before >>before &&
+ grep "directory invalidation" trace-after >>after &&
+ # UNTR extension unchanged, dir invalidation count unchanged
+ test_cmp before after
+'
+
test_done
diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh
index dfde6a675a..6736d8d131 100755
--- a/t/t7600-merge.sh
+++ b/t/t7600-merge.sh
@@ -700,6 +700,42 @@ test_expect_success 'merge --no-ff --edit' '
test_cmp expected actual
'
+test_expect_success 'merge annotated/signed tag w/o tracking' '
+ test_when_finished "rm -rf dst; git tag -d anno1" &&
+ git tag -a -m "anno c1" anno1 c1 &&
+ git init dst &&
+ git rev-parse c1 >dst/expect &&
+ (
+ # c0 fast-forwards to c1 but because this repository
+ # is not a "downstream" whose refs/tags follows along
+ # tag from the "upstream", this pull defaults to --no-ff
+ cd dst &&
+ git pull .. c0 &&
+ git pull .. anno1 &&
+ git rev-parse HEAD^2 >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'merge annotated/signed tag w/ tracking' '
+ test_when_finished "rm -rf dst; git tag -d anno1" &&
+ git tag -a -m "anno c1" anno1 c1 &&
+ git init dst &&
+ git rev-parse c1 >dst/expect &&
+ (
+ # c0 fast-forwards to c1 and because this repository
+ # is a "downstream" whose refs/tags follows along
+ # tag from the "upstream", this pull defaults to --ff
+ cd dst &&
+ git remote add origin .. &&
+ git pull origin c0 &&
+ git fetch origin &&
+ git merge anno1 &&
+ git rev-parse HEAD >actual &&
+ test_cmp expect actual
+ )
+'
+
test_expect_success GPG 'merge --ff-only tag' '
git reset --hard c0 &&
git commit --allow-empty -m "A newer commit" &&
@@ -718,7 +754,7 @@ test_expect_success GPG 'merge --no-edit tag should skip editor' '
git tag -f -s -m "A newer commit" signed &&
git reset --hard c0 &&
- EDITOR=false git merge --no-edit signed &&
+ EDITOR=false git merge --no-edit --no-ff signed &&
git rev-parse signed^0 >expect &&
git rev-parse HEAD^2 >actual &&
test_cmp expect actual
diff --git a/t/t7607-merge-overwrite.sh b/t/t7607-merge-overwrite.sh
index 9444d6a9b9..dd8ab7ede1 100755
--- a/t/t7607-merge-overwrite.sh
+++ b/t/t7607-merge-overwrite.sh
@@ -92,12 +92,15 @@ test_expect_success 'will not overwrite removed file with staged changes' '
test_cmp important c1.c
'
-test_expect_failure 'will not overwrite unstaged changes in renamed file' '
+test_expect_success 'will not overwrite unstaged changes in renamed file' '
git reset --hard c1 &&
git mv c1.c other.c &&
git commit -m rename &&
cp important other.c &&
- git merge c1a &&
+ test_must_fail git merge c1a >out &&
+ test_i18ngrep "Refusing to lose dirty file at other.c" out &&
+ test_path_is_file other.c~HEAD &&
+ test $(git hash-object other.c~HEAD) = $(git rev-parse c1a:c1.c) &&
test_cmp important other.c
'
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index 19601fb546..e80eacbb1b 100755
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
@@ -224,6 +224,7 @@ Message-Id: MESSAGE-ID-STRING
X-Mailer: X-MAILER-STRING
In-Reply-To: <unique-message-id@example.com>
References: <unique-message-id@example.com>
+Reply-To: Reply <reply@example.com>
Result: OK
EOF
@@ -316,6 +317,7 @@ test_expect_success $PREREQ 'Show all headers' '
--dry-run \
--suppress-cc=sob \
--from="Example <from@example.com>" \
+ --reply-to="Reply <reply@example.com>" \
--to=to@example.com \
--cc=cc@example.com \
--bcc=bcc@example.com \
diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh
index c30660d606..06742748e9 100755
--- a/t/t9400-git-cvsserver-server.sh
+++ b/t/t9400-git-cvsserver-server.sh
@@ -447,12 +447,10 @@ test_expect_success 'cvs update (-p)' '
git push gitcvs.git >/dev/null &&
cd cvswork &&
GIT_CONFIG="$git_config" cvs update &&
- rm -f failures &&
for i in merge no-lf empty really-empty; do
- GIT_CONFIG="$git_config" cvs update -p "$i" >$i.out
- test_cmp $i.out ../$i >>failures 2>&1
- done &&
- test -z "$(cat failures)"
+ GIT_CONFIG="$git_config" cvs update -p "$i" >$i.out &&
+ test_cmp $i.out ../$i || return 1
+ done
'
cd "$WORKDIR"
diff --git a/t/t9402-git-cvsserver-refs.sh b/t/t9402-git-cvsserver-refs.sh
index 6d2d3c8739..cf31ace667 100755
--- a/t/t9402-git-cvsserver-refs.sh
+++ b/t/t9402-git-cvsserver-refs.sh
@@ -455,20 +455,20 @@ test_expect_success 'cvs up -r $(git rev-parse v1)' '
'
test_expect_success 'cvs diff -r v1 -u' '
- ( cd cvswork && cvs -f diff -r v1 -u ) >cvsDiff.out 2>cvs.log &&
+ ( cd cvswork && cvs -f diff -r v1 -u >../cvsDiff.out 2>../cvs.log ) &&
test_must_be_empty cvsDiff.out &&
test_must_be_empty cvs.log
'
test_expect_success 'cvs diff -N -r v2 -u' '
- ( cd cvswork && ! cvs -f diff -N -r v2 -u ) >cvsDiff.out 2>cvs.log &&
+ ( cd cvswork && ! cvs -f diff -N -r v2 -u >../cvsDiff.out 2>../cvs.log ) &&
test_must_be_empty cvs.log &&
test -s cvsDiff.out &&
check_diff cvsDiff.out v2 v1 >check_diff.out 2>&1
'
test_expect_success 'cvs diff -N -r v2 -r v1.2' '
- ( cd cvswork && ! cvs -f diff -N -r v2 -r v1.2 -u ) >cvsDiff.out 2>cvs.log &&
+ ( cd cvswork && ! cvs -f diff -N -r v2 -r v1.2 -u >../cvsDiff.out 2>../cvs.log ) &&
test_must_be_empty cvs.log &&
test -s cvsDiff.out &&
check_diff cvsDiff.out v2 v1.2 >check_diff.out 2>&1
@@ -487,7 +487,7 @@ test_expect_success 'apply early [cvswork3] diff to b3' '
'
test_expect_success 'check [cvswork3] diff' '
- ( cd cvswork3 && ! cvs -f diff -N -u ) >"$WORKDIR/cvsDiff.out" 2>cvs.log &&
+ ( cd cvswork3 && ! cvs -f diff -N -u >"$WORKDIR/cvsDiff.out" 2>../cvs.log ) &&
test_must_be_empty cvs.log &&
test -s cvsDiff.out &&
test $(grep Index: cvsDiff.out | wc -l) = 3 &&
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index fc614dcbfa..1b34caa1e1 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -1237,17 +1237,19 @@ test_expect_success 'double dash "git" itself' '
test_expect_success 'double dash "git checkout"' '
test_completion "git checkout --" <<-\EOF
--quiet Z
+ --detach Z
+ --track Z
+ --orphan=Z
--ours Z
--theirs Z
- --track Z
- --no-track Z
--merge Z
- --conflict=
- --orphan Z
+ --conflict=Z
--patch Z
- --detach Z
--ignore-skip-worktree-bits Z
+ --ignore-other-worktrees Z
--recurse-submodules Z
+ --progress Z
+ --no-track Z
--no-recurse-submodules Z
EOF
'
@@ -1452,6 +1454,12 @@ test_expect_success 'completion used <cmd> completion for alias: !f() { : git <c
EOF
'
+test_expect_success 'completion without explicit _git_xxx function' '
+ test_completion "git version --" <<-\EOF
+ --build-options Z
+ EOF
+'
+
test_expect_failure 'complete with tilde expansion' '
git init tmp && cd tmp &&
test_when_finished "cd .. && rm -rf tmp" &&
@@ -1495,4 +1503,35 @@ do
'
done
+test_expect_success 'sourcing the completion script clears cached commands' '
+ __git_compute_all_commands &&
+ verbose test -n "$__git_all_commands" &&
+ . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" &&
+ verbose test -z "$__git_all_commands"
+'
+
+test_expect_success 'sourcing the completion script clears cached porcelain commands' '
+ __git_compute_porcelain_commands &&
+ verbose test -n "$__git_porcelain_commands" &&
+ . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" &&
+ verbose test -z "$__git_porcelain_commands"
+'
+
+test_expect_success !GETTEXT_POISON 'sourcing the completion script clears cached merge strategies' '
+ __git_compute_merge_strategies &&
+ verbose test -n "$__git_merge_strategies" &&
+ . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" &&
+ verbose test -z "$__git_merge_strategies"
+'
+
+test_expect_success 'sourcing the completion script clears cached --options' '
+ __gitcomp_builtin checkout &&
+ verbose test -n "$__gitcomp_builtin_checkout" &&
+ __gitcomp_builtin notes_edit &&
+ verbose test -n "$__gitcomp_builtin_notes_edit" &&
+ . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" &&
+ verbose test -z "$__gitcomp_builtin_checkout" &&
+ verbose test -z "$__gitcomp_builtin_notes_edit"
+'
+
test_done
diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh
index 97c9b32c2e..8f5c811dd7 100755
--- a/t/t9903-bash-prompt.sh
+++ b/t/t9903-bash-prompt.sh
@@ -735,22 +735,12 @@ test_expect_success 'prompt - hide if pwd ignored - env var set, config unset, p
test_cmp expected "$actual"
'
-test_expect_success 'prompt - hide if pwd ignored - inside gitdir (stdout)' '
+test_expect_success 'prompt - hide if pwd ignored - inside gitdir' '
printf " (GIT_DIR!)" >expected &&
(
GIT_PS1_HIDE_IF_PWD_IGNORED=y &&
cd .git &&
- __git_ps1 >"$actual" 2>/dev/null
- ) &&
- test_cmp expected "$actual"
-'
-
-test_expect_success 'prompt - hide if pwd ignored - inside gitdir (stderr)' '
- printf "" >expected &&
- (
- GIT_PS1_HIDE_IF_PWD_IGNORED=y &&
- cd .git &&
- __git_ps1 >/dev/null 2>"$actual"
+ __git_ps1 >"$actual"
) &&
test_cmp expected "$actual"
'
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
index a679b02a1c..7d620bf2a9 100644
--- a/t/test-lib-functions.sh
+++ b/t/test-lib-functions.sh
@@ -610,6 +610,14 @@ list_contains () {
#
# Writing this as "! git checkout ../outerspace" is wrong, because
# the failure could be due to a segv. We want a controlled failure.
+#
+# Accepts the following options:
+#
+# ok=<signal-name>[,<...>]:
+# Don't treat an exit caused by the given signal as error.
+# Multiple signals can be specified as a comma separated list.
+# Currently recognized signal names are: sigpipe, success.
+# (Don't use 'success', use 'test_might_fail' instead.)
test_must_fail () {
case "$1" in
@@ -621,30 +629,30 @@ test_must_fail () {
_test_ok=
;;
esac
- "$@"
+ "$@" 2>&7
exit_code=$?
if test $exit_code -eq 0 && ! list_contains "$_test_ok" success
then
- echo >&2 "test_must_fail: command succeeded: $*"
+ echo >&4 "test_must_fail: command succeeded: $*"
return 1
elif test_match_signal 13 $exit_code && list_contains "$_test_ok" sigpipe
then
return 0
elif test $exit_code -gt 129 && test $exit_code -le 192
then
- echo >&2 "test_must_fail: died by signal $(($exit_code - 128)): $*"
+ echo >&4 "test_must_fail: died by signal $(($exit_code - 128)): $*"
return 1
elif test $exit_code -eq 127
then
- echo >&2 "test_must_fail: command not found: $*"
+ echo >&4 "test_must_fail: command not found: $*"
return 1
elif test $exit_code -eq 126
then
- echo >&2 "test_must_fail: valgrind error: $*"
+ echo >&4 "test_must_fail: valgrind error: $*"
return 1
fi
return 0
-}
+} 7>&2 2>&4
# Similar to test_must_fail, but tolerates success, too. This is
# meant to be used in contexts like:
@@ -656,10 +664,12 @@ test_must_fail () {
#
# Writing "git config --unset all.configuration || :" would be wrong,
# because we want to notice if it fails due to segv.
+#
+# Accepts the same options as test_must_fail.
test_might_fail () {
- test_must_fail ok=success "$@"
-}
+ test_must_fail ok=success "$@" 2>&7
+} 7>&2 2>&4
# Similar to test_must_fail and test_might_fail, but check that a
# given command exited with a given exit code. Meant to be used as:
@@ -671,16 +681,16 @@ test_might_fail () {
test_expect_code () {
want_code=$1
shift
- "$@"
+ "$@" 2>&7
exit_code=$?
if test $exit_code = $want_code
then
return 0
fi
- echo >&2 "test_expect_code: command exited with $exit_code, we wanted $want_code $*"
+ echo >&4 "test_expect_code: command exited with $exit_code, we wanted $want_code $*"
return 1
-}
+} 7>&2 2>&4
# test_cmp is a helper function to compare actual and expected output.
# You can use it like:
@@ -705,12 +715,66 @@ test_cmp_bin() {
cmp "$@"
}
+# Use this instead of test_cmp to compare files that contain expected and
+# actual output from git commands that can be translated. When running
+# under GETTEXT_POISON this pretends that the command produced expected
+# results.
+test_i18ncmp () {
+ test -n "$GETTEXT_POISON" || test_cmp "$@"
+}
+
+# Use this instead of "grep expected-string actual" to see if the
+# output from a git command that can be translated either contains an
+# expected string, or does not contain an unwanted one. When running
+# under GETTEXT_POISON this pretends that the command produced expected
+# results.
+test_i18ngrep () {
+ eval "last_arg=\${$#}"
+
+ test -f "$last_arg" ||
+ error "bug in the test script: test_i18ngrep requires a file" \
+ "to read as the last parameter"
+
+ if test $# -lt 2 ||
+ { test "x!" = "x$1" && test $# -lt 3 ; }
+ then
+ error "bug in the test script: too few parameters to test_i18ngrep"
+ fi
+
+ if test -n "$GETTEXT_POISON"
+ then
+ # pretend success
+ return 0
+ fi
+
+ if test "x!" = "x$1"
+ then
+ shift
+ ! grep "$@" && return 0
+
+ echo >&4 "error: '! grep $@' did find a match in:"
+ else
+ grep "$@" && return 0
+
+ echo >&4 "error: 'grep $@' didn't find a match in:"
+ fi
+
+ if test -s "$last_arg"
+ then
+ cat >&4 "$last_arg"
+ else
+ echo >&4 "<File '$last_arg' is empty>"
+ fi
+
+ return 1
+}
+
# Call any command "$@" but be more verbose about its
# failure. This is handy for commands like "test" which do
# not output anything when they fail.
verbose () {
"$@" && return 0
- echo >&2 "command failed: $(git rev-parse --sq-quote "$@")"
+ echo >&4 "command failed: $(git rev-parse --sq-quote "$@")"
return 1
}
@@ -718,6 +782,7 @@ verbose () {
# otherwise.
test_must_be_empty () {
+ test_path_is_file "$1" &&
if test -s "$1"
then
echo "'$1' is not empty, it contains:"
@@ -828,8 +893,8 @@ test_write_lines () {
}
perl () {
- command "$PERL_PATH" "$@"
-}
+ command "$PERL_PATH" "$@" 2>&7
+} 7>&2 2>&4
# Is the value one of the various ways to spell a boolean true/false?
test_normalize_bool () {
@@ -969,13 +1034,13 @@ test_env () {
shift
;;
*)
- "$@"
+ "$@" 2>&7
exit
;;
esac
done
)
-}
+} 7>&2 2>&4
# Returns true if the numeric exit code in "$2" represents the expected signal
# in "$1". Signals should be given numerically.
@@ -1017,9 +1082,9 @@ nongit () {
GIT_CEILING_DIRECTORIES=$(pwd) &&
export GIT_CEILING_DIRECTORIES &&
cd non-repo &&
- "$@"
+ "$@" 2>&7
)
-}
+} 7>&2 2>&4
# convert stdin to pktline representation; note that empty input becomes an
# empty packet, not a flush packet (for that you can just print 0000 yourself).
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 9af19055b3..7740d511d2 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -116,6 +116,7 @@ unset VISUAL EMAIL LANGUAGE COLUMNS $("$PERL_PATH" -e '
my @vars = grep(/^GIT_/ && !/^GIT_($ok)/o, @env);
print join("\n", @vars);
')
+unset XDG_CACHE_HOME
unset XDG_CONFIG_HOME
unset GITPERLLIB
GIT_AUTHOR_EMAIL=author@example.com
@@ -263,7 +264,24 @@ do
GIT_TEST_CHAIN_LINT=0
shift ;;
-x)
- trace=t
+ # Some test scripts can't be reliably traced with '-x',
+ # unless the test is run with a Bash version supporting
+ # BASH_XTRACEFD (introduced in Bash v4.1). Check whether
+ # this test is marked as such, and ignore '-x' if it
+ # isn't executed with a suitable Bash version.
+ if test -z "$test_untraceable" || {
+ test -n "$BASH_VERSION" && {
+ test ${BASH_VERSINFO[0]} -gt 4 || {
+ test ${BASH_VERSINFO[0]} -eq 4 &&
+ test ${BASH_VERSINFO[1]} -ge 1
+ }
+ }
+ }
+ then
+ trace=t
+ else
+ echo >&2 "warning: ignoring -x; '$0' is untraceable without BASH_XTRACEFD"
+ fi
shift ;;
--verbose-log)
verbose_log=t
@@ -1062,32 +1080,6 @@ else
test_set_prereq C_LOCALE_OUTPUT
fi
-# Use this instead of test_cmp to compare files that contain expected and
-# actual output from git commands that can be translated. When running
-# under GETTEXT_POISON this pretends that the command produced expected
-# results.
-test_i18ncmp () {
- test -n "$GETTEXT_POISON" || test_cmp "$@"
-}
-
-# Use this instead of "grep expected-string actual" to see if the
-# output from a git command that can be translated either contains an
-# expected string, or does not contain an unwanted one. When running
-# under GETTEXT_POISON this pretends that the command produced expected
-# results.
-test_i18ngrep () {
- if test -n "$GETTEXT_POISON"
- then
- : # pretend success
- elif test "x!" = "x$1"
- then
- shift
- ! grep "$@"
- else
- grep "$@"
- fi
-}
-
test_lazy_prereq PIPE '
# test whether the filesystem supports FIFOs
test_have_prereq !MINGW,!CYGWIN &&
@@ -1132,6 +1124,10 @@ test_lazy_prereq EXPENSIVE '
test -n "$GIT_TEST_LONG"
'
+test_lazy_prereq EXPENSIVE_ON_WINDOWS '
+ test_have_prereq EXPENSIVE || test_have_prereq !MINGW,!CYGWIN
+'
+
test_lazy_prereq USR_BIN_TIME '
test -x /usr/bin/time
'