summaryrefslogtreecommitdiff
path: root/t
diff options
context:
space:
mode:
Diffstat (limited to 't')
-rw-r--r--t/helper/test-chmtime.c44
-rw-r--r--t/helper/test-pkt-line.c64
-rw-r--r--t/helper/test-ref-store.c3
-rw-r--r--t/helper/test-submodule-config.c8
-rwxr-xr-xt/perf/aggregate.perl62
-rwxr-xr-xt/perf/bisect_regression73
-rwxr-xr-xt/perf/bisect_run_script47
-rwxr-xr-xt/perf/run56
-rwxr-xr-xt/t0028-working-tree-encoding.sh245
-rwxr-xr-xt/t0061-run-command.sh2
-rwxr-xr-xt/t1300-config.sh (renamed from t/t1300-repo-config.sh)197
-rwxr-xr-xt/t1310-config-default.sh36
-rwxr-xr-xt/t1501-work-tree.sh12
-rwxr-xr-xt/t1510-repo-setup.sh1
-rwxr-xr-xt/t2022-checkout-paths.sh4
-rwxr-xr-xt/t2025-worktree-add.sh26
-rwxr-xr-xt/t2028-worktree-move.sh7
-rwxr-xr-xt/t3200-branch.sh24
-rwxr-xr-xt/t3404-rebase-interactive.sh2
-rwxr-xr-xt/t3418-rebase-continue.sh49
-rwxr-xr-xt/t3421-rebase-topology-linear.sh6
-rwxr-xr-xt/t3428-rebase-signoff.sh38
-rwxr-xr-xt/t3501-revert-cherry-pick.sh7
-rwxr-xr-xt/t3510-cherry-pick-sequence.sh4
-rwxr-xr-xt/t4200-rerere.sh8
-rwxr-xr-xt/t5000-tar-tree.sh2
-rwxr-xr-xt/t5304-prune.sh10
-rwxr-xr-xt/t5310-pack-bitmaps.sh8
-rwxr-xr-xt/t5318-commit-graph.sh224
-rwxr-xr-xt/t5404-tracking-branches.sh2
-rwxr-xr-xt/t5512-ls-remote.sh54
-rwxr-xr-xt/t5516-fetch-push.sh10
-rwxr-xr-xt/t5541-http-push-smart.sh12
-rwxr-xr-xt/t5550-http-fetch-dumb.sh11
-rwxr-xr-xt/t5561-http-backend.sh10
-rwxr-xr-xt/t5701-git-serve.sh197
-rwxr-xr-xt/t5702-protocol-v2.sh305
-rwxr-xr-xt/t6001-rev-list-graft.sh9
-rwxr-xr-xt/t6022-merge-rename.sh27
-rwxr-xr-xt/t6043-merge-rename-directories.sh3998
-rwxr-xr-xt/t6046-merge-skip-unneeded-updates.sh761
-rwxr-xr-xt/t6050-replace.sh28
-rwxr-xr-xt/t6500-gc.sh32
-rwxr-xr-xt/t6501-freshen-objects.sh6
-rwxr-xr-xt/t7001-mv.sh2
-rwxr-xr-xt/t7004-tag.sh15
-rwxr-xr-xt/t7005-editor.sh12
-rwxr-xr-xt/t7501-commit.sh12
-rwxr-xr-xt/t7508-status.sh4
-rwxr-xr-xt/t7607-merge-overwrite.sh2
-rwxr-xr-xt/t7700-repack.sh27
-rwxr-xr-xt/t7701-repack-unpack-unreachable.sh6
-rwxr-xr-xt/t9350-fast-export.sh18
-rw-r--r--t/test-lib-functions.sh38
-rw-r--r--t/test-lib.sh4
55 files changed, 6716 insertions, 155 deletions
diff --git a/t/helper/test-chmtime.c b/t/helper/test-chmtime.c
index 1790ceab51..aa22af48c2 100644
--- a/t/helper/test-chmtime.c
+++ b/t/helper/test-chmtime.c
@@ -18,20 +18,30 @@
*
* Examples:
*
- * To just print the mtime use --verbose and set the file mtime offset to 0:
+ * To print the mtime and the file name use --verbose and set
+ * the file mtime offset to 0:
*
* test-tool chmtime -v +0 file
*
+ * To print only the mtime use --get:
+ *
+ * test-tool chmtime --get file
+ *
* To set the mtime to current time:
*
* test-tool chmtime =+0 file
*
+ * To set the file mtime offset to +1 and print the new value:
+ *
+ * test-tool chmtime --get +1 file
+ *
*/
#include "test-tool.h"
#include "git-compat-util.h"
#include <utime.h>
-static const char usage_str[] = "-v|--verbose (+|=|=+|=-|-)<seconds> <file>...";
+static const char usage_str[] =
+ "(-v|--verbose|-g|--get) (+|=|=+|=-|-)<seconds> <file>...";
static int timespec_arg(const char *arg, long int *set_time, int *set_eq)
{
@@ -47,7 +57,6 @@ static int timespec_arg(const char *arg, long int *set_time, int *set_eq)
}
*set_time = strtol(timespec, &test, 10);
if (*test) {
- fprintf(stderr, "Not a base-10 integer: %s\n", arg + 1);
return 0;
}
if ((*set_eq && *set_time < 0) || *set_eq == 2) {
@@ -60,6 +69,7 @@ static int timespec_arg(const char *arg, long int *set_time, int *set_eq)
int cmd__chmtime(int argc, const char **argv)
{
static int verbose;
+ static int get;
int i = 1;
/* no mtime change by default */
@@ -69,18 +79,34 @@ int cmd__chmtime(int argc, const char **argv)
if (argc < 3)
goto usage;
- if (strcmp(argv[i], "--verbose") == 0 || strcmp(argv[i], "-v") == 0) {
+ if (strcmp(argv[i], "--get") == 0 || strcmp(argv[i], "-g") == 0) {
+ get = 1;
+ ++i;
+ } else if (strcmp(argv[i], "--verbose") == 0 || strcmp(argv[i], "-v") == 0) {
verbose = 1;
++i;
}
- if (timespec_arg(argv[i], &set_time, &set_eq))
+
+ if (i == argc) {
+ goto usage;
+ }
+
+ if (timespec_arg(argv[i], &set_time, &set_eq)) {
++i;
- else
+ } else {
+ if (get == 0) {
+ fprintf(stderr, "Not a base-10 integer: %s\n", argv[i] + 1);
+ goto usage;
+ }
+ }
+
+ if (i == argc)
goto usage;
for (; i < argc; i++) {
struct stat sb;
struct utimbuf utb;
+ uintmax_t mtime;
if (stat(argv[i], &sb) < 0) {
fprintf(stderr, "Failed to stat %s: %s\n",
@@ -100,8 +126,10 @@ int cmd__chmtime(int argc, const char **argv)
utb.actime = sb.st_atime;
utb.modtime = set_eq ? set_time : sb.st_mtime + set_time;
- if (verbose) {
- uintmax_t mtime = utb.modtime < 0 ? 0: utb.modtime;
+ mtime = utb.modtime < 0 ? 0: utb.modtime;
+ if (get) {
+ printf("%"PRIuMAX"\n", mtime);
+ } else if (verbose) {
printf("%"PRIuMAX"\t%s\n", mtime, argv[i]);
}
diff --git a/t/helper/test-pkt-line.c b/t/helper/test-pkt-line.c
new file mode 100644
index 0000000000..0f19e53c75
--- /dev/null
+++ b/t/helper/test-pkt-line.c
@@ -0,0 +1,64 @@
+#include "pkt-line.h"
+
+static void pack_line(const char *line)
+{
+ if (!strcmp(line, "0000") || !strcmp(line, "0000\n"))
+ packet_flush(1);
+ else if (!strcmp(line, "0001") || !strcmp(line, "0001\n"))
+ packet_delim(1);
+ else
+ packet_write_fmt(1, "%s", line);
+}
+
+static void pack(int argc, const char **argv)
+{
+ if (argc) { /* read from argv */
+ int i;
+ for (i = 0; i < argc; i++)
+ pack_line(argv[i]);
+ } else { /* read from stdin */
+ char line[LARGE_PACKET_MAX];
+ while (fgets(line, sizeof(line), stdin)) {
+ pack_line(line);
+ }
+ }
+}
+
+static void unpack(void)
+{
+ struct packet_reader reader;
+ packet_reader_init(&reader, 0, NULL, 0,
+ PACKET_READ_GENTLE_ON_EOF |
+ PACKET_READ_CHOMP_NEWLINE);
+
+ while (packet_reader_read(&reader) != PACKET_READ_EOF) {
+ switch (reader.status) {
+ case PACKET_READ_EOF:
+ break;
+ case PACKET_READ_NORMAL:
+ printf("%s\n", reader.line);
+ break;
+ case PACKET_READ_FLUSH:
+ printf("0000\n");
+ break;
+ case PACKET_READ_DELIM:
+ printf("0001\n");
+ break;
+ }
+ }
+}
+
+int cmd_main(int argc, const char **argv)
+{
+ if (argc < 2)
+ die("too few arguments");
+
+ if (!strcmp(argv[1], "pack"))
+ pack(argc - 2, argv + 2);
+ else if (!strcmp(argv[1], "unpack"))
+ unpack();
+ else
+ die("invalid argument '%s'", argv[1]);
+
+ return 0;
+}
diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c
index 7c4f43746e..e9e0541276 100644
--- a/t/helper/test-ref-store.c
+++ b/t/helper/test-ref-store.c
@@ -3,6 +3,7 @@
#include "refs.h"
#include "worktree.h"
#include "object-store.h"
+#include "repository.h"
static const char *notnull(const char *arg, const char *name)
{
@@ -23,7 +24,7 @@ static const char **get_store(const char **argv, struct ref_store **refs)
if (!argv[0]) {
die("ref store required");
} else if (!strcmp(argv[0], "main")) {
- *refs = get_main_ref_store();
+ *refs = get_main_ref_store(the_repository);
} else if (skip_prefix(argv[0], "submodule:", &gitdir)) {
struct strbuf sb = STRBUF_INIT;
int ret;
diff --git a/t/helper/test-submodule-config.c b/t/helper/test-submodule-config.c
index 5c6e4b010d..e2692746df 100644
--- a/t/helper/test-submodule-config.c
+++ b/t/helper/test-submodule-config.c
@@ -49,9 +49,11 @@ int cmd__submodule_config(int argc, const char **argv)
die_usage(argc, argv, "Commit not found.");
if (lookup_name) {
- submodule = submodule_from_name(&commit_oid, path_or_name);
+ submodule = submodule_from_name(the_repository,
+ &commit_oid, path_or_name);
} else
- submodule = submodule_from_path(&commit_oid, path_or_name);
+ submodule = submodule_from_path(the_repository,
+ &commit_oid, path_or_name);
if (!submodule)
die_usage(argc, argv, "Submodule not found.");
@@ -65,7 +67,7 @@ int cmd__submodule_config(int argc, const char **argv)
arg += 2;
}
- submodule_free();
+ submodule_free(the_repository);
return 0;
}
diff --git a/t/perf/aggregate.perl b/t/perf/aggregate.perl
index 48637ef64b..bc865160e7 100755
--- a/t/perf/aggregate.perl
+++ b/t/perf/aggregate.perl
@@ -4,6 +4,7 @@ use lib '../../perl/build/lib';
use strict;
use warnings;
use JSON;
+use Getopt::Long;
use Git;
sub get_times {
@@ -36,46 +37,34 @@ sub format_times {
return $out;
}
+sub usage {
+ print <<EOT;
+./aggregate.perl [options] [--] [<dir_or_rev>...] [--] [<test_script>...] >
+
+ Options:
+ --codespeed * Format output for Codespeed
+ --reponame <str> * Send given reponame to codespeed
+ --sort-by <str> * Sort output (only "regression" criteria is supported)
+ --subsection <str> * Use results from given subsection
+
+EOT
+ exit(1);
+}
+
my (@dirs, %dirnames, %dirabbrevs, %prefixes, @tests,
$codespeed, $sortby, $subsection, $reponame);
+
+Getopt::Long::Configure qw/ require_order /;
+
+my $rc = GetOptions("codespeed" => \$codespeed,
+ "reponame=s" => \$reponame,
+ "sort-by=s" => \$sortby,
+ "subsection=s" => \$subsection);
+usage() unless $rc;
+
while (scalar @ARGV) {
my $arg = $ARGV[0];
my $dir;
- if ($arg eq "--codespeed") {
- $codespeed = 1;
- shift @ARGV;
- next;
- }
- if ($arg =~ /--sort-by(?:=(.*))?/) {
- shift @ARGV;
- if (defined $1) {
- $sortby = $1;
- } else {
- $sortby = shift @ARGV;
- if (! defined $sortby) {
- die "'--sort-by' requires an argument";
- }
- }
- next;
- }
- if ($arg eq "--subsection") {
- shift @ARGV;
- $subsection = $ARGV[0];
- shift @ARGV;
- if (! $subsection) {
- die "empty subsection";
- }
- next;
- }
- if ($arg eq "--reponame") {
- shift @ARGV;
- $reponame = $ARGV[0];
- shift @ARGV;
- if (! $reponame) {
- die "empty reponame";
- }
- next;
- }
last if -f $arg or $arg eq "--";
if (! -d $arg) {
my $rev = Git::command_oneline(qw(rev-parse --verify), $arg);
@@ -225,7 +214,8 @@ sub print_sorted_results {
my ($sortby) = @_;
if ($sortby ne "regression") {
- die "only 'regression' is supported as '--sort-by' argument";
+ print "Only 'regression' is supported as '--sort-by' argument\n";
+ usage();
}
my @evolutions;
diff --git a/t/perf/bisect_regression b/t/perf/bisect_regression
new file mode 100755
index 0000000000..a94d9955d0
--- /dev/null
+++ b/t/perf/bisect_regression
@@ -0,0 +1,73 @@
+#!/bin/sh
+
+# Read a line coming from `./aggregate.perl --sort-by regression ...`
+# and automatically bisect to find the commit responsible for the
+# performance regression.
+#
+# Lines from `./aggregate.perl --sort-by regression ...` look like:
+#
+# +100.0% p7821-grep-engines-fixed.1 0.04(0.10+0.03) 0.08(0.11+0.08) v2.14.3 v2.15.1
+# +33.3% p7820-grep-engines.1 0.03(0.08+0.02) 0.04(0.08+0.02) v2.14.3 v2.15.1
+#
+
+die () {
+ echo >&2 "error: $*"
+ exit 1
+}
+
+while [ $# -gt 0 ]; do
+ arg="$1"
+ case "$arg" in
+ --help)
+ echo "usage: $0 [--config file] [--subsection subsection]"
+ exit 0
+ ;;
+ --config)
+ shift
+ GIT_PERF_CONFIG_FILE=$(cd "$(dirname "$1")"; pwd)/$(basename "$1")
+ export GIT_PERF_CONFIG_FILE
+ shift ;;
+ --subsection)
+ shift
+ GIT_PERF_SUBSECTION="$1"
+ export GIT_PERF_SUBSECTION
+ shift ;;
+ --*)
+ die "unrecognised option: '$arg'" ;;
+ *)
+ die "unknown argument '$arg'"
+ ;;
+ esac
+done
+
+read -r regression subtest oldtime newtime oldrev newrev
+
+test_script=$(echo "$subtest" | sed -e 's/\(.*\)\.[0-9]*$/\1.sh/')
+test_number=$(echo "$subtest" | sed -e 's/.*\.\([0-9]*\)$/\1/')
+
+# oldtime and newtime are decimal number, not integers
+
+oldtime=$(echo "$oldtime" | sed -e 's/^\([0-9]\+\.[0-9]\+\).*$/\1/')
+newtime=$(echo "$newtime" | sed -e 's/^\([0-9]\+\.[0-9]\+\).*$/\1/')
+
+test $(echo "$newtime" "$oldtime" | awk '{ print ($1 > $2) }') = 1 ||
+ die "New time '$newtime' shoud be greater than old time '$oldtime'"
+
+tmpdir=$(mktemp -d -t bisect_regression_XXXXXX) || die "Failed to create temp directory"
+echo "$oldtime" >"$tmpdir/oldtime" || die "Failed to write to '$tmpdir/oldtime'"
+echo "$newtime" >"$tmpdir/newtime" || die "Failed to write to '$tmpdir/newtime'"
+
+# Bisecting must be performed from the top level directory (even with --no-checkout)
+(
+ toplevel_dir=$(git rev-parse --show-toplevel) || die "Failed to find top level directory"
+ cd "$toplevel_dir" || die "Failed to cd into top level directory '$toplevel_dir'"
+
+ git bisect start --no-checkout "$newrev" "$oldrev" || die "Failed to start bisecting"
+
+ git bisect run t/perf/bisect_run_script "$test_script" "$test_number" "$tmpdir"
+ res="$?"
+
+ git bisect reset
+
+ exit "$res"
+)
diff --git a/t/perf/bisect_run_script b/t/perf/bisect_run_script
new file mode 100755
index 0000000000..038255df4b
--- /dev/null
+++ b/t/perf/bisect_run_script
@@ -0,0 +1,47 @@
+#!/bin/sh
+
+script="$1"
+test_number="$2"
+info_dir="$3"
+
+# This aborts the bisection immediately
+die () {
+ echo >&2 "error: $*"
+ exit 255
+}
+
+bisect_head=$(git rev-parse --verify BISECT_HEAD) || die "Failed to find BISECT_HEAD ref"
+
+script_number=$(echo "$script" | sed -e "s/^p\([0-9]*\).*\$/\1/") || die "Failed to get script number for '$script'"
+
+oldtime=$(cat "$info_dir/oldtime") || die "Failed to access '$info_dir/oldtime'"
+newtime=$(cat "$info_dir/newtime") || die "Failed to access '$info_dir/newtime'"
+
+cd t/perf || die "Failed to cd into 't/perf'"
+
+result_file="$info_dir/perf_${script_number}_${bisect_head}_results.txt"
+
+GIT_PERF_DIRS_OR_REVS="$bisect_head"
+export GIT_PERF_DIRS_OR_REVS
+
+./run "$script" >"$result_file" 2>&1 || die "Failed to run perf test '$script'"
+
+rtime=$(sed -n "s/^$script_number\.$test_number:.*\([0-9]\+\.[0-9]\+\)(.*).*\$/\1/p" "$result_file")
+
+echo "newtime: $newtime"
+echo "rtime: $rtime"
+echo "oldtime: $oldtime"
+
+# Compare ($newtime - $rtime) with ($rtime - $oldtime)
+# Times are decimal number, not integers
+
+if test $(echo "$newtime" "$rtime" "$oldtime" | awk '{ print ($1 - $2 > $2 - $3) }') = 1
+then
+ # Current commit is considered "good/old"
+ echo "$rtime" >"$info_dir/oldtime"
+ exit 0
+else
+ # Current commit is considered "bad/new"
+ echo "$rtime" >"$info_dir/newtime"
+ exit 1
+fi
diff --git a/t/perf/run b/t/perf/run
index 213da5d6b9..9aaa733c77 100755
--- a/t/perf/run
+++ b/t/perf/run
@@ -1,21 +1,34 @@
#!/bin/sh
-case "$1" in
+die () {
+ echo >&2 "error: $*"
+ exit 1
+}
+
+while [ $# -gt 0 ]; do
+ arg="$1"
+ case "$arg" in
+ --)
+ break ;;
--help)
- echo "usage: $0 [--config file] [other_git_tree...] [--] [test_scripts]"
- exit 0
- ;;
+ echo "usage: $0 [--config file] [--subsection subsec] [other_git_tree...] [--] [test_scripts]"
+ exit 0 ;;
--config)
shift
GIT_PERF_CONFIG_FILE=$(cd "$(dirname "$1")"; pwd)/$(basename "$1")
export GIT_PERF_CONFIG_FILE
shift ;;
-esac
-
-die () {
- echo >&2 "error: $*"
- exit 1
-}
+ --subsection)
+ shift
+ GIT_PERF_SUBSECTION="$1"
+ export GIT_PERF_SUBSECTION
+ shift ;;
+ --*)
+ die "unrecognised option: '$arg'" ;;
+ *)
+ break ;;
+ esac
+done
run_one_dir () {
if test $# -eq 0; then
@@ -172,9 +185,32 @@ get_subsections "perf" >test-results/run_subsections.names
if test $(wc -l <test-results/run_subsections.names) -eq 0
then
+ if test -n "$GIT_PERF_SUBSECTION"
+ then
+ if test -n "$GIT_PERF_CONFIG_FILE"
+ then
+ die "no subsections are defined in config file '$GIT_PERF_CONFIG_FILE'"
+ else
+ die "subsection '$GIT_PERF_SUBSECTION' defined without a config file"
+ fi
+ fi
(
run_subsection "$@"
)
+elif test -n "$GIT_PERF_SUBSECTION"
+then
+ egrep "^$GIT_PERF_SUBSECTION\$" test-results/run_subsections.names >/dev/null ||
+ die "subsection '$GIT_PERF_SUBSECTION' not found in '$GIT_PERF_CONFIG_FILE'"
+
+ egrep "^$GIT_PERF_SUBSECTION\$" test-results/run_subsections.names | while read -r subsec
+ do
+ (
+ GIT_PERF_SUBSECTION="$subsec"
+ export GIT_PERF_SUBSECTION
+ echo "======== Run for subsection '$GIT_PERF_SUBSECTION' ========"
+ run_subsection "$@"
+ )
+ done
else
while read -r subsec
do
diff --git a/t/t0028-working-tree-encoding.sh b/t/t0028-working-tree-encoding.sh
new file mode 100755
index 0000000000..12b8eb963a
--- /dev/null
+++ b/t/t0028-working-tree-encoding.sh
@@ -0,0 +1,245 @@
+#!/bin/sh
+
+test_description='working-tree-encoding conversion via gitattributes'
+
+. ./test-lib.sh
+
+GIT_TRACE_WORKING_TREE_ENCODING=1 && export GIT_TRACE_WORKING_TREE_ENCODING
+
+test_expect_success 'setup test files' '
+ git config core.eol lf &&
+
+ text="hallo there!\ncan you read me?" &&
+ echo "*.utf16 text working-tree-encoding=utf-16" >.gitattributes &&
+ printf "$text" >test.utf8.raw &&
+ printf "$text" | iconv -f UTF-8 -t UTF-16 >test.utf16.raw &&
+ printf "$text" | iconv -f UTF-8 -t UTF-32 >test.utf32.raw &&
+
+ # Line ending tests
+ printf "one\ntwo\nthree\n" >lf.utf8.raw &&
+ printf "one\r\ntwo\r\nthree\r\n" >crlf.utf8.raw &&
+
+ # BOM tests
+ printf "\0a\0b\0c" >nobom.utf16be.raw &&
+ printf "a\0b\0c\0" >nobom.utf16le.raw &&
+ printf "\376\777\0a\0b\0c" >bebom.utf16be.raw &&
+ printf "\777\376a\0b\0c\0" >lebom.utf16le.raw &&
+ printf "\0\0\0a\0\0\0b\0\0\0c" >nobom.utf32be.raw &&
+ printf "a\0\0\0b\0\0\0c\0\0\0" >nobom.utf32le.raw &&
+ printf "\0\0\376\777\0\0\0a\0\0\0b\0\0\0c" >bebom.utf32be.raw &&
+ printf "\777\376\0\0a\0\0\0b\0\0\0c\0\0\0" >lebom.utf32le.raw &&
+
+ # Add only UTF-16 file, we will add the UTF-32 file later
+ cp test.utf16.raw test.utf16 &&
+ cp test.utf32.raw test.utf32 &&
+ git add .gitattributes test.utf16 &&
+ git commit -m initial
+'
+
+test_expect_success 'ensure UTF-8 is stored in Git' '
+ test_when_finished "rm -f test.utf16.git" &&
+
+ git cat-file -p :test.utf16 >test.utf16.git &&
+ test_cmp_bin test.utf8.raw test.utf16.git
+'
+
+test_expect_success 're-encode to UTF-16 on checkout' '
+ test_when_finished "rm -f test.utf16.raw" &&
+
+ rm test.utf16 &&
+ git checkout test.utf16 &&
+ test_cmp_bin test.utf16.raw test.utf16
+'
+
+test_expect_success 'check $GIT_DIR/info/attributes support' '
+ test_when_finished "rm -f test.utf32.git" &&
+ test_when_finished "git reset --hard HEAD" &&
+
+ echo "*.utf32 text working-tree-encoding=utf-32" >.git/info/attributes &&
+ git add test.utf32 &&
+
+ git cat-file -p :test.utf32 >test.utf32.git &&
+ test_cmp_bin test.utf8.raw test.utf32.git
+'
+
+for i in 16 32
+do
+ test_expect_success "check prohibited UTF-${i} BOM" '
+ test_when_finished "git reset --hard HEAD" &&
+
+ echo "*.utf${i}be text working-tree-encoding=utf-${i}be" >>.gitattributes &&
+ echo "*.utf${i}le text working-tree-encoding=utf-${i}LE" >>.gitattributes &&
+
+ # Here we add a UTF-16 (resp. UTF-32) files with BOM (big/little-endian)
+ # but we tell Git to treat it as UTF-16BE/UTF-16LE (resp. UTF-32).
+ # In these cases the BOM is prohibited.
+ cp bebom.utf${i}be.raw bebom.utf${i}be &&
+ test_must_fail git add bebom.utf${i}be 2>err.out &&
+ test_i18ngrep "fatal: BOM is prohibited .* utf-${i}be" err.out &&
+ test_i18ngrep "use UTF-${i} as working-tree-encoding" err.out &&
+
+ cp lebom.utf${i}le.raw lebom.utf${i}be &&
+ test_must_fail git add lebom.utf${i}be 2>err.out &&
+ test_i18ngrep "fatal: BOM is prohibited .* utf-${i}be" err.out &&
+ test_i18ngrep "use UTF-${i} as working-tree-encoding" err.out &&
+
+ cp bebom.utf${i}be.raw bebom.utf${i}le &&
+ test_must_fail git add bebom.utf${i}le 2>err.out &&
+ test_i18ngrep "fatal: BOM is prohibited .* utf-${i}LE" err.out &&
+ test_i18ngrep "use UTF-${i} as working-tree-encoding" err.out &&
+
+ cp lebom.utf${i}le.raw lebom.utf${i}le &&
+ test_must_fail git add lebom.utf${i}le 2>err.out &&
+ test_i18ngrep "fatal: BOM is prohibited .* utf-${i}LE" err.out &&
+ test_i18ngrep "use UTF-${i} as working-tree-encoding" err.out
+ '
+
+ test_expect_success "check required UTF-${i} BOM" '
+ test_when_finished "git reset --hard HEAD" &&
+
+ echo "*.utf${i} text working-tree-encoding=utf-${i}" >>.gitattributes &&
+
+ cp nobom.utf${i}be.raw nobom.utf${i} &&
+ test_must_fail git add nobom.utf${i} 2>err.out &&
+ test_i18ngrep "fatal: BOM is required .* utf-${i}" err.out &&
+ test_i18ngrep "use UTF-${i}BE or UTF-${i}LE" err.out &&
+
+ cp nobom.utf${i}le.raw nobom.utf${i} &&
+ test_must_fail git add nobom.utf${i} 2>err.out &&
+ test_i18ngrep "fatal: BOM is required .* utf-${i}" err.out &&
+ test_i18ngrep "use UTF-${i}BE or UTF-${i}LE" err.out
+ '
+
+ test_expect_success "eol conversion for UTF-${i} encoded files on checkout" '
+ test_when_finished "rm -f crlf.utf${i}.raw lf.utf${i}.raw" &&
+ test_when_finished "git reset --hard HEAD^" &&
+
+ cat lf.utf8.raw | iconv -f UTF-8 -t UTF-${i} >lf.utf${i}.raw &&
+ cat crlf.utf8.raw | iconv -f UTF-8 -t UTF-${i} >crlf.utf${i}.raw &&
+ cp crlf.utf${i}.raw eol.utf${i} &&
+
+ cat >expectIndexLF <<-EOF &&
+ i/lf w/-text attr/text eol.utf${i}
+ EOF
+
+ git add eol.utf${i} &&
+ git commit -m eol &&
+
+ # UTF-${i} with CRLF (Windows line endings)
+ rm eol.utf${i} &&
+ git -c core.eol=crlf checkout eol.utf${i} &&
+ test_cmp_bin crlf.utf${i}.raw eol.utf${i} &&
+
+ # Although the file has CRLF in the working tree,
+ # ensure LF in the index
+ git ls-files --eol eol.utf${i} >actual &&
+ test_cmp expectIndexLF actual &&
+
+ # UTF-${i} with LF (Unix line endings)
+ rm eol.utf${i} &&
+ git -c core.eol=lf checkout eol.utf${i} &&
+ test_cmp_bin lf.utf${i}.raw eol.utf${i} &&
+
+ # The file LF in the working tree, ensure LF in the index
+ git ls-files --eol eol.utf${i} >actual &&
+ test_cmp expectIndexLF actual
+ '
+done
+
+test_expect_success 'check unsupported encodings' '
+ test_when_finished "git reset --hard HEAD" &&
+
+ echo "*.set text working-tree-encoding" >.gitattributes &&
+ printf "set" >t.set &&
+ test_must_fail git add t.set 2>err.out &&
+ test_i18ngrep "true/false are no valid working-tree-encodings" err.out &&
+
+ echo "*.unset text -working-tree-encoding" >.gitattributes &&
+ printf "unset" >t.unset &&
+ git add t.unset &&
+
+ echo "*.empty text working-tree-encoding=" >.gitattributes &&
+ printf "empty" >t.empty &&
+ git add t.empty &&
+
+ echo "*.garbage text working-tree-encoding=garbage" >.gitattributes &&
+ printf "garbage" >t.garbage &&
+ test_must_fail git add t.garbage 2>err.out &&
+ test_i18ngrep "failed to encode" err.out
+'
+
+test_expect_success 'error if encoding round trip is not the same during refresh' '
+ BEFORE_STATE=$(git rev-parse HEAD) &&
+ test_when_finished "git reset --hard $BEFORE_STATE" &&
+
+ # Add and commit a UTF-16 file but skip the "working-tree-encoding"
+ # filter. Consequently, the in-repo representation is UTF-16 and not
+ # UTF-8. This simulates a Git version that has no working tree encoding
+ # support.
+ echo "*.utf16le text working-tree-encoding=utf-16le" >.gitattributes &&
+ echo "hallo" >nonsense.utf16le &&
+ TEST_HASH=$(git hash-object --no-filters -w nonsense.utf16le) &&
+ git update-index --add --cacheinfo 100644 $TEST_HASH nonsense.utf16le &&
+ COMMIT=$(git commit-tree -p $(git rev-parse HEAD) -m "plain commit" $(git write-tree)) &&
+ git update-ref refs/heads/master $COMMIT &&
+
+ test_must_fail git checkout HEAD^ 2>err.out &&
+ test_i18ngrep "error: .* overwritten by checkout:" err.out
+'
+
+test_expect_success 'error if encoding garbage is already in Git' '
+ BEFORE_STATE=$(git rev-parse HEAD) &&
+ test_when_finished "git reset --hard $BEFORE_STATE" &&
+
+ # Skip the UTF-16 filter for the added file
+ # This simulates a Git version that has no checkoutEncoding support
+ cp nobom.utf16be.raw nonsense.utf16 &&
+ TEST_HASH=$(git hash-object --no-filters -w nonsense.utf16) &&
+ git update-index --add --cacheinfo 100644 $TEST_HASH nonsense.utf16 &&
+ COMMIT=$(git commit-tree -p $(git rev-parse HEAD) -m "plain commit" $(git write-tree)) &&
+ git update-ref refs/heads/master $COMMIT &&
+
+ git diff 2>err.out &&
+ test_i18ngrep "error: BOM is required" err.out
+'
+
+test_expect_success 'check roundtrip encoding' '
+ test_when_finished "rm -f roundtrip.shift roundtrip.utf16" &&
+ test_when_finished "git reset --hard HEAD" &&
+
+ text="hallo there!\nroundtrip test here!" &&
+ printf "$text" | iconv -f UTF-8 -t SHIFT-JIS >roundtrip.shift &&
+ printf "$text" | iconv -f UTF-8 -t UTF-16 >roundtrip.utf16 &&
+ echo "*.shift text working-tree-encoding=SHIFT-JIS" >>.gitattributes &&
+
+ # SHIFT-JIS encoded files are round-trip checked by default...
+ GIT_TRACE=1 git add .gitattributes roundtrip.shift 2>&1 |
+ grep "Checking roundtrip encoding for SHIFT-JIS" &&
+ git reset &&
+
+ # ... unless we overwrite the Git config!
+ ! GIT_TRACE=1 git -c core.checkRoundtripEncoding=garbage \
+ add .gitattributes roundtrip.shift 2>&1 |
+ grep "Checking roundtrip encoding for SHIFT-JIS" &&
+ git reset &&
+
+ # UTF-16 encoded files should not be round-trip checked by default...
+ ! GIT_TRACE=1 git add roundtrip.utf16 2>&1 |
+ grep "Checking roundtrip encoding for UTF-16" &&
+ git reset &&
+
+ # ... unless we tell Git to check it!
+ GIT_TRACE=1 git -c core.checkRoundtripEncoding="UTF-16, UTF-32" \
+ add roundtrip.utf16 2>&1 |
+ grep "Checking roundtrip encoding for utf-16" &&
+ git reset &&
+
+ # ... unless we tell Git to check it!
+ # (here we also check that the casing of the encoding is irrelevant)
+ GIT_TRACE=1 git -c core.checkRoundtripEncoding="UTF-32, utf-16" \
+ add roundtrip.utf16 2>&1 |
+ grep "Checking roundtrip encoding for utf-16" &&
+ git reset
+'
+
+test_done
diff --git a/t/t0061-run-command.sh b/t/t0061-run-command.sh
index d03149be9f..c887ed5b45 100755
--- a/t/t0061-run-command.sh
+++ b/t/t0061-run-command.sh
@@ -145,7 +145,7 @@ test_trace () {
expect="$1"
shift
GIT_TRACE=1 test-tool run-command "$@" run-command true 2>&1 >/dev/null | \
- sed 's/.* run_command: //' >actual &&
+ sed -e 's/.* run_command: //' -e '/trace: .*/d' >actual &&
echo "$expect true" >expect &&
test_cmp expect actual
}
diff --git a/t/t1300-repo-config.sh b/t/t1300-config.sh
index e95b1e67da..03c223708e 100755
--- a/t/t1300-repo-config.sh
+++ b/t/t1300-config.sh
@@ -108,6 +108,7 @@ bar = foo
[beta]
baz = multiple \
lines
+foo = bar
EOF
test_expect_success 'unset with cont. lines' '
@@ -118,6 +119,7 @@ cat > expect <<\EOF
[alpha]
bar = foo
[beta]
+foo = bar
EOF
test_expect_success 'unset with cont. lines is correct' 'test_cmp expect .git/config'
@@ -740,7 +742,7 @@ test_expect_success bool '
do
git config --bool --get bool.true$i >>result
git config --bool --get bool.false$i >>result
- done &&
+ done &&
test_cmp expect result'
test_expect_success 'invalid bool (--get)' '
@@ -931,6 +933,36 @@ test_expect_success 'get --expiry-date' '
test_must_fail git config --expiry-date date.invalid1
'
+test_expect_success 'get --type=color' '
+ rm .git/config &&
+ git config foo.color "red" &&
+ git config --get --type=color foo.color >actual.raw &&
+ test_decode_color <actual.raw >actual &&
+ echo "<RED>" >expect &&
+ test_cmp expect actual
+'
+
+cat >expect << EOF
+[foo]
+ color = red
+EOF
+
+test_expect_success 'set --type=color' '
+ rm .git/config &&
+ git config --type=color foo.color "red" &&
+ test_cmp expect .git/config
+'
+
+test_expect_success 'get --type=color barfs on non-color' '
+ echo "[foo]bar=not-a-color" >.git/config &&
+ test_must_fail git config --get --type=color foo.bar
+'
+
+test_expect_success 'set --type=color barfs on non-color' '
+ test_must_fail git config --type=color foo.color "not-a-color" 2>error &&
+ test_i18ngrep "cannot parse color" error
+'
+
cat > expect << EOF
[quote]
leading = " test"
@@ -1411,7 +1443,7 @@ test_expect_success 'urlmatch with wildcard' '
'
# good section hygiene
-test_expect_failure 'unsetting the last key in a section removes header' '
+test_expect_success '--unset last key removes section (except if commented)' '
cat >.git/config <<-\EOF &&
# some generic comment on the configuration file itself
# a comment specific to this "section" section.
@@ -1425,13 +1457,86 @@ test_expect_failure 'unsetting the last key in a section removes header' '
cat >expect <<-\EOF &&
# some generic comment on the configuration file itself
+ # a comment specific to this "section" section.
+ [section]
+ # some intervening lines
+ # that should also be dropped
+
+ # please be careful when you update the above variable
EOF
git config --unset section.key &&
- test_cmp expect .git/config
+ test_cmp expect .git/config &&
+
+ cat >.git/config <<-\EOF &&
+ [section]
+ key = value
+ [next-section]
+ EOF
+
+ cat >expect <<-\EOF &&
+ [next-section]
+ EOF
+
+ git config --unset section.key &&
+ test_cmp expect .git/config &&
+
+ q_to_tab >.git/config <<-\EOF &&
+ [one]
+ Qkey = "multiline \
+ QQ# with comment"
+ [two]
+ key = true
+ EOF
+ git config --unset two.key &&
+ ! grep two .git/config &&
+
+ q_to_tab >.git/config <<-\EOF &&
+ [one]
+ Qkey = "multiline \
+ QQ# with comment"
+ [one]
+ key = true
+ EOF
+ git config --unset-all one.key &&
+ test_line_count = 0 .git/config &&
+
+ q_to_tab >.git/config <<-\EOF &&
+ [one]
+ Qkey = true
+ Q# a comment not at the start
+ [two]
+ Qkey = true
+ EOF
+ git config --unset two.key &&
+ grep two .git/config &&
+
+ q_to_tab >.git/config <<-\EOF &&
+ [one]
+ Qkey = not [two "subsection"]
+ [two "subsection"]
+ [two "subsection"]
+ Qkey = true
+ [TWO "subsection"]
+ [one]
+ EOF
+ git config --unset two.subsection.key &&
+ test "not [two subsection]" = "$(git config one.key)" &&
+ test_line_count = 3 .git/config
'
-test_expect_failure 'adding a key into an empty section reuses header' '
+test_expect_success '--unset-all removes section if empty & uncommented' '
+ cat >.git/config <<-\EOF &&
+ [section]
+ key = value1
+ key = value2
+ EOF
+
+ git config --unset-all section.key &&
+ test_line_count = 0 .git/config
+'
+
+test_expect_success 'adding a key into an empty section reuses header' '
cat >.git/config <<-\EOF &&
[section]
EOF
@@ -1611,4 +1716,88 @@ test_expect_success '--local requires a repo' '
test_expect_code 128 nongit git config --local foo.bar
'
+cat >.git/config <<-\EOF &&
+[core]
+foo = true
+number = 10
+big = 1M
+EOF
+
+test_expect_success 'identical modern --type specifiers are allowed' '
+ git config --type=int --type=int core.big >actual &&
+ echo 1048576 >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'identical legacy --type specifiers are allowed' '
+ git config --int --int core.big >actual &&
+ echo 1048576 >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'identical mixed --type specifiers are allowed' '
+ git config --int --type=int core.big >actual &&
+ echo 1048576 >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'non-identical modern --type specifiers are not allowed' '
+ test_must_fail git config --type=int --type=bool core.big 2>error &&
+ test_i18ngrep "only one type at a time" error
+'
+
+test_expect_success 'non-identical legacy --type specifiers are not allowed' '
+ test_must_fail git config --int --bool core.big 2>error &&
+ test_i18ngrep "only one type at a time" error
+'
+
+test_expect_success 'non-identical mixed --type specifiers are not allowed' '
+ test_must_fail git config --type=int --bool core.big 2>error &&
+ test_i18ngrep "only one type at a time" error
+'
+
+test_expect_success '--type allows valid type specifiers' '
+ echo "true" >expect &&
+ git config --type=bool core.foo >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--no-type unsets type specifiers' '
+ echo "10" >expect &&
+ git config --type=bool --no-type core.number >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'unset type specifiers may be reset to conflicting ones' '
+ echo 1048576 >expect &&
+ git config --type=bool --no-type --type=int core.big >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--type rejects unknown specifiers' '
+ test_must_fail git config --type=nonsense core.foo 2>error &&
+ test_i18ngrep "unrecognized --type argument" error
+'
+
+test_expect_success '--replace-all does not invent newlines' '
+ q_to_tab >.git/config <<-\EOF &&
+ [abc]key
+ QkeepSection
+ [xyz]
+ Qkey = 1
+ [abc]
+ Qkey = a
+ EOF
+ q_to_tab >expect <<-\EOF &&
+ [abc]
+ QkeepSection
+ [xyz]
+ Qkey = 1
+ [abc]
+ Qkey = b
+ EOF
+ git config --replace-all abc.key b &&
+ test_cmp .git/config expect
+'
+
test_done
diff --git a/t/t1310-config-default.sh b/t/t1310-config-default.sh
new file mode 100755
index 0000000000..6049d91708
--- /dev/null
+++ b/t/t1310-config-default.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+test_description='Test git config in different settings (with --default)'
+
+. ./test-lib.sh
+
+test_expect_success 'uses --default when entry missing' '
+ echo quux >expect &&
+ git config -f config --default=quux core.foo >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'does not use --default when entry present' '
+ echo bar >expect &&
+ git -c core.foo=bar config --default=baz core.foo >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'canonicalizes --default with appropriate type' '
+ echo true >expect &&
+ git config -f config --default=yes --bool core.foo >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'dies when --default cannot be parsed' '
+ test_must_fail git config -f config --type=expiry-date --default=x --get \
+ not.a.section 2>error &&
+ test_i18ngrep "failed to format default config value" error
+'
+
+test_expect_success 'does not allow --default without --get' '
+ test_must_fail git config --default=quux --unset a.section >output 2>&1 &&
+ test_i18ngrep "\-\-default is only applicable to" output
+'
+
+test_done
diff --git a/t/t1501-work-tree.sh b/t/t1501-work-tree.sh
index 02cf2013fc..9c0bc65250 100755
--- a/t/t1501-work-tree.sh
+++ b/t/t1501-work-tree.sh
@@ -431,4 +431,16 @@ test_expect_success 'error out gracefully on invalid $GIT_WORK_TREE' '
)
'
+test_expect_success 'refs work with relative gitdir and work tree' '
+ git init relative &&
+ git -C relative commit --allow-empty -m one &&
+ git -C relative commit --allow-empty -m two &&
+
+ GIT_DIR=relative/.git GIT_WORK_TREE=relative git reset HEAD^ &&
+
+ git -C relative log -1 --format=%s >actual &&
+ echo one >expect &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t1510-repo-setup.sh b/t/t1510-repo-setup.sh
index e6854b828e..972bd9c785 100755
--- a/t/t1510-repo-setup.sh
+++ b/t/t1510-repo-setup.sh
@@ -238,7 +238,6 @@ test_expect_success '#0: nonbare repo, no explicit configuration' '
'
test_expect_success '#1: GIT_WORK_TREE without explicit GIT_DIR is accepted' '
- mkdir -p wt &&
try_repo 1 "$here" unset unset "" unset \
"$here/1/.git" "$here" "$here" 1/ \
"$here/1/.git" "$here" "$here" 1/sub/ 2>message &&
diff --git a/t/t2022-checkout-paths.sh b/t/t2022-checkout-paths.sh
index e74d58b9e1..fc3eb43b89 100755
--- a/t/t2022-checkout-paths.sh
+++ b/t/t2022-checkout-paths.sh
@@ -73,8 +73,8 @@ test_expect_success 'do not touch files that are already up-to-date' '
git checkout HEAD -- file1 file2 &&
echo one >expect &&
test_cmp expect file1 &&
- echo "1000000000 file2" >expect &&
- test-tool chmtime -v +0 file2 >actual &&
+ echo "1000000000" >expect &&
+ test-tool chmtime --get file2 >actual &&
test_cmp expect actual
'
diff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh
index d0d2e4f7ec..2240498924 100755
--- a/t/t2025-worktree-add.sh
+++ b/t/t2025-worktree-add.sh
@@ -198,13 +198,25 @@ test_expect_success '"add" with <branch> omitted' '
test_cmp_rev HEAD bat
'
-test_expect_success '"add" auto-vivify does not clobber existing branch' '
- test_commit c1 &&
- test_commit c2 &&
- git branch precious HEAD~1 &&
- test_must_fail git worktree add precious &&
- test_cmp_rev HEAD~1 precious &&
- test_path_is_missing precious
+test_expect_success '"add" checks out existing branch of dwimd name' '
+ git branch dwim HEAD~1 &&
+ git worktree add dwim &&
+ test_cmp_rev HEAD~1 dwim &&
+ (
+ cd dwim &&
+ test_cmp_rev HEAD dwim
+ )
+'
+
+test_expect_success '"add <path>" dwim fails with checked out branch' '
+ git checkout -b test-branch &&
+ test_must_fail git worktree add test-branch &&
+ test_path_is_missing test-branch
+'
+
+test_expect_success '"add --force" with existing dwimd name doesnt die' '
+ git checkout test-branch &&
+ git worktree add --force test-branch
'
test_expect_success '"add" no auto-vivify with --detach and <branch> omitted' '
diff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh
index 5d5b3632ba..5f7d45b7b7 100755
--- a/t/t2028-worktree-move.sh
+++ b/t/t2028-worktree-move.sh
@@ -72,12 +72,11 @@ test_expect_success 'move locked worktree' '
'
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 &&
+ grep "^worktree.*/destination$" out &&
+ ! grep "^worktree.*/source$" out &&
git -C destination log --format=%s >actual2 &&
echo init >expected2 &&
test_cmp expected2 actual2
@@ -93,7 +92,7 @@ test_expect_success 'move worktree to another 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 &&
+ grep "^worktree.*/some-dir/destination$" out &&
git -C some-dir/destination log --format=%s >actual2 &&
echo init >expected2 &&
test_cmp expected2 actual2
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index 6c0b7ea4ad..c0ef946811 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -6,6 +6,7 @@
test_description='git branch assorted tests'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-rebase.sh
test_expect_success 'prepare a trivial repository' '
echo Hello >A &&
@@ -1246,6 +1247,29 @@ test_expect_success '--merged is incompatible with --no-merged' '
test_must_fail git branch --merged HEAD --no-merged HEAD
'
+test_expect_success '--list during rebase' '
+ test_when_finished "reset_rebase" &&
+ git checkout master &&
+ FAKE_LINES="1 edit 2" &&
+ export FAKE_LINES &&
+ set_fake_editor &&
+ git rebase -i HEAD~2 &&
+ git branch --list >actual &&
+ test_i18ngrep "rebasing master" actual
+'
+
+test_expect_success '--list during rebase from detached HEAD' '
+ test_when_finished "reset_rebase && git checkout master" &&
+ git checkout master^0 &&
+ oid=$(git rev-parse --short HEAD) &&
+ FAKE_LINES="1 edit 2" &&
+ export FAKE_LINES &&
+ set_fake_editor &&
+ git rebase -i HEAD~2 &&
+ git branch --list >actual &&
+ test_i18ngrep "rebasing detached HEAD $oid" actual
+'
+
test_expect_success 'tracking with unexpected .fetch refspec' '
rm -rf a b c d &&
git init a &&
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index 756de26c19..59c766540e 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -717,7 +717,7 @@ test_expect_success 'avoid unnecessary reset' '
set_fake_editor &&
git rebase -i HEAD~4 &&
test $HEAD = $(git rev-parse HEAD) &&
- MTIME=$(test-tool chmtime -v +0 file3 | sed 's/[^0-9].*$//') &&
+ MTIME=$(test-tool chmtime --get file3) &&
test 123456789 = $MTIME
'
diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh
index 9214d0bb51..03bf1b8a3b 100755
--- a/t/t3418-rebase-continue.sh
+++ b/t/t3418-rebase-continue.sh
@@ -88,6 +88,55 @@ test_expect_success 'rebase passes merge strategy options correctly' '
git rebase --continue
'
+test_expect_success '--skip after failed fixup cleans commit message' '
+ test_when_finished "test_might_fail git rebase --abort" &&
+ git checkout -b with-conflicting-fixup &&
+ test_commit wants-fixup &&
+ test_commit "fixup! wants-fixup" wants-fixup.t 1 wants-fixup-1 &&
+ test_commit "fixup! wants-fixup" wants-fixup.t 2 wants-fixup-2 &&
+ test_commit "fixup! wants-fixup" wants-fixup.t 3 wants-fixup-3 &&
+ test_must_fail env FAKE_LINES="1 fixup 2 squash 4" \
+ git rebase -i HEAD~4 &&
+
+ : now there is a conflict, and comments in the commit message &&
+ git show HEAD >out &&
+ grep "fixup! wants-fixup" out &&
+
+ : skip and continue &&
+ echo "cp \"\$1\" .git/copy.txt" | write_script copy-editor.sh &&
+ (test_set_editor "$PWD/copy-editor.sh" && git rebase --skip) &&
+
+ : the user should not have had to edit the commit message &&
+ test_path_is_missing .git/copy.txt &&
+
+ : now the comments in the commit message should have been cleaned up &&
+ git show HEAD >out &&
+ ! grep "fixup! wants-fixup" out &&
+
+ : now, let us ensure that "squash" is handled correctly &&
+ git reset --hard wants-fixup-3 &&
+ test_must_fail env FAKE_LINES="1 squash 4 squash 2 squash 4" \
+ git rebase -i HEAD~4 &&
+
+ : the first squash failed, but there are two more in the chain &&
+ (test_set_editor "$PWD/copy-editor.sh" &&
+ test_must_fail git rebase --skip) &&
+
+ : not the final squash, no need to edit the commit message &&
+ test_path_is_missing .git/copy.txt &&
+
+ : The first squash was skipped, therefore: &&
+ git show HEAD >out &&
+ test_i18ngrep "# This is a combination of 2 commits" out &&
+
+ (test_set_editor "$PWD/copy-editor.sh" && git rebase --skip) &&
+ git show HEAD >out &&
+ test_i18ngrep ! "# This is a combination" out &&
+
+ : Final squash failed, but there was still a squash &&
+ test_i18ngrep "# This is a combination of 2 commits" .git/copy.txt
+'
+
test_expect_success 'setup rerere database' '
rm -fr .git/rebase-* &&
git reset --hard commit-new-file-F3-on-topic-branch &&
diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh
index 68fe2003ef..b078f93046 100755
--- a/t/t3421-rebase-topology-linear.sh
+++ b/t/t3421-rebase-topology-linear.sh
@@ -199,7 +199,7 @@ test_run_rebase () {
"
}
test_run_rebase success ''
-test_run_rebase failure -m
+test_run_rebase success -m
test_run_rebase success -i
test_run_rebase failure -p
@@ -214,8 +214,8 @@ test_run_rebase () {
"
}
test_run_rebase success ''
-test_run_rebase failure -m
-test_run_rebase failure -i
+test_run_rebase success -m
+test_run_rebase success -i
test_run_rebase failure -p
# m
diff --git a/t/t3428-rebase-signoff.sh b/t/t3428-rebase-signoff.sh
index 2afb564701..f6993b7e14 100755
--- a/t/t3428-rebase-signoff.sh
+++ b/t/t3428-rebase-signoff.sh
@@ -12,6 +12,13 @@ cat >file <<EOF
a
EOF
+# Expected commit message for initial commit after rebase --signoff
+cat >expected-initial-signed <<EOF
+Initial empty commit
+
+Signed-off-by: $(git var GIT_COMMITTER_IDENT | sed -e "s/>.*/>/")
+EOF
+
# Expected commit message after rebase --signoff
cat >expected-signed <<EOF
first
@@ -43,4 +50,35 @@ test_expect_success 'rebase --no-signoff does not add a sign-off line' '
test_cmp expected-unsigned actual
'
+test_expect_success 'rebase --exec --signoff adds a sign-off line' '
+ test_when_finished "rm exec" &&
+ git commit --amend -m "first" &&
+ git rebase --exec "touch exec" --signoff HEAD^ &&
+ test_path_is_file exec &&
+ git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
+ test_cmp expected-signed actual
+'
+
+test_expect_success 'rebase --root --signoff adds a sign-off line' '
+ git commit --amend -m "first" &&
+ git rebase --root --keep-empty --signoff &&
+ git cat-file commit HEAD^ | sed -e "1,/^\$/d" >actual &&
+ test_cmp expected-initial-signed actual &&
+ git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
+ test_cmp expected-signed actual
+'
+
+test_expect_success 'rebase -i --signoff fails' '
+ git commit --amend -m "first" &&
+ git rebase -i --signoff HEAD^ &&
+ git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
+ test_cmp expected-signed actual
+'
+
+test_expect_success 'rebase -m --signoff fails' '
+ git commit --amend -m "first" &&
+ git rebase -m --signoff HEAD^ &&
+ git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
+ test_cmp expected-signed actual
+'
test_done
diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh
index ccbc118514..d1c68af8c5 100755
--- a/t/t3501-revert-cherry-pick.sh
+++ b/t/t3501-revert-cherry-pick.sh
@@ -141,7 +141,7 @@ test_expect_success 'cherry-pick "-" works with arguments' '
test_cmp expect actual
'
-test_expect_failure 'cherry-pick works with dirty renamed file' '
+test_expect_success 'cherry-pick works with dirty renamed file' '
test_commit to-rename &&
git checkout -b unrelated &&
test_commit unrelated &&
@@ -150,9 +150,8 @@ test_expect_failure 'cherry-pick works with dirty renamed file' '
test_tick &&
git commit -m renamed &&
echo modified >renamed &&
- test_must_fail git cherry-pick refs/heads/unrelated >out &&
- test_i18ngrep "Refusing to lose dirty file at renamed" out &&
- test $(git rev-parse :0:renamed) = $(git rev-parse HEAD^:to-rename.t) &&
+ git cherry-pick refs/heads/unrelated >out &&
+ test $(git rev-parse :0:renamed) = $(git rev-parse HEAD~2:to-rename.t) &&
grep -q "^modified$" renamed
'
diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh
index 9f93445f1e..21b4f194a2 100755
--- a/t/t3510-cherry-pick-sequence.sh
+++ b/t/t3510-cherry-pick-sequence.sh
@@ -247,9 +247,9 @@ test_expect_success '--abort after last commit in sequence' '
test_expect_success 'cherry-pick does not implicitly stomp an existing operation' '
pristine_detach initial &&
test_expect_code 1 git cherry-pick base..anotherpick &&
- test-tool chmtime -v +0 .git/sequencer >expect &&
+ test-tool chmtime --get .git/sequencer >expect &&
test_expect_code 128 git cherry-pick unrelatedpick &&
- test-tool chmtime -v +0 .git/sequencer >actual &&
+ test-tool chmtime --get .git/sequencer >actual &&
test_cmp expect actual
'
diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh
index deafaa3e07..eaf18c81cb 100755
--- a/t/t4200-rerere.sh
+++ b/t/t4200-rerere.sh
@@ -166,7 +166,7 @@ test_expect_success 'first postimage wins' '
git commit -q -a -m "prefer first over second" &&
test -f $rr/postimage &&
- oldmtimepost=$(test-tool chmtime -v -60 $rr/postimage | cut -f 1) &&
+ oldmtimepost=$(test-tool chmtime --get -60 $rr/postimage) &&
git checkout -b third master &&
git show second^:a1 | sed "s/To die: t/To die! T/" >a1 &&
@@ -179,7 +179,7 @@ test_expect_success 'first postimage wins' '
'
test_expect_success 'rerere updates postimage timestamp' '
- newmtimepost=$(test-tool chmtime -v +0 $rr/postimage | cut -f 1) &&
+ newmtimepost=$(test-tool chmtime --get $rr/postimage) &&
test $oldmtimepost -lt $newmtimepost
'
@@ -512,7 +512,7 @@ test_expect_success 'multiple identical conflicts' '
count_pre_post 2 0 &&
# Pretend that the conflicts were made quite some time ago
- find .git/rr-cache/ -type f | xargs test-tool chmtime -172800 &&
+ test-tool chmtime -172800 $(find .git/rr-cache/ -type f) &&
# Unresolved entries have not expired yet
git -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc &&
@@ -568,7 +568,7 @@ test_expect_success 'multiple identical conflicts' '
git rerere &&
# Pretend that the resolutions are old again
- find .git/rr-cache/ -type f | xargs test-tool chmtime -172800 &&
+ test-tool chmtime -172800 $(find .git/rr-cache/ -type f) &&
# Resolved entries have not expired yet
git -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc &&
diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh
index af4d9b8876..2a97b27b0a 100755
--- a/t/t5000-tar-tree.sh
+++ b/t/t5000-tar-tree.sh
@@ -192,7 +192,7 @@ test_expect_success \
'validate file modification time' \
'mkdir extract &&
"$TAR" xf b.tar -C extract a/a &&
- test-tool chmtime -v +0 extract/a/a |cut -f 1 >b.mtime &&
+ test-tool chmtime --get extract/a/a >b.mtime &&
echo "1117231200" >expected.mtime &&
test_cmp expected.mtime b.mtime'
diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh
index f0f6e2a5f3..f20f03c103 100755
--- a/t/t5304-prune.sh
+++ b/t/t5304-prune.sh
@@ -320,4 +320,14 @@ test_expect_success 'prune: handle HEAD reflog in multiple worktrees' '
test_cmp expected actual
'
+test_expect_success 'prune: handle expire option correctly' '
+ test_must_fail git prune --expire 2>error &&
+ test_i18ngrep "requires a value" error &&
+
+ test_must_fail git prune --expire=nyah 2>error &&
+ test_i18ngrep "malformed expiration" error &&
+
+ git prune --no-expire
+'
+
test_done
diff --git a/t/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh
index f6d600fd82..423c0a475f 100755
--- a/t/t5310-pack-bitmaps.sh
+++ b/t/t5310-pack-bitmaps.sh
@@ -264,9 +264,9 @@ test_expect_success 'pack with missing parent' '
'
test_expect_success JGIT 'we can read jgit bitmaps' '
- git clone . compat-jgit &&
+ git clone --bare . compat-jgit.git &&
(
- cd compat-jgit &&
+ cd compat-jgit.git &&
rm -f .git/objects/pack/*.bitmap &&
jgit gc &&
git rev-list --test-bitmap HEAD
@@ -274,9 +274,9 @@ test_expect_success JGIT 'we can read jgit bitmaps' '
'
test_expect_success JGIT 'jgit can read our bitmaps' '
- git clone . compat-us &&
+ git clone --bare . compat-us.git &&
(
- cd compat-us &&
+ cd compat-us.git &&
git repack -adb &&
# jgit gc will barf if it does not like our bitmaps
jgit gc
diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh
new file mode 100755
index 0000000000..a380419b65
--- /dev/null
+++ b/t/t5318-commit-graph.sh
@@ -0,0 +1,224 @@
+#!/bin/sh
+
+test_description='commit graph'
+. ./test-lib.sh
+
+test_expect_success 'setup full repo' '
+ mkdir full &&
+ cd "$TRASH_DIRECTORY/full" &&
+ git init &&
+ git config core.commitGraph true &&
+ objdir=".git/objects"
+'
+
+test_expect_success 'write graph with no packs' '
+ cd "$TRASH_DIRECTORY/full" &&
+ git commit-graph write --object-dir . &&
+ test_path_is_file info/commit-graph
+'
+
+test_expect_success 'create commits and repack' '
+ cd "$TRASH_DIRECTORY/full" &&
+ for i in $(test_seq 3)
+ do
+ test_commit $i &&
+ git branch commits/$i
+ done &&
+ git repack
+'
+
+graph_git_two_modes() {
+ git -c core.graph=true $1 >output
+ git -c core.graph=false $1 >expect
+ test_cmp output expect
+}
+
+graph_git_behavior() {
+ MSG=$1
+ DIR=$2
+ BRANCH=$3
+ COMPARE=$4
+ test_expect_success "check normal git operations: $MSG" '
+ cd "$TRASH_DIRECTORY/$DIR" &&
+ graph_git_two_modes "log --oneline $BRANCH" &&
+ graph_git_two_modes "log --topo-order $BRANCH" &&
+ graph_git_two_modes "log --graph $COMPARE..$BRANCH" &&
+ graph_git_two_modes "branch -vv" &&
+ graph_git_two_modes "merge-base -a $BRANCH $COMPARE"
+ '
+}
+
+graph_git_behavior 'no graph' full commits/3 commits/1
+
+graph_read_expect() {
+ OPTIONAL=""
+ NUM_CHUNKS=3
+ if test ! -z $2
+ then
+ OPTIONAL=" $2"
+ NUM_CHUNKS=$((3 + $(echo "$2" | wc -w)))
+ fi
+ cat >expect <<- EOF
+ header: 43475048 1 1 $NUM_CHUNKS 0
+ num_commits: $1
+ chunks: oid_fanout oid_lookup commit_metadata$OPTIONAL
+ EOF
+ git commit-graph read >output &&
+ test_cmp expect output
+}
+
+test_expect_success 'write graph' '
+ cd "$TRASH_DIRECTORY/full" &&
+ graph1=$(git commit-graph write) &&
+ test_path_is_file $objdir/info/commit-graph &&
+ graph_read_expect "3"
+'
+
+graph_git_behavior 'graph exists' full commits/3 commits/1
+
+test_expect_success 'Add more commits' '
+ cd "$TRASH_DIRECTORY/full" &&
+ git reset --hard commits/1 &&
+ for i in $(test_seq 4 5)
+ do
+ test_commit $i &&
+ git branch commits/$i
+ done &&
+ git reset --hard commits/2 &&
+ for i in $(test_seq 6 7)
+ do
+ test_commit $i &&
+ git branch commits/$i
+ done &&
+ git reset --hard commits/2 &&
+ git merge commits/4 &&
+ git branch merge/1 &&
+ git reset --hard commits/4 &&
+ git merge commits/6 &&
+ git branch merge/2 &&
+ git reset --hard commits/3 &&
+ git merge commits/5 commits/7 &&
+ git branch merge/3 &&
+ git repack
+'
+
+# Current graph structure:
+#
+# __M3___
+# / | \
+# 3 M1 5 M2 7
+# |/ \|/ \|
+# 2 4 6
+# |___/____/
+# 1
+
+test_expect_success 'write graph with merges' '
+ cd "$TRASH_DIRECTORY/full" &&
+ git commit-graph write &&
+ test_path_is_file $objdir/info/commit-graph &&
+ graph_read_expect "10" "large_edges"
+'
+
+graph_git_behavior 'merge 1 vs 2' full merge/1 merge/2
+graph_git_behavior 'merge 1 vs 3' full merge/1 merge/3
+graph_git_behavior 'merge 2 vs 3' full merge/2 merge/3
+
+test_expect_success 'Add one more commit' '
+ cd "$TRASH_DIRECTORY/full" &&
+ test_commit 8 &&
+ git branch commits/8 &&
+ ls $objdir/pack | grep idx >existing-idx &&
+ git repack &&
+ ls $objdir/pack| grep idx | grep -v --file=existing-idx >new-idx
+'
+
+# Current graph structure:
+#
+# 8
+# |
+# __M3___
+# / | \
+# 3 M1 5 M2 7
+# |/ \|/ \|
+# 2 4 6
+# |___/____/
+# 1
+
+graph_git_behavior 'mixed mode, commit 8 vs merge 1' full commits/8 merge/1
+graph_git_behavior 'mixed mode, commit 8 vs merge 2' full commits/8 merge/2
+
+test_expect_success 'write graph with new commit' '
+ cd "$TRASH_DIRECTORY/full" &&
+ git commit-graph write &&
+ test_path_is_file $objdir/info/commit-graph &&
+ graph_read_expect "11" "large_edges"
+'
+
+graph_git_behavior 'full graph, commit 8 vs merge 1' full commits/8 merge/1
+graph_git_behavior 'full graph, commit 8 vs merge 2' full commits/8 merge/2
+
+test_expect_success 'write graph with nothing new' '
+ cd "$TRASH_DIRECTORY/full" &&
+ git commit-graph write &&
+ test_path_is_file $objdir/info/commit-graph &&
+ graph_read_expect "11" "large_edges"
+'
+
+graph_git_behavior 'cleared graph, commit 8 vs merge 1' full commits/8 merge/1
+graph_git_behavior 'cleared graph, commit 8 vs merge 2' full commits/8 merge/2
+
+test_expect_success 'build graph from latest pack with closure' '
+ cd "$TRASH_DIRECTORY/full" &&
+ cat new-idx | git commit-graph write --stdin-packs &&
+ test_path_is_file $objdir/info/commit-graph &&
+ graph_read_expect "9" "large_edges"
+'
+
+graph_git_behavior 'graph from pack, commit 8 vs merge 1' full commits/8 merge/1
+graph_git_behavior 'graph from pack, commit 8 vs merge 2' full commits/8 merge/2
+
+test_expect_success 'build graph from commits with closure' '
+ cd "$TRASH_DIRECTORY/full" &&
+ git tag -a -m "merge" tag/merge merge/2 &&
+ git rev-parse tag/merge >commits-in &&
+ git rev-parse merge/1 >>commits-in &&
+ cat commits-in | git commit-graph write --stdin-commits &&
+ test_path_is_file $objdir/info/commit-graph &&
+ graph_read_expect "6"
+'
+
+graph_git_behavior 'graph from commits, commit 8 vs merge 1' full commits/8 merge/1
+graph_git_behavior 'graph from commits, commit 8 vs merge 2' full commits/8 merge/2
+
+test_expect_success 'build graph from commits with append' '
+ cd "$TRASH_DIRECTORY/full" &&
+ git rev-parse merge/3 | git commit-graph write --stdin-commits --append &&
+ test_path_is_file $objdir/info/commit-graph &&
+ graph_read_expect "10" "large_edges"
+'
+
+graph_git_behavior 'append graph, commit 8 vs merge 1' full commits/8 merge/1
+graph_git_behavior 'append graph, commit 8 vs merge 2' full commits/8 merge/2
+
+test_expect_success 'setup bare repo' '
+ cd "$TRASH_DIRECTORY" &&
+ git clone --bare --no-local full bare &&
+ cd bare &&
+ git config core.commitGraph true &&
+ baredir="./objects"
+'
+
+graph_git_behavior 'bare repo, commit 8 vs merge 1' bare commits/8 merge/1
+graph_git_behavior 'bare repo, commit 8 vs merge 2' bare commits/8 merge/2
+
+test_expect_success 'write graph in bare repo' '
+ cd "$TRASH_DIRECTORY/bare" &&
+ git commit-graph write &&
+ test_path_is_file $baredir/info/commit-graph &&
+ graph_read_expect "11" "large_edges"
+'
+
+graph_git_behavior 'bare repo with graph, commit 8 vs merge 1' bare commits/8 merge/1
+graph_git_behavior 'bare repo with graph, commit 8 vs merge 2' bare commits/8 merge/2
+
+test_done
diff --git a/t/t5404-tracking-branches.sh b/t/t5404-tracking-branches.sh
index 2b8c0bac7d..2762f420bc 100755
--- a/t/t5404-tracking-branches.sh
+++ b/t/t5404-tracking-branches.sh
@@ -56,7 +56,7 @@ test_expect_success 'deleted branches have their tracking branches removed' '
test_expect_success 'already deleted tracking branches ignored' '
git branch -d -r origin/b3 &&
git push origin :b3 >output 2>&1 &&
- ! grep error output
+ ! grep "^error: " output
'
test_done
diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh
index 02106c9226..6a949484d0 100755
--- a/t/t5512-ls-remote.sh
+++ b/t/t5512-ls-remote.sh
@@ -10,6 +10,9 @@ test_expect_success setup '
test_tick &&
git commit -m initial &&
git tag mark &&
+ git tag mark1.1 &&
+ git tag mark1.2 &&
+ git tag mark1.10 &&
git show-ref --tags -d | sed -e "s/ / /" >expected.tag &&
(
echo "$(git rev-parse HEAD) HEAD"
@@ -39,6 +42,39 @@ test_expect_success 'ls-remote self' '
test_cmp expected.all actual
'
+test_expect_success 'ls-remote --sort="version:refname" --tags self' '
+ cat >expect <<-EOF &&
+ $(git rev-parse mark) refs/tags/mark
+ $(git rev-parse mark1.1) refs/tags/mark1.1
+ $(git rev-parse mark1.2) refs/tags/mark1.2
+ $(git rev-parse mark1.10) refs/tags/mark1.10
+ EOF
+ git ls-remote --sort="version:refname" --tags self >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'ls-remote --sort="-version:refname" --tags self' '
+ cat >expect <<-EOF &&
+ $(git rev-parse mark1.10) refs/tags/mark1.10
+ $(git rev-parse mark1.2) refs/tags/mark1.2
+ $(git rev-parse mark1.1) refs/tags/mark1.1
+ $(git rev-parse mark) refs/tags/mark
+ EOF
+ git ls-remote --sort="-version:refname" --tags self >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'ls-remote --sort="-refname" --tags self' '
+ cat >expect <<-EOF &&
+ $(git rev-parse mark1.2) refs/tags/mark1.2
+ $(git rev-parse mark1.10) refs/tags/mark1.10
+ $(git rev-parse mark1.1) refs/tags/mark1.1
+ $(git rev-parse mark) refs/tags/mark
+ EOF
+ git ls-remote --sort="-refname" --tags self >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'dies when no remote specified and no default remotes found' '
test_must_fail git ls-remote
'
@@ -131,7 +167,7 @@ test_expect_success 'Report no-match with --exit-code' '
test_expect_success 'Report match with --exit-code' '
git ls-remote --exit-code other.git "refs/tags/*" >actual &&
- git ls-remote . tags/mark >expect &&
+ git ls-remote . tags/mark* >expect &&
test_cmp expect actual
'
@@ -171,13 +207,17 @@ test_expect_success 'overrides work between mixed transfer/upload-pack hideRefs'
'
test_expect_success 'ls-remote --symref' '
- cat >expect <<-\EOF &&
+ git fetch origin &&
+ cat >expect <<-EOF &&
ref: refs/heads/master HEAD
- 1bd44cb9d13204b0fe1958db0082f5028a16eb3a HEAD
- 1bd44cb9d13204b0fe1958db0082f5028a16eb3a refs/heads/master
- 1bd44cb9d13204b0fe1958db0082f5028a16eb3a refs/remotes/origin/HEAD
- 1bd44cb9d13204b0fe1958db0082f5028a16eb3a refs/remotes/origin/master
- 1bd44cb9d13204b0fe1958db0082f5028a16eb3a refs/tags/mark
+ $(git rev-parse HEAD) HEAD
+ $(git rev-parse refs/heads/master) refs/heads/master
+ $(git rev-parse HEAD) refs/remotes/origin/HEAD
+ $(git rev-parse refs/remotes/origin/master) refs/remotes/origin/master
+ $(git rev-parse refs/tags/mark) refs/tags/mark
+ $(git rev-parse refs/tags/mark1.1) refs/tags/mark1.1
+ $(git rev-parse refs/tags/mark1.10) refs/tags/mark1.10
+ $(git rev-parse refs/tags/mark1.2) refs/tags/mark1.2
EOF
git ls-remote --symref >actual &&
test_cmp expect actual
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 82239138d5..3e8940eee5 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -94,6 +94,9 @@ mk_child() {
}
check_push_result () {
+ test $# -ge 3 ||
+ error "bug in the test script: check_push_result requires at least 3 parameters"
+
repo_name="$1"
shift
@@ -553,10 +556,7 @@ test_expect_success 'branch.*.pushremote config order is irrelevant' '
test_expect_success 'push with dry-run' '
mk_test testrepo heads/master &&
- (
- cd testrepo &&
- old_commit=$(git show-ref -s --verify refs/heads/master)
- ) &&
+ old_commit=$(git -C testrepo show-ref -s --verify refs/heads/master) &&
git push --dry-run testrepo : &&
check_push_result testrepo $old_commit heads/master
'
@@ -612,7 +612,7 @@ test_expect_success 'push does not update local refs on failure' '
chmod +x testrepo/.git/hooks/pre-receive &&
(
cd child &&
- git pull .. master
+ git pull .. master &&
test_must_fail git push &&
test $(git rev-parse master) != \
$(git rev-parse remotes/origin/master)
diff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh
index 21340e89c9..a2af693068 100755
--- a/t/t5541-http-push-smart.sh
+++ b/t/t5541-http-push-smart.sh
@@ -377,5 +377,17 @@ test_expect_success 'push status output scrubs password' '
grep "^To $HTTPD_URL/smart/test_repo.git" status
'
+test_expect_success 'colorize errors/hints' '
+ cd "$ROOT_PATH"/test_repo_clone &&
+ test_must_fail git -c color.transport=always -c color.advice=always \
+ -c color.push=always \
+ push origin origin/master^:master 2>act &&
+ test_decode_color <act >decoded &&
+ test_i18ngrep "<RED>.*rejected.*<RESET>" decoded &&
+ test_i18ngrep "<RED>error: failed to push some refs" decoded &&
+ test_i18ngrep "<YELLOW>hint: " decoded &&
+ test_i18ngrep ! "^hint: " decoded
+'
+
stop_httpd
test_done
diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh
index 8552184e74..6d7d88ccc9 100755
--- a/t/t5550-http-fetch-dumb.sh
+++ b/t/t5550-http-fetch-dumb.sh
@@ -169,6 +169,17 @@ test_expect_success 'fetch changes via manual http-fetch' '
test_cmp file clone2/file
'
+test_expect_success 'manual http-fetch without -a works just as well' '
+ cp -R clone-tmpl clone3 &&
+
+ HEAD=$(git rev-parse --verify HEAD) &&
+ (cd clone3 &&
+ git http-fetch -w heads/master-new $HEAD $(git config remote.origin.url) &&
+ git checkout master-new &&
+ test $HEAD = $(git rev-parse --verify HEAD)) &&
+ test_cmp file clone3/file
+'
+
test_expect_success 'http remote detects correct HEAD' '
git push public master:other &&
(cd clone &&
diff --git a/t/t5561-http-backend.sh b/t/t5561-http-backend.sh
index 90e0d6f0fe..84a955770a 100755
--- a/t/t5561-http-backend.sh
+++ b/t/t5561-http-backend.sh
@@ -3,10 +3,16 @@
test_description='test git-http-backend'
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-httpd.sh
+
+if ! test_have_prereq CURL; then
+ skip_all='skipping raw http-backend tests, curl not available'
+ test_done
+fi
+
start_httpd
GET() {
- curl --include "$HTTPD_URL/$SMART/repo.git/$1" >out 2>/dev/null &&
+ curl --include "$HTTPD_URL/$SMART/repo.git/$1" >out &&
tr '\015' Q <out |
sed '
s/Q$//
@@ -19,7 +25,7 @@ GET() {
POST() {
curl --include --data "$2" \
--header "Content-Type: application/x-$1-request" \
- "$HTTPD_URL/smart/repo.git/$1" >out 2>/dev/null &&
+ "$HTTPD_URL/smart/repo.git/$1" >out &&
tr '\015' Q <out |
sed '
s/Q$//
diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh
new file mode 100755
index 0000000000..011a5796db
--- /dev/null
+++ b/t/t5701-git-serve.sh
@@ -0,0 +1,197 @@
+#!/bin/sh
+
+test_description='test git-serve and server commands'
+
+. ./test-lib.sh
+
+test_expect_success 'test capability advertisement' '
+ cat >expect <<-EOF &&
+ version 2
+ agent=git/$(git version | cut -d" " -f3)
+ ls-refs
+ fetch=shallow
+ server-option
+ 0000
+ EOF
+
+ git serve --advertise-capabilities >out &&
+ test-pkt-line unpack <out >actual &&
+ test_cmp actual expect
+'
+
+test_expect_success 'stateless-rpc flag does not list capabilities' '
+ # Empty request
+ test-pkt-line pack >in <<-EOF &&
+ 0000
+ EOF
+ git serve --stateless-rpc >out <in &&
+ test_must_be_empty out &&
+
+ # EOF
+ git serve --stateless-rpc >out &&
+ test_must_be_empty out
+'
+
+test_expect_success 'request invalid capability' '
+ test-pkt-line pack >in <<-EOF &&
+ foobar
+ 0000
+ EOF
+ test_must_fail git serve --stateless-rpc 2>err <in &&
+ test_i18ngrep "unknown capability" err
+'
+
+test_expect_success 'request with no command' '
+ test-pkt-line pack >in <<-EOF &&
+ agent=git/test
+ 0000
+ EOF
+ test_must_fail git serve --stateless-rpc 2>err <in &&
+ test_i18ngrep "no command requested" err
+'
+
+test_expect_success 'request invalid command' '
+ test-pkt-line pack >in <<-EOF &&
+ command=foo
+ agent=git/test
+ 0000
+ EOF
+ test_must_fail git serve --stateless-rpc 2>err <in &&
+ test_i18ngrep "invalid command" err
+'
+
+# Test the basics of ls-refs
+#
+test_expect_success 'setup some refs and tags' '
+ test_commit one &&
+ git branch dev master &&
+ test_commit two &&
+ git symbolic-ref refs/heads/release refs/heads/master &&
+ git tag -a -m "annotated tag" annotated-tag
+'
+
+test_expect_success 'basics of ls-refs' '
+ test-pkt-line pack >in <<-EOF &&
+ command=ls-refs
+ 0000
+ EOF
+
+ cat >expect <<-EOF &&
+ $(git rev-parse HEAD) HEAD
+ $(git rev-parse refs/heads/dev) refs/heads/dev
+ $(git rev-parse refs/heads/master) refs/heads/master
+ $(git rev-parse refs/heads/release) refs/heads/release
+ $(git rev-parse refs/tags/annotated-tag) refs/tags/annotated-tag
+ $(git rev-parse refs/tags/one) refs/tags/one
+ $(git rev-parse refs/tags/two) refs/tags/two
+ 0000
+ EOF
+
+ git serve --stateless-rpc <in >out &&
+ test-pkt-line unpack <out >actual &&
+ test_cmp actual expect
+'
+
+test_expect_success 'basic ref-prefixes' '
+ test-pkt-line pack >in <<-EOF &&
+ command=ls-refs
+ 0001
+ ref-prefix refs/heads/master
+ ref-prefix refs/tags/one
+ 0000
+ EOF
+
+ cat >expect <<-EOF &&
+ $(git rev-parse refs/heads/master) refs/heads/master
+ $(git rev-parse refs/tags/one) refs/tags/one
+ 0000
+ EOF
+
+ git serve --stateless-rpc <in >out &&
+ test-pkt-line unpack <out >actual &&
+ test_cmp actual expect
+'
+
+test_expect_success 'refs/heads prefix' '
+ test-pkt-line pack >in <<-EOF &&
+ command=ls-refs
+ 0001
+ ref-prefix refs/heads/
+ 0000
+ EOF
+
+ cat >expect <<-EOF &&
+ $(git rev-parse refs/heads/dev) refs/heads/dev
+ $(git rev-parse refs/heads/master) refs/heads/master
+ $(git rev-parse refs/heads/release) refs/heads/release
+ 0000
+ EOF
+
+ git serve --stateless-rpc <in >out &&
+ test-pkt-line unpack <out >actual &&
+ test_cmp actual expect
+'
+
+test_expect_success 'peel parameter' '
+ test-pkt-line pack >in <<-EOF &&
+ command=ls-refs
+ 0001
+ peel
+ ref-prefix refs/tags/
+ 0000
+ EOF
+
+ cat >expect <<-EOF &&
+ $(git rev-parse refs/tags/annotated-tag) refs/tags/annotated-tag peeled:$(git rev-parse refs/tags/annotated-tag^{})
+ $(git rev-parse refs/tags/one) refs/tags/one
+ $(git rev-parse refs/tags/two) refs/tags/two
+ 0000
+ EOF
+
+ git serve --stateless-rpc <in >out &&
+ test-pkt-line unpack <out >actual &&
+ test_cmp actual expect
+'
+
+test_expect_success 'symrefs parameter' '
+ test-pkt-line pack >in <<-EOF &&
+ command=ls-refs
+ 0001
+ symrefs
+ ref-prefix refs/heads/
+ 0000
+ EOF
+
+ cat >expect <<-EOF &&
+ $(git rev-parse refs/heads/dev) refs/heads/dev
+ $(git rev-parse refs/heads/master) refs/heads/master
+ $(git rev-parse refs/heads/release) refs/heads/release symref-target:refs/heads/master
+ 0000
+ EOF
+
+ git serve --stateless-rpc <in >out &&
+ test-pkt-line unpack <out >actual &&
+ test_cmp actual expect
+'
+
+test_expect_success 'sending server-options' '
+ test-pkt-line pack >in <<-EOF &&
+ command=ls-refs
+ server-option=hello
+ server-option=world
+ 0001
+ ref-prefix HEAD
+ 0000
+ EOF
+
+ cat >expect <<-EOF &&
+ $(git rev-parse HEAD) HEAD
+ 0000
+ EOF
+
+ git serve --stateless-rpc <in >out &&
+ test-pkt-line unpack <out >actual &&
+ test_cmp actual expect
+'
+
+test_done
diff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh
new file mode 100755
index 0000000000..dbfd0691c0
--- /dev/null
+++ b/t/t5702-protocol-v2.sh
@@ -0,0 +1,305 @@
+#!/bin/sh
+
+test_description='test git wire-protocol version 2'
+
+TEST_NO_CREATE_REPO=1
+
+. ./test-lib.sh
+
+# Test protocol v2 with 'git://' transport
+#
+. "$TEST_DIRECTORY"/lib-git-daemon.sh
+start_git_daemon --export-all --enable=receive-pack
+daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent
+
+test_expect_success 'create repo to be served by git-daemon' '
+ git init "$daemon_parent" &&
+ test_commit -C "$daemon_parent" one
+'
+
+test_expect_success 'list refs with git:// using protocol v2' '
+ test_when_finished "rm -f log" &&
+
+ GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \
+ ls-remote --symref "$GIT_DAEMON_URL/parent" >actual &&
+
+ # Client requested to use protocol v2
+ grep "git> .*\\\0\\\0version=2\\\0$" log &&
+ # Server responded using protocol v2
+ grep "git< version 2" log &&
+
+ git ls-remote --symref "$GIT_DAEMON_URL/parent" >expect &&
+ test_cmp actual expect
+'
+
+test_expect_success 'ref advertisment is filtered with ls-remote using protocol v2' '
+ test_when_finished "rm -f log" &&
+
+ GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \
+ ls-remote "$GIT_DAEMON_URL/parent" master >actual &&
+
+ cat >expect <<-EOF &&
+ $(git -C "$daemon_parent" rev-parse refs/heads/master)$(printf "\t")refs/heads/master
+ EOF
+
+ test_cmp actual expect
+'
+
+test_expect_success 'clone with git:// using protocol v2' '
+ test_when_finished "rm -f log" &&
+
+ GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \
+ clone "$GIT_DAEMON_URL/parent" daemon_child &&
+
+ git -C daemon_child log -1 --format=%s >actual &&
+ git -C "$daemon_parent" log -1 --format=%s >expect &&
+ test_cmp expect actual &&
+
+ # Client requested to use protocol v2
+ grep "clone> .*\\\0\\\0version=2\\\0$" log &&
+ # Server responded using protocol v2
+ grep "clone< version 2" log
+'
+
+test_expect_success 'fetch with git:// using protocol v2' '
+ test_when_finished "rm -f log" &&
+
+ test_commit -C "$daemon_parent" two &&
+
+ GIT_TRACE_PACKET="$(pwd)/log" git -C daemon_child -c protocol.version=2 \
+ fetch &&
+
+ git -C daemon_child log -1 --format=%s origin/master >actual &&
+ git -C "$daemon_parent" log -1 --format=%s >expect &&
+ test_cmp expect actual &&
+
+ # Client requested to use protocol v2
+ grep "fetch> .*\\\0\\\0version=2\\\0$" log &&
+ # Server responded using protocol v2
+ grep "fetch< version 2" log
+'
+
+test_expect_success 'pull with git:// using protocol v2' '
+ test_when_finished "rm -f log" &&
+
+ GIT_TRACE_PACKET="$(pwd)/log" git -C daemon_child -c protocol.version=2 \
+ pull &&
+
+ git -C daemon_child log -1 --format=%s >actual &&
+ git -C "$daemon_parent" log -1 --format=%s >expect &&
+ test_cmp expect actual &&
+
+ # Client requested to use protocol v2
+ grep "fetch> .*\\\0\\\0version=2\\\0$" log &&
+ # Server responded using protocol v2
+ grep "fetch< version 2" log
+'
+
+test_expect_success 'push with git:// and a config of v2 does not request v2' '
+ test_when_finished "rm -f log" &&
+
+ # Till v2 for push is designed, make sure that if a client has
+ # protocol.version configured to use v2, that the client instead falls
+ # back and uses v0.
+
+ test_commit -C daemon_child three &&
+
+ # Push to another branch, as the target repository has the
+ # master branch checked out and we cannot push into it.
+ GIT_TRACE_PACKET="$(pwd)/log" git -C daemon_child -c protocol.version=2 \
+ push origin HEAD:client_branch &&
+
+ git -C daemon_child log -1 --format=%s >actual &&
+ git -C "$daemon_parent" log -1 --format=%s client_branch >expect &&
+ test_cmp expect actual &&
+
+ # Client requested to use protocol v2
+ ! grep "push> .*\\\0\\\0version=2\\\0$" log &&
+ # Server responded using protocol v2
+ ! grep "push< version 2" log
+'
+
+stop_git_daemon
+
+# Test protocol v2 with 'file://' transport
+#
+test_expect_success 'create repo to be served by file:// transport' '
+ git init file_parent &&
+ test_commit -C file_parent one
+'
+
+test_expect_success 'list refs with file:// using protocol v2' '
+ test_when_finished "rm -f log" &&
+
+ GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \
+ ls-remote --symref "file://$(pwd)/file_parent" >actual &&
+
+ # Server responded using protocol v2
+ grep "git< version 2" log &&
+
+ git ls-remote --symref "file://$(pwd)/file_parent" >expect &&
+ test_cmp actual expect
+'
+
+test_expect_success 'ref advertisment is filtered with ls-remote using protocol v2' '
+ test_when_finished "rm -f log" &&
+
+ GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \
+ ls-remote "file://$(pwd)/file_parent" master >actual &&
+
+ cat >expect <<-EOF &&
+ $(git -C file_parent rev-parse refs/heads/master)$(printf "\t")refs/heads/master
+ EOF
+
+ test_cmp actual expect
+'
+
+test_expect_success 'server-options are sent when using ls-remote' '
+ test_when_finished "rm -f log" &&
+
+ GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \
+ ls-remote -o hello -o world "file://$(pwd)/file_parent" master >actual &&
+
+ cat >expect <<-EOF &&
+ $(git -C file_parent rev-parse refs/heads/master)$(printf "\t")refs/heads/master
+ EOF
+
+ test_cmp actual expect &&
+ grep "server-option=hello" log &&
+ grep "server-option=world" log
+'
+
+
+test_expect_success 'clone with file:// using protocol v2' '
+ test_when_finished "rm -f log" &&
+
+ GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \
+ clone "file://$(pwd)/file_parent" file_child &&
+
+ git -C file_child log -1 --format=%s >actual &&
+ git -C file_parent log -1 --format=%s >expect &&
+ test_cmp expect actual &&
+
+ # Server responded using protocol v2
+ grep "clone< version 2" log
+'
+
+test_expect_success 'fetch with file:// using protocol v2' '
+ test_when_finished "rm -f log" &&
+
+ test_commit -C file_parent two &&
+
+ GIT_TRACE_PACKET="$(pwd)/log" git -C file_child -c protocol.version=2 \
+ fetch origin &&
+
+ git -C file_child log -1 --format=%s origin/master >actual &&
+ git -C file_parent log -1 --format=%s >expect &&
+ test_cmp expect actual &&
+
+ # Server responded using protocol v2
+ grep "fetch< version 2" log
+'
+
+test_expect_success 'ref advertisment is filtered during fetch using protocol v2' '
+ test_when_finished "rm -f log" &&
+
+ test_commit -C file_parent three &&
+
+ GIT_TRACE_PACKET="$(pwd)/log" git -C file_child -c protocol.version=2 \
+ fetch origin master &&
+
+ git -C file_child log -1 --format=%s origin/master >actual &&
+ git -C file_parent log -1 --format=%s >expect &&
+ test_cmp expect actual &&
+
+ ! grep "refs/tags/one" log &&
+ ! grep "refs/tags/two" log &&
+ ! grep "refs/tags/three" log
+'
+
+test_expect_success 'server-options are sent when fetching' '
+ test_when_finished "rm -f log" &&
+
+ test_commit -C file_parent four &&
+
+ GIT_TRACE_PACKET="$(pwd)/log" git -C file_child -c protocol.version=2 \
+ fetch -o hello -o world origin master &&
+
+ git -C file_child log -1 --format=%s origin/master >actual &&
+ git -C file_parent log -1 --format=%s >expect &&
+ test_cmp expect actual &&
+
+ grep "server-option=hello" log &&
+ grep "server-option=world" log
+'
+
+# Test protocol v2 with 'http://' transport
+#
+. "$TEST_DIRECTORY"/lib-httpd.sh
+start_httpd
+
+test_expect_success 'create repo to be served by http:// transport' '
+ git init "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
+ git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" config http.receivepack true &&
+ test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" one
+'
+
+test_expect_success 'clone with http:// using protocol v2' '
+ test_when_finished "rm -f log" &&
+
+ GIT_TRACE_PACKET="$(pwd)/log" GIT_TRACE_CURL="$(pwd)/log" git -c protocol.version=2 \
+ clone "$HTTPD_URL/smart/http_parent" http_child &&
+
+ git -C http_child log -1 --format=%s >actual &&
+ git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s >expect &&
+ test_cmp expect actual &&
+
+ # Client requested to use protocol v2
+ grep "Git-Protocol: version=2" log &&
+ # Server responded using protocol v2
+ grep "git< version 2" log
+'
+
+test_expect_success 'fetch with http:// using protocol v2' '
+ test_when_finished "rm -f log" &&
+
+ test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" two &&
+
+ GIT_TRACE_PACKET="$(pwd)/log" git -C http_child -c protocol.version=2 \
+ fetch &&
+
+ git -C http_child log -1 --format=%s origin/master >actual &&
+ git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s >expect &&
+ test_cmp expect actual &&
+
+ # Server responded using protocol v2
+ grep "git< version 2" log
+'
+
+test_expect_success 'push with http:// and a config of v2 does not request v2' '
+ test_when_finished "rm -f log" &&
+ # Till v2 for push is designed, make sure that if a client has
+ # protocol.version configured to use v2, that the client instead falls
+ # back and uses v0.
+
+ test_commit -C http_child three &&
+
+ # Push to another branch, as the target repository has the
+ # master branch checked out and we cannot push into it.
+ GIT_TRACE_PACKET="$(pwd)/log" git -C http_child -c protocol.version=2 \
+ push origin HEAD:client_branch &&
+
+ git -C http_child log -1 --format=%s >actual &&
+ git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s client_branch >expect &&
+ test_cmp expect actual &&
+
+ # Client didnt request to use protocol v2
+ ! grep "Git-Protocol: version=2" log &&
+ # Server didnt respond using protocol v2
+ ! grep "git< version 2" log
+'
+
+
+stop_httpd
+
+test_done
diff --git a/t/t6001-rev-list-graft.sh b/t/t6001-rev-list-graft.sh
index 05ddc69cf2..7504ba4751 100755
--- a/t/t6001-rev-list-graft.sh
+++ b/t/t6001-rev-list-graft.sh
@@ -110,4 +110,13 @@ do
"
done
+
+test_expect_success 'show advice that grafts are deprecated' '
+ git show HEAD 2>err &&
+ test_i18ngrep "git replace" err &&
+ test_config advice.graftFileDeprecated false &&
+ git show HEAD 2>err &&
+ test_i18ngrep ! "git replace" err
+'
+
test_done
diff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh
index a1fad6980b..b760c223c6 100755
--- a/t/t6022-merge-rename.sh
+++ b/t/t6022-merge-rename.sh
@@ -247,7 +247,7 @@ test_expect_success 'merge of identical changes in a renamed file' '
git reset --hard HEAD^ &&
git checkout change &&
GIT_MERGE_VERBOSITY=3 git merge change+rename >out &&
- test_i18ngrep "^Skipped B" out
+ test_i18ngrep ! "^Skipped B" out
'
test_expect_success 'setup for rename + d/f conflicts' '
@@ -635,10 +635,9 @@ test_expect_success 'setup avoid unnecessary update, normal rename' '
test_expect_success 'avoid unnecessary update, normal rename' '
git checkout -q avoid-unnecessary-update-1^0 &&
- test-tool chmtime =1000000000 rename &&
- test-tool chmtime -v +0 rename >expect &&
+ test-tool chmtime --get =1000000000 rename >expect &&
git merge merge-branch-1 &&
- test-tool chmtime -v +0 rename >actual &&
+ test-tool chmtime --get rename >actual &&
test_cmp expect actual # "rename" should have stayed intact
'
@@ -668,10 +667,9 @@ test_expect_success 'setup to test avoiding unnecessary update, with D/F conflic
test_expect_success 'avoid unnecessary update, with D/F conflict' '
git checkout -q avoid-unnecessary-update-2^0 &&
- test-tool chmtime =1000000000 df &&
- test-tool chmtime -v +0 df >expect &&
+ test-tool chmtime --get =1000000000 df >expect &&
git merge merge-branch-2 &&
- test-tool chmtime -v +0 df >actual &&
+ test-tool chmtime --get df >actual &&
test_cmp expect actual # "df" should have stayed intact
'
@@ -700,10 +698,9 @@ test_expect_success 'setup avoid unnecessary update, dir->(file,nothing)' '
test_expect_success 'avoid unnecessary update, dir->(file,nothing)' '
git checkout -q master^0 &&
- test-tool chmtime =1000000000 df &&
- test-tool chmtime -v +0 df >expect &&
+ test-tool chmtime --get =1000000000 df >expect &&
git merge side &&
- test-tool chmtime -v +0 df >actual &&
+ test-tool chmtime --get df >actual &&
test_cmp expect actual # "df" should have stayed intact
'
@@ -730,10 +727,9 @@ test_expect_success 'setup avoid unnecessary update, modify/delete' '
test_expect_success 'avoid unnecessary update, modify/delete' '
git checkout -q master^0 &&
- test-tool chmtime =1000000000 file &&
- test-tool chmtime -v +0 file >expect &&
+ test-tool chmtime --get =1000000000 file >expect &&
test_must_fail git merge side &&
- test-tool chmtime -v +0 file >actual &&
+ test-tool chmtime --get file >actual &&
test_cmp expect actual # "file" should have stayed intact
'
@@ -759,10 +755,9 @@ test_expect_success 'setup avoid unnecessary update, rename/add-dest' '
test_expect_success 'avoid unnecessary update, rename/add-dest' '
git checkout -q master^0 &&
- test-tool chmtime =1000000000 newfile &&
- test-tool chmtime -v +0 newfile >expect &&
+ test-tool chmtime --get =1000000000 newfile >expect &&
git merge side &&
- test-tool chmtime -v +0 newfile >actual &&
+ test-tool chmtime --get newfile >actual &&
test_cmp expect actual # "file" should have stayed intact
'
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/t6046-merge-skip-unneeded-updates.sh b/t/t6046-merge-skip-unneeded-updates.sh
new file mode 100755
index 0000000000..fcefffcaec
--- /dev/null
+++ b/t/t6046-merge-skip-unneeded-updates.sh
@@ -0,0 +1,761 @@
+#!/bin/sh
+
+test_description="merge cases"
+
+# The setup for all of them, pictorially, is:
+#
+# A
+# o
+# / \
+# O o ?
+# \ /
+# o
+# B
+#
+# To help make it easier to follow the flow of tests, they have been
+# divided into sections and each test will start with a quick explanation
+# of what commits O, A, and B contain.
+#
+# Notation:
+# z/{b,c} means files z/b and z/c both exist
+# x/d_1 means file x/d exists with content d1. (Purpose of the
+# underscore notation is to differentiate different
+# files that might be renamed into each other's paths.)
+
+. ./test-lib.sh
+
+
+###########################################################################
+# SECTION 1: Cases involving no renames (one side has subset of changes of
+# the other side)
+###########################################################################
+
+# Testcase 1a, Changes on A, subset of changes on B
+# Commit O: b_1
+# Commit A: b_2
+# Commit B: b_3
+# Expected: b_2
+
+test_expect_success '1a-setup: Modify(A)/Modify(B), change on B subset of A' '
+ test_create_repo 1a &&
+ (
+ cd 1a &&
+
+ test_write_lines 1 2 3 4 5 6 7 8 9 10 >b &&
+ git add b &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b &&
+ git add b &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b &&
+ git add b &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '1a-check-L: Modify(A)/Modify(B), change on B subset of A' '
+ test_when_finished "git -C 1a reset --hard" &&
+ test_when_finished "git -C 1a clean -fd" &&
+ (
+ cd 1a &&
+
+ git checkout A^0 &&
+
+ test-tool chmtime =31337 b &&
+ test-tool chmtime -v +0 b >expected-mtime &&
+
+ GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
+
+ test_i18ngrep "Skipped b" out &&
+ test_must_be_empty err &&
+
+ test-tool chmtime -v +0 b >actual-mtime &&
+ test_cmp expected-mtime actual-mtime &&
+
+ git ls-files -s >index_files &&
+ test_line_count = 1 index_files &&
+
+ git rev-parse >actual HEAD:b &&
+ git rev-parse >expect A:b &&
+ test_cmp expect actual &&
+
+ git hash-object b >actual &&
+ git rev-parse A:b >expect &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success '1a-check-R: Modify(A)/Modify(B), change on B subset of A' '
+ test_when_finished "git -C 1a reset --hard" &&
+ test_when_finished "git -C 1a clean -fd" &&
+ (
+ cd 1a &&
+
+ git checkout B^0 &&
+
+ GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err &&
+
+ test_i18ngrep "Auto-merging b" out &&
+ test_must_be_empty err &&
+
+ git ls-files -s >index_files &&
+ test_line_count = 1 index_files &&
+
+ git rev-parse >actual HEAD:b &&
+ git rev-parse >expect A:b &&
+ test_cmp expect actual &&
+
+ git hash-object b >actual &&
+ git rev-parse A:b >expect &&
+ test_cmp expect actual
+ )
+'
+
+
+###########################################################################
+# SECTION 2: Cases involving basic renames
+###########################################################################
+
+# Testcase 2a, Changes on A, rename on B
+# Commit O: b_1
+# Commit A: b_2
+# Commit B: c_1
+# Expected: c_2
+
+test_expect_success '2a-setup: Modify(A)/rename(B)' '
+ test_create_repo 2a &&
+ (
+ cd 2a &&
+
+ test_seq 1 10 >b &&
+ git add b &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ test_seq 1 11 >b &&
+ git add b &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv b c &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '2a-check-L: Modify/rename, merge into modify side' '
+ test_when_finished "git -C 2a reset --hard" &&
+ test_when_finished "git -C 2a clean -fd" &&
+ (
+ cd 2a &&
+
+ git checkout A^0 &&
+
+ GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
+
+ test_i18ngrep ! "Skipped c" out &&
+ test_must_be_empty err &&
+
+ git ls-files -s >index_files &&
+ test_line_count = 1 index_files &&
+
+ git rev-parse >actual HEAD:c &&
+ git rev-parse >expect A:b &&
+ test_cmp expect actual &&
+
+ git hash-object c >actual &&
+ git rev-parse A:b >expect &&
+ test_cmp expect actual &&
+
+ test_must_fail git rev-parse HEAD:b &&
+ test_path_is_missing b
+ )
+'
+
+test_expect_success '2a-check-R: Modify/rename, merge into rename side' '
+ test_when_finished "git -C 2a reset --hard" &&
+ test_when_finished "git -C 2a clean -fd" &&
+ (
+ cd 2a &&
+
+ git checkout B^0 &&
+
+ GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err &&
+
+ test_i18ngrep ! "Skipped c" out &&
+ test_must_be_empty err &&
+
+ git ls-files -s >index_files &&
+ test_line_count = 1 index_files &&
+
+ git rev-parse >actual HEAD:c &&
+ git rev-parse >expect A:b &&
+ test_cmp expect actual &&
+
+ git hash-object c >actual &&
+ git rev-parse A:b >expect &&
+ test_cmp expect actual &&
+
+ test_must_fail git rev-parse HEAD:b &&
+ test_path_is_missing b
+ )
+'
+
+# Testcase 2b, Changed and renamed on A, subset of changes on B
+# Commit O: b_1
+# Commit A: c_2
+# Commit B: b_3
+# Expected: c_2
+
+test_expect_success '2b-setup: Rename+Mod(A)/Mod(B), B mods subset of A' '
+ test_create_repo 2b &&
+ (
+ cd 2b &&
+
+ test_write_lines 1 2 3 4 5 6 7 8 9 10 >b &&
+ git add b &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b &&
+ git add b &&
+ git mv b c &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b &&
+ git add b &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '2b-check-L: Rename+Mod(A)/Mod(B), B mods subset of A' '
+ test_when_finished "git -C 2b reset --hard" &&
+ test_when_finished "git -C 2b clean -fd" &&
+ (
+ cd 2b &&
+
+ git checkout A^0 &&
+
+ test-tool chmtime =31337 c &&
+ test-tool chmtime -v +0 c >expected-mtime &&
+
+ GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
+
+ test_i18ngrep "Skipped c" out &&
+ test_must_be_empty err &&
+
+ test-tool chmtime -v +0 c >actual-mtime &&
+ test_cmp expected-mtime actual-mtime &&
+
+ git ls-files -s >index_files &&
+ test_line_count = 1 index_files &&
+
+ git rev-parse >actual HEAD:c &&
+ git rev-parse >expect A:c &&
+ test_cmp expect actual &&
+
+ git hash-object c >actual &&
+ git rev-parse A:c >expect &&
+ test_cmp expect actual &&
+
+ test_must_fail git rev-parse HEAD:b &&
+ test_path_is_missing b
+ )
+'
+
+test_expect_success '2b-check-R: Rename+Mod(A)/Mod(B), B mods subset of A' '
+ test_when_finished "git -C 2b reset --hard" &&
+ test_when_finished "git -C 2b clean -fd" &&
+ (
+ cd 2b &&
+
+ git checkout B^0 &&
+
+ GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err &&
+
+ test_i18ngrep "Auto-merging c" out &&
+ test_must_be_empty err &&
+
+ git ls-files -s >index_files &&
+ test_line_count = 1 index_files &&
+
+ git rev-parse >actual HEAD:c &&
+ git rev-parse >expect A:c &&
+ test_cmp expect actual &&
+
+ git hash-object c >actual &&
+ git rev-parse A:c >expect &&
+ test_cmp expect actual &&
+
+ test_must_fail git rev-parse HEAD:b &&
+ test_path_is_missing b
+ )
+'
+
+# Testcase 2c, Changes on A, rename on B
+# Commit O: b_1
+# Commit A: b_2, c_3
+# Commit B: c_1
+# Expected: rename/add conflict c_2 vs c_3
+#
+# NOTE: Since A modified b_1->b_2, and B renamed b_1->c_1, the threeway
+# merge of those files should result in c_2. We then should have a
+# rename/add conflict between c_2 and c_3. However, if we note in
+# merge_content() that A had the right contents (b_2 has same
+# contents as c_2, just at a different name), and that A had the
+# right path present (c_3 existed) and thus decides that it can
+# skip the update, then we're in trouble. This test verifies we do
+# not make that particular mistake.
+
+test_expect_success '2c-setup: Modify b & add c VS rename b->c' '
+ test_create_repo 2c &&
+ (
+ cd 2c &&
+
+ test_seq 1 10 >b &&
+ git add b &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ test_seq 1 11 >b &&
+ echo whatever >c &&
+ git add b c &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv b c &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '2c-check: Modify b & add c VS rename b->c' '
+ (
+ cd 2c &&
+
+ git checkout A^0 &&
+
+ GIT_MERGE_VERBOSITY=3 test_must_fail git merge -s recursive B^0 >out 2>err &&
+
+ test_i18ngrep "CONFLICT (rename/add): Rename b->c" out &&
+ test_i18ngrep ! "Skipped c" out &&
+ test_must_be_empty err
+
+ # FIXME: rename/add conflicts are horribly broken right now;
+ # when I get back to my patch series fixing it and
+ # rename/rename(2to1) conflicts to bring them in line with
+ # how add/add conflicts behave, then checks like the below
+ # could be added. But that patch series is waiting until
+ # the rename-directory-detection series lands, which this
+ # is part of. And in the mean time, I do not want to further
+ # enforce broken behavior. So for now, the main test is the
+ # one above that err is an empty file.
+
+ #git ls-files -s >index_files &&
+ #test_line_count = 2 index_files &&
+
+ #git rev-parse >actual :2:c :3:c &&
+ #git rev-parse >expect A:b A:c &&
+ #test_cmp expect actual &&
+
+ #git cat-file -p A:b >>merged &&
+ #git cat-file -p A:c >>merge-me &&
+ #>empty &&
+ #test_must_fail git merge-file \
+ # -L "Temporary merge branch 1" \
+ # -L "" \
+ # -L "Temporary merge branch 2" \
+ # merged empty merge-me &&
+ #sed -e "s/^\([<=>]\)/\1\1\1/" merged >merged-internal &&
+
+ #git hash-object c >actual &&
+ #git hash-object merged-internal >expect &&
+ #test_cmp expect actual &&
+
+ #test_path_is_missing b
+ )
+'
+
+
+###########################################################################
+# SECTION 3: Cases involving directory renames
+#
+# NOTE:
+# Directory renames only apply when one side renames a directory, and the
+# other side adds or renames a path into that directory. Applying the
+# directory rename to that new path creates a new pathname that didn't
+# exist on either side of history. Thus, it is impossible for the
+# merge contents to already be at the right path, so all of these checks
+# exist just to make sure that updates are not skipped.
+###########################################################################
+
+# Testcase 3a, Change + rename into dir foo on A, dir rename foo->bar on B
+# Commit O: bq_1, foo/whatever
+# Commit A: foo/{bq_2, whatever}
+# Commit B: bq_1, bar/whatever
+# Expected: bar/{bq_2, whatever}
+
+test_expect_success '3a-setup: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
+ test_create_repo 3a &&
+ (
+ cd 3a &&
+
+ mkdir foo &&
+ test_seq 1 10 >bq &&
+ test_write_lines a b c d e f g h i j k >foo/whatever &&
+ git add bq foo/whatever &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ test_seq 1 11 >bq &&
+ git add bq &&
+ git mv bq foo/ &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ git mv foo/ bar/ &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '3a-check-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
+ test_when_finished "git -C 3a reset --hard" &&
+ test_when_finished "git -C 3a clean -fd" &&
+ (
+ cd 3a &&
+
+ git checkout A^0 &&
+
+ GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
+
+ test_i18ngrep ! "Skipped bar/bq" out &&
+ test_must_be_empty err &&
+
+ git ls-files -s >index_files &&
+ test_line_count = 2 index_files &&
+
+ git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever &&
+ git rev-parse >expect A:foo/bq A:foo/whatever &&
+ test_cmp expect actual &&
+
+ git hash-object bar/bq bar/whatever >actual &&
+ git rev-parse A:foo/bq A:foo/whatever >expect &&
+ test_cmp expect actual &&
+
+ test_must_fail git rev-parse HEAD:bq HEAD:foo/bq &&
+ test_path_is_missing bq foo/bq foo/whatever
+ )
+'
+
+test_expect_success '3a-check-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
+ test_when_finished "git -C 3a reset --hard" &&
+ test_when_finished "git -C 3a clean -fd" &&
+ (
+ cd 3a &&
+
+ git checkout B^0 &&
+
+ GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err &&
+
+ test_i18ngrep ! "Skipped bar/bq" out &&
+ test_must_be_empty err &&
+
+ git ls-files -s >index_files &&
+ test_line_count = 2 index_files &&
+
+ git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever &&
+ git rev-parse >expect A:foo/bq A:foo/whatever &&
+ test_cmp expect actual &&
+
+ git hash-object bar/bq bar/whatever >actual &&
+ git rev-parse A:foo/bq A:foo/whatever >expect &&
+ test_cmp expect actual &&
+
+ test_must_fail git rev-parse HEAD:bq HEAD:foo/bq &&
+ test_path_is_missing bq foo/bq foo/whatever
+ )
+'
+
+# Testcase 3b, rename into dir foo on A, dir rename foo->bar + change on B
+# Commit O: bq_1, foo/whatever
+# Commit A: foo/{bq_1, whatever}
+# Commit B: bq_2, bar/whatever
+# Expected: bar/{bq_2, whatever}
+
+test_expect_success '3b-setup: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
+ test_create_repo 3b &&
+ (
+ cd 3b &&
+
+ mkdir foo &&
+ test_seq 1 10 >bq &&
+ test_write_lines a b c d e f g h i j k >foo/whatever &&
+ git add bq foo/whatever &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ git mv bq foo/ &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ test_seq 1 11 >bq &&
+ git add bq &&
+ git mv foo/ bar/ &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '3b-check-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
+ test_when_finished "git -C 3b reset --hard" &&
+ test_when_finished "git -C 3b clean -fd" &&
+ (
+ cd 3b &&
+
+ git checkout A^0 &&
+
+ GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
+
+ test_i18ngrep ! "Skipped bar/bq" out &&
+ test_must_be_empty err &&
+
+ git ls-files -s >index_files &&
+ test_line_count = 2 index_files &&
+
+ git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever &&
+ git rev-parse >expect B:bq A:foo/whatever &&
+ test_cmp expect actual &&
+
+ git hash-object bar/bq bar/whatever >actual &&
+ git rev-parse B:bq A:foo/whatever >expect &&
+ test_cmp expect actual &&
+
+ test_must_fail git rev-parse HEAD:bq HEAD:foo/bq &&
+ test_path_is_missing bq foo/bq foo/whatever
+ )
+'
+
+test_expect_success '3b-check-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
+ test_when_finished "git -C 3b reset --hard" &&
+ test_when_finished "git -C 3b clean -fd" &&
+ (
+ cd 3b &&
+
+ git checkout B^0 &&
+
+ GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err &&
+
+ test_i18ngrep ! "Skipped bar/bq" out &&
+ test_must_be_empty err &&
+
+ git ls-files -s >index_files &&
+ test_line_count = 2 index_files &&
+
+ git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever &&
+ git rev-parse >expect B:bq A:foo/whatever &&
+ test_cmp expect actual &&
+
+ git hash-object bar/bq bar/whatever >actual &&
+ git rev-parse B:bq A:foo/whatever >expect &&
+ test_cmp expect actual &&
+
+ test_must_fail git rev-parse HEAD:bq HEAD:foo/bq &&
+ test_path_is_missing bq foo/bq foo/whatever
+ )
+'
+
+###########################################################################
+# SECTION 4: Cases involving dirty changes
+###########################################################################
+
+# Testcase 4a, Changed on A, subset of changes on B, locally modified
+# Commit O: b_1
+# Commit A: b_2
+# Commit B: b_3
+# Working copy: b_4
+# Expected: b_2 for merge, b_4 in working copy
+
+test_expect_success '4a-setup: Change on A, change on B subset of A, dirty mods present' '
+ test_create_repo 4a &&
+ (
+ cd 4a &&
+
+ test_write_lines 1 2 3 4 5 6 7 8 9 10 >b &&
+ git add b &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b &&
+ git add b &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b &&
+ git add b &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+# NOTE: For as long as we continue using unpack_trees() without index_only
+# set to true, it will error out on a case like this claiming the the locally
+# modified file would be overwritten by the merge. Getting this testcase
+# correct requires doing the merge in-memory first, then realizing that no
+# updates to the file are necessary, and thus that we can just leave the path
+# alone.
+test_expect_failure '4a-check: Change on A, change on B subset of A, dirty mods present' '
+ test_when_finished "git -C 4a reset --hard" &&
+ test_when_finished "git -C 4a clean -fd" &&
+ (
+ cd 4a &&
+
+ git checkout A^0 &&
+ echo "File rewritten" >b &&
+
+ test-tool chmtime =31337 b &&
+ test-tool chmtime -v +0 b >expected-mtime &&
+
+ GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
+
+ test_i18ngrep "Skipped b" out &&
+ test_must_be_empty err &&
+
+ test-tool chmtime -v +0 b >actual-mtime &&
+ test_cmp expected-mtime actual-mtime &&
+
+ git ls-files -s >index_files &&
+ test_line_count = 1 index_files &&
+
+ git rev-parse >actual :0:b &&
+ git rev-parse >expect A:b &&
+ test_cmp expect actual &&
+
+ git hash-object b >actual &&
+ echo "File rewritten" | git hash-object --stdin >expect &&
+ test_cmp expect actual
+ )
+'
+
+# Testcase 4b, Changed+renamed on A, subset of changes on B, locally modified
+# Commit O: b_1
+# Commit A: c_2
+# Commit B: b_3
+# Working copy: c_4
+# Expected: c_2
+
+test_expect_success '4b-setup: Rename+Mod(A)/Mod(B), change on B subset of A, dirty mods present' '
+ test_create_repo 4b &&
+ (
+ cd 4b &&
+
+ test_write_lines 1 2 3 4 5 6 7 8 9 10 >b &&
+ git add b &&
+ test_tick &&
+ git commit -m "O" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b &&
+ git add b &&
+ git mv b c &&
+ test_tick &&
+ git commit -m "A" &&
+
+ git checkout B &&
+ test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b &&
+ git add b &&
+ test_tick &&
+ git commit -m "B"
+ )
+'
+
+test_expect_success '4b-check: Rename+Mod(A)/Mod(B), change on B subset of A, dirty mods present' '
+ test_when_finished "git -C 4b reset --hard" &&
+ test_when_finished "git -C 4b clean -fd" &&
+ (
+ cd 4b &&
+
+ git checkout A^0 &&
+ echo "File rewritten" >c &&
+
+ test-tool chmtime =31337 c &&
+ test-tool chmtime -v +0 c >expected-mtime &&
+
+ GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
+
+ test_i18ngrep "Skipped c" out &&
+ test_must_be_empty err &&
+
+ test-tool chmtime -v +0 c >actual-mtime &&
+ test_cmp expected-mtime actual-mtime &&
+
+ git ls-files -s >index_files &&
+ test_line_count = 1 index_files &&
+
+ git rev-parse >actual :0:c &&
+ git rev-parse >expect A:c &&
+ test_cmp expect actual &&
+
+ git hash-object c >actual &&
+ echo "File rewritten" | git hash-object --stdin >expect &&
+ test_cmp expect actual &&
+
+ test_must_fail git rev-parse HEAD:b &&
+ test_path_is_missing b
+ )
+'
+
+test_done
diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh
index c630aba657..d174bfed30 100755
--- a/t/t6050-replace.sh
+++ b/t/t6050-replace.sh
@@ -444,4 +444,32 @@ test_expect_success GPG '--graft on a commit with a mergetag' '
git replace -d $HASH10
'
+test_expect_success '--convert-graft-file' '
+ git checkout -b with-graft-file &&
+ test_commit root2 &&
+ git reset --hard root2^ &&
+ test_commit root1 &&
+ test_commit after-root1 &&
+ test_tick &&
+ git merge -m merge-root2 root2 &&
+
+ : add and convert graft file &&
+ printf "%s\n%s %s\n\n# comment\n%s\n" \
+ $(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \
+ >.git/info/grafts &&
+ git replace --convert-graft-file &&
+ test_path_is_missing .git/info/grafts &&
+
+ : verify that the history is now "grafted" &&
+ git rev-list HEAD >out &&
+ test_line_count = 4 out &&
+
+ : create invalid graft file and verify that it is not deleted &&
+ test_when_finished "rm -f .git/info/grafts" &&
+ echo $EMPTY_BLOB $EMPTY_TREE >.git/info/grafts &&
+ test_must_fail git replace --convert-graft-file 2>err &&
+ test_i18ngrep "$EMPTY_BLOB $EMPTY_TREE" err &&
+ test_i18ngrep "$EMPTY_BLOB $EMPTY_TREE" .git/info/grafts
+'
+
test_done
diff --git a/t/t6500-gc.sh b/t/t6500-gc.sh
index d5255dd576..818435f04e 100755
--- a/t/t6500-gc.sh
+++ b/t/t6500-gc.sh
@@ -5,6 +5,13 @@ test_description='basic git gc tests
. ./test-lib.sh
+test_expect_success 'setup' '
+ # do not let the amount of physical memory affects gc
+ # behavior, make sure we always pack everything to one pack by
+ # default
+ git config gc.bigPackThreshold 2g
+'
+
test_expect_success 'gc empty repository' '
git gc
'
@@ -43,6 +50,31 @@ test_expect_success 'gc is not aborted due to a stale symref' '
)
'
+test_expect_success 'gc --keep-largest-pack' '
+ test_create_repo keep-pack &&
+ (
+ cd keep-pack &&
+ test_commit one &&
+ test_commit two &&
+ test_commit three &&
+ git gc &&
+ ( cd .git/objects/pack && ls *.pack ) >pack-list &&
+ test_line_count = 1 pack-list &&
+ BASE_PACK=.git/objects/pack/pack-*.pack &&
+ test_commit four &&
+ git repack -d &&
+ test_commit five &&
+ git repack -d &&
+ ( cd .git/objects/pack && ls *.pack ) >pack-list &&
+ test_line_count = 3 pack-list &&
+ git gc --keep-largest-pack &&
+ ( cd .git/objects/pack && ls *.pack ) >pack-list &&
+ test_line_count = 2 pack-list &&
+ test_path_is_file $BASE_PACK &&
+ git fsck
+ )
+'
+
test_expect_success 'auto gc with too many loose objects does not attempt to create bitmaps' '
test_config gc.auto 3 &&
test_config gc.autodetach false &&
diff --git a/t/t6501-freshen-objects.sh b/t/t6501-freshen-objects.sh
index 765cced60b..033871ee5f 100755
--- a/t/t6501-freshen-objects.sh
+++ b/t/t6501-freshen-objects.sh
@@ -72,8 +72,7 @@ for repack in '' true; do
'
test_expect_success "simulate time passing ($title)" '
- find .git/objects -type f |
- xargs test-tool chmtime -v -86400
+ test-tool chmtime --get -86400 $(find .git/objects -type f)
'
test_expect_success "start writing new commit with old blob ($title)" '
@@ -103,8 +102,7 @@ for repack in '' true; do
test_expect_success "abandon objects again ($title)" '
git reset --hard HEAD^ &&
- find .git/objects -type f |
- xargs test-tool chmtime -v -86400
+ test-tool chmtime --get -86400 $(find .git/objects -type f)
'
test_expect_success "start writing new commit with same tree ($title)" '
diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh
index e96cbdb105..cc3fd2baf2 100755
--- a/t/t7001-mv.sh
+++ b/t/t7001-mv.sh
@@ -495,7 +495,7 @@ test_expect_success 'moving a submodule in nested directories' '
test_cmp expect actual
'
-test_expect_failure 'moving nested submodules' '
+test_expect_success 'moving nested submodules' '
git commit -am "cleanup commit" &&
mkdir sub_nested_nested &&
(cd sub_nested_nested &&
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index 2aac77af70..d7b319e919 100755
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -363,7 +363,7 @@ test_expect_success 'tag -l <pattern> -l <pattern> works, as our buggy documenta
'
test_expect_success 'listing tags in column' '
- COLUMNS=40 git tag -l --column=row >actual &&
+ COLUMNS=41 git tag -l --column=row >actual &&
cat >expected <<\EOF &&
a1 aa1 cba t210 t211
v0.2.1 v1.0 v1.0.1 v1.1.3
@@ -1056,7 +1056,18 @@ test_expect_success GPG \
git tag -s -F sigblanknonlfile blanknonlfile-signed-tag &&
get_tag_msg blanknonlfile-signed-tag >actual &&
test_cmp expect actual &&
- git tag -v signed-tag
+ git tag -v blanknonlfile-signed-tag
+'
+
+test_expect_success GPG 'signed tag with embedded PGP message' '
+ cat >msg <<-\EOF &&
+ -----BEGIN PGP MESSAGE-----
+
+ this is not a real PGP message
+ -----END PGP MESSAGE-----
+ EOF
+ git tag -s -F msg confusing-pgp-message &&
+ git tag -v confusing-pgp-message
'
# messages with commented lines for signed tags:
diff --git a/t/t7005-editor.sh b/t/t7005-editor.sh
index 29e5043b94..b2ca77b338 100755
--- a/t/t7005-editor.sh
+++ b/t/t7005-editor.sh
@@ -111,14 +111,8 @@ do
'
done
-if echo 'echo space > "$1"' > "e space.sh"
-then
- # FS supports spaces in filenames
- test_set_prereq SPACES_IN_FILENAMES
-fi
-
-test_expect_success SPACES_IN_FILENAMES 'editor with a space' '
-
+test_expect_success 'editor with a space' '
+ echo "echo space >\$1" >"e space.sh" &&
chmod a+x "e space.sh" &&
GIT_EDITOR="./e\ space.sh" git commit --amend &&
test space = "$(git show -s --pretty=format:%s)"
@@ -126,7 +120,7 @@ test_expect_success SPACES_IN_FILENAMES 'editor with a space' '
'
unset GIT_EDITOR
-test_expect_success SPACES_IN_FILENAMES 'core.editor with a space' '
+test_expect_success 'core.editor with a space' '
git config core.editor \"./e\ space.sh\" &&
git commit --amend &&
diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh
index fa61b1a4ee..9dbbd01fc0 100755
--- a/t/t7501-commit.sh
+++ b/t/t7501-commit.sh
@@ -52,6 +52,18 @@ test_expect_success PERL 'can use paths with --interactive' '
git reset --hard HEAD^
'
+test_expect_success 'removed files and relative paths' '
+ test_when_finished "rm -rf foo" &&
+ git init foo &&
+ >foo/foo.txt &&
+ git -C foo add foo.txt &&
+ git -C foo commit -m first &&
+ git -C foo rm foo.txt &&
+
+ mkdir -p foo/bar &&
+ git -C foo/bar commit -m second ../foo.txt
+'
+
test_expect_success 'using invalid commit with -C' '
test_must_fail git commit --allow-empty -C bogus
'
diff --git a/t/t7508-status.sh b/t/t7508-status.sh
index 7afadb175a..18a40257fb 100755
--- a/t/t7508-status.sh
+++ b/t/t7508-status.sh
@@ -1674,10 +1674,10 @@ test_expect_success '"Initial commit" should not be noted in commit template' '
test_expect_success '--no-optional-locks prevents index update' '
test-tool chmtime =1234567890 .git/index &&
git --no-optional-locks status &&
- test-tool chmtime -v +0 .git/index >out &&
+ test-tool chmtime --get .git/index >out &&
grep ^1234567890 out &&
git status &&
- test-tool chmtime -v +0 .git/index >out &&
+ test-tool chmtime --get .git/index >out &&
! grep ^1234567890 out
'
diff --git a/t/t7607-merge-overwrite.sh b/t/t7607-merge-overwrite.sh
index 9c422bcd7c..dd8ab7ede1 100755
--- a/t/t7607-merge-overwrite.sh
+++ b/t/t7607-merge-overwrite.sh
@@ -92,7 +92,7 @@ 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 &&
diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh
index 6061a04147..6162e2a8e6 100755
--- a/t/t7700-repack.sh
+++ b/t/t7700-repack.sh
@@ -4,6 +4,12 @@ test_description='git repack works correctly'
. ./test-lib.sh
+commit_and_pack() {
+ test_commit "$@" >/dev/null &&
+ SHA1=$(git pack-objects --all --unpacked --incremental .git/objects/pack/pack </dev/null) &&
+ echo pack-${SHA1}.pack
+}
+
test_expect_success 'objects in packs marked .keep are not repacked' '
echo content1 > file1 &&
echo content2 > file2 &&
@@ -194,7 +200,26 @@ test_expect_success 'objects made unreachable by grafts only are kept' '
git reflog expire --expire=$test_tick --expire-unreachable=$test_tick --all &&
git repack -a -d &&
git cat-file -t $H1
- '
+'
+
+test_expect_success 'repack --keep-pack' '
+ test_create_repo keep-pack &&
+ (
+ cd keep-pack &&
+ P1=$(commit_and_pack 1) &&
+ P2=$(commit_and_pack 2) &&
+ P3=$(commit_and_pack 3) &&
+ P4=$(commit_and_pack 4) &&
+ ls .git/objects/pack/*.pack >old-counts &&
+ test_line_count = 4 old-counts &&
+ git repack -a -d --keep-pack $P1 --keep-pack $P4 &&
+ ls .git/objects/pack/*.pack >new-counts &&
+ grep -q $P1 new-counts &&
+ grep -q $P4 new-counts &&
+ test_line_count = 3 new-counts &&
+ git fsck
+ )
+'
test_done
diff --git a/t/t7701-repack-unpack-unreachable.sh b/t/t7701-repack-unpack-unreachable.sh
index 8a586ab021..48261ba080 100755
--- a/t/t7701-repack-unpack-unreachable.sh
+++ b/t/t7701-repack-unpack-unreachable.sh
@@ -55,8 +55,8 @@ test_expect_success '-A with -d option leaves unreachable objects unpacked' '
compare_mtimes ()
{
- read tref rest &&
- while read t rest; do
+ read tref &&
+ while read t; do
test "$tref" = "$t" || return 1
done
}
@@ -90,7 +90,7 @@ test_expect_success 'unpacked objects receive timestamp of pack file' '
tmppack=".git/objects/pack/tmp_pack" &&
ln "$packfile" "$tmppack" &&
git repack -A -l -d &&
- test-tool chmtime -v +0 "$tmppack" "$fsha1path" "$csha1path" "$tsha1path" \
+ test-tool chmtime --get "$tmppack" "$fsha1path" "$csha1path" "$tsha1path" \
> mtimes &&
compare_mtimes < mtimes
'
diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh
index d5679ffb87..6a392e87bc 100755
--- a/t/t9350-fast-export.sh
+++ b/t/t9350-fast-export.sh
@@ -538,4 +538,22 @@ test_expect_success 'when using -C, do not declare copy when source of copy is a
test_cmp expected actual
'
+test_expect_success 'merge commit gets exported with --import-marks' '
+ test_create_repo merging &&
+ (
+ cd merging &&
+ test_commit initial &&
+ git checkout -b topic &&
+ test_commit on-topic &&
+ git checkout master &&
+ test_commit on-master &&
+ test_tick &&
+ git merge --no-ff -m Yeah topic &&
+
+ echo ":1 $(git rev-parse HEAD^^)" >marks &&
+ git fast-export --import-marks=marks master >out &&
+ grep Yeah out
+ )
+'
+
test_done
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
index 7d620bf2a9..2b2181dca0 100644
--- a/t/test-lib-functions.sh
+++ b/t/test-lib-functions.sh
@@ -145,12 +145,28 @@ test_pause () {
"$SHELL_PATH" <&6 >&5 2>&7
}
-# Wrap git in gdb. Adding this to a command can make it easier to
-# understand what is going on in a failing test.
+# Wrap git with a debugger. Adding this to a command can make it easier
+# to understand what is going on in a failing test.
#
-# Example: "debug git checkout master".
+# Examples:
+# debug git checkout master
+# debug --debugger=nemiver git $ARGS
+# debug -d "valgrind --tool=memcheck --track-origins=yes" git $ARGS
debug () {
- GIT_TEST_GDB=1 "$@" <&6 >&5 2>&7
+ case "$1" in
+ -d)
+ GIT_DEBUGGER="$2" &&
+ shift 2
+ ;;
+ --debugger=*)
+ GIT_DEBUGGER="${1#*=}" &&
+ shift 1
+ ;;
+ *)
+ GIT_DEBUGGER=1
+ ;;
+ esac &&
+ GIT_DEBUGGER="${GIT_DEBUGGER}" "$@" <&6 >&5 2>&7
}
# Call test_commit with the arguments
@@ -278,8 +294,20 @@ write_script () {
# The single parameter is the prerequisite tag (a simple word, in all
# capital letters by convention).
+test_unset_prereq () {
+ ! test_have_prereq "$1" ||
+ satisfied_prereq="${satisfied_prereq% $1 *} ${satisfied_prereq#* $1 }"
+}
+
test_set_prereq () {
- satisfied_prereq="$satisfied_prereq$1 "
+ case "$1" in
+ !*)
+ test_unset_prereq "${1#!}"
+ ;;
+ *)
+ satisfied_prereq="$satisfied_prereq$1 "
+ ;;
+ esac
}
satisfied_prereq=" "
lazily_testable_prereq= lazily_tested_prereq=
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 483c8d6d7c..ea2bbaaa7a 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -1208,3 +1208,7 @@ test_lazy_prereq LONG_IS_64BIT '
test_lazy_prereq TIME_IS_64BIT 'test-tool date is64bit'
test_lazy_prereq TIME_T_IS_64BIT 'test-tool date time_t-is64bit'
+
+test_lazy_prereq CURL '
+ curl --version
+'