summaryrefslogtreecommitdiff
path: root/t
diff options
context:
space:
mode:
Diffstat (limited to 't')
-rw-r--r--t/README8
-rw-r--r--t/chainlint.sed67
-rw-r--r--t/chainlint/here-doc.expect2
-rw-r--r--t/chainlint/here-doc.test7
-rw-r--r--t/chainlint/subshell-here-doc.expect1
-rw-r--r--t/chainlint/subshell-here-doc.test4
-rwxr-xr-xt/check-non-portable-shell.pl3
-rw-r--r--t/helper/test-dump-cache-tree.c2
-rw-r--r--t/helper/test-reach.c130
-rw-r--r--t/helper/test-read-midx.c51
-rw-r--r--t/helper/test-tool.c2
-rw-r--r--t/helper/test-tool.h4
-rw-r--r--t/lib-rebase.sh6
-rwxr-xr-xt/lib-submodule-update.sh5
-rwxr-xr-xt/t0000-basic.sh2
-rwxr-xr-xt/t0001-init.sh5
-rwxr-xr-xt/t0003-attributes.sh3
-rw-r--r--t/t0019/parse_json.perl3
-rwxr-xr-xt/t0020-crlf.sh2
-rwxr-xr-xt/t0028-working-tree-encoding.sh6
-rwxr-xr-xt/t0030-stripspace.sh22
-rwxr-xr-xt/t0040-parse-options.sh4
-rwxr-xr-xt/t0061-run-command.sh9
-rwxr-xr-xt/t0090-cache-tree.sh2
-rwxr-xr-xt/t0203-gettext-setlocale-sanity.sh4
-rwxr-xr-xt/t1090-sparse-checkout-scope.sh14
-rwxr-xr-xt/t1300-config.sh5
-rwxr-xr-xt/t1404-update-ref-errors.sh6
-rwxr-xr-xt/t1410-reflog.sh3
-rwxr-xr-xt/t1411-reflog-show.sh3
-rwxr-xr-xt/t1450-fsck.sh11
-rwxr-xr-xt/t1501-work-tree.sh2
-rwxr-xr-xt/t1510-repo-setup.sh24
-rwxr-xr-xt/t1600-index.sh3
-rwxr-xr-xt/t1700-split-index.sh4
-rwxr-xr-xt/t2013-checkout-submodule.sh6
-rwxr-xr-xt/t2025-worktree-add.sh5
-rwxr-xr-xt/t2200-add-update.sh3
-rwxr-xr-xt/t2203-add-intent.sh6
-rwxr-xr-xt/t2204-add-ignored.sh8
-rwxr-xr-xt/t3001-ls-files-others-exclude.sh6
-rwxr-xr-xt/t3004-ls-files-basic.sh6
-rwxr-xr-xt/t3035-merge-sparse.sh5
-rwxr-xr-xt/t3200-branch.sh46
-rwxr-xr-xt/t3206-range-diff.sh57
-rwxr-xr-xt/t3210-pack-refs.sh6
-rwxr-xr-xt/t3301-notes.sh4
-rwxr-xr-xt/t3308-notes-merge.sh2
-rwxr-xr-xt/t3310-notes-merge-manual-resolve.sh8
-rwxr-xr-xt/t3401-rebase-and-am-rename.sh110
-rwxr-xr-xt/t3404-rebase-interactive.sh23
-rwxr-xr-xt/t3420-rebase-autostash.sh8
-rwxr-xr-xt/t3600-rm.sh6
-rwxr-xr-xt/t3903-stash.sh2
-rwxr-xr-xt/t4011-diff-symlink.sh2
-rwxr-xr-xt/t4013-diff-various.sh2
-rwxr-xr-xt/t4014-format-patch.sh34
-rwxr-xr-xt/t4015-diff-whitespace.sh4
-rwxr-xr-xt/t4019-diff-wserror.sh2
-rwxr-xr-xt/t4027-diff-submodule.sh29
-rwxr-xr-xt/t4041-diff-submodule-option.sh18
-rwxr-xr-xt/t4047-diff-dirstat.sh4
-rwxr-xr-xt/t4051-diff-function-context.sh2
-rwxr-xr-xt/t4060-diff-submodule-option-diff-format.sh18
-rwxr-xr-xt/t4116-apply-reverse.sh2
-rwxr-xr-xt/t4124-apply-ws-rule.sh2
-rwxr-xr-xt/t4132-apply-removal.sh5
-rwxr-xr-xt/t4150-am.sh4
-rwxr-xr-xt/t4200-rerere.sh65
-rwxr-xr-xt/t4201-shortlog.sh2
-rwxr-xr-xt/t4203-mailmap.sh4
-rwxr-xr-xt/t4205-log-pretty-formats.sh23
-rwxr-xr-xt/t4211-line-log.sh2
-rwxr-xr-xt/t4212-log-corrupt.sh6
-rwxr-xr-xt/t4256-am-format-flowed.sh19
-rw-r--r--t/t4256/1/mailinfo.c1245
-rw-r--r--t/t4256/1/mailinfo.c.orig1185
-rw-r--r--t/t4256/1/patch129
-rwxr-xr-xt/t4300-merge-tree.sh34
-rwxr-xr-xt/t5304-prune.sh3
-rwxr-xr-xt/t5310-pack-bitmaps.sh2
-rwxr-xr-xt/t5314-pack-cycle-detection.sh3
-rwxr-xr-xt/t5318-commit-graph.sh2
-rwxr-xr-xt/t5319-multi-pack-index.sh217
-rwxr-xr-xt/t5320-delta-islands.sh143
-rwxr-xr-xt/t5401-update-hooks.sh10
-rwxr-xr-xt/t5500-fetch-pack.sh2
-rwxr-xr-xt/t5509-fetch-push-namespaces.sh2
-rwxr-xr-xt/t5523-push-upstream.sh2
-rwxr-xr-xt/t5526-fetch-submodules.sh45
-rwxr-xr-xt/t5541-http-push-smart.sh2
-rwxr-xr-xt/t5552-skipping-fetch-negotiator.sh12
-rwxr-xr-xt/t5562-http-backend-content-length.sh11
-rwxr-xr-xt/t5570-git-daemon.sh2
-rwxr-xr-xt/t5601-clone.sh8
-rwxr-xr-xt/t5703-upload-pack-ref-in-want.sh4
-rwxr-xr-xt/t6018-rev-list-glob.sh4
-rwxr-xr-xt/t6112-rev-list-filters-objects.sh3
-rwxr-xr-xt/t6120-describe.sh3
-rwxr-xr-xt/t6130-pathspec-noglob.sh9
-rwxr-xr-xt/t6200-fmt-merge-msg.sh4
-rwxr-xr-xt/t6300-for-each-ref.sh23
-rwxr-xr-xt/t6600-test-reach.sh242
-rwxr-xr-xt/t7001-mv.sh6
-rwxr-xr-xt/t7004-tag.sh8
-rwxr-xr-xt/t7008-grep-binary.sh6
-rwxr-xr-xt/t7064-wtstatus-pv2.sh5
-rwxr-xr-xt/t7201-co.sh7
-rwxr-xr-xt/t7400-submodule-basic.sh33
-rwxr-xr-xt/t7401-submodule-summary.sh2
-rwxr-xr-xt/t7501-commit.sh24
-rwxr-xr-xt/t7513-interpret-trailers.sh42
-rwxr-xr-xt/t7600-merge.sh9
-rwxr-xr-xt/t7610-mergetool.sh3
-rwxr-xr-xt/t7810-grep.sh35
-rwxr-xr-xt/t7811-grep-open.sh18
-rwxr-xr-xt/t8010-cat-file-filters.sh2
-rwxr-xr-xt/t9011-svn-da.sh14
-rwxr-xr-xt/t9131-git-svn-empty-symlink.sh6
-rwxr-xr-xt/t9135-git-svn-moved-branch-empty-file.sh3
-rwxr-xr-xt/t9200-git-cvsexportcommit.sh4
-rwxr-xr-xt/t9802-git-p4-filetype.sh2
-rwxr-xr-xt/t9903-bash-prompt.sh6
-rw-r--r--t/test-lib-functions.sh2
-rw-r--r--t/test-lib.sh10
125 files changed, 4168 insertions, 425 deletions
diff --git a/t/README b/t/README
index 8373a27fea..204b9f4cc5 100644
--- a/t/README
+++ b/t/README
@@ -315,6 +315,14 @@ packs on demand. This normally only happens when the object size is
over 2GB. This variable forces the code path on any object larger than
<n> bytes.
+GIT_TEST_OE_DELTA_SIZE=<n> exercises the uncomon pack-objects code
+path where deltas larger than this limit require extra memory
+allocation for bookkeeping.
+
+GIT_TEST_VALIDATE_INDEX_CACHE_ENTRIES=<boolean> checks that cache-tree
+records are valid when the index is written out or after a merge. This
+is mostly to catch missing invalidation. Default is true.
+
Naming Tests
------------
diff --git a/t/chainlint.sed b/t/chainlint.sed
index 8544df38df..70df40e34b 100644
--- a/t/chainlint.sed
+++ b/t/chainlint.sed
@@ -94,14 +94,14 @@
# here-doc -- swallow it to avoid false hits within its body (but keep the
# command to which it was attached)
-/<<[ ]*[-\\']*[A-Za-z0-9_]/ {
- s/^\(.*\)<<[ ]*[-\\']*\([A-Za-z0-9_][A-Za-z0-9_]*\)'*/<\2>\1<</
+/<<[ ]*[-\\'"]*[A-Za-z0-9_]/ {
+ s/^\(.*\)<<[ ]*[-\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\2>\1<</
s/[ ]*<<//
- :hereslurp
+ :hered
N
/^<\([^>]*\)>.*\n[ ]*\1[ ]*$/!{
s/\n.*$//
- bhereslurp
+ bhered
}
s/^<[^>]*>//
s/\n.*$//
@@ -131,9 +131,8 @@ b
b
:subshell
-# bare "(" line?
+# bare "(" line? -- stash for later printing
/^[ ]*([ ]*$/ {
- # stash for later printing
h
bnextline
}
@@ -150,7 +149,7 @@ s/.*\n//
:slurp
# incomplete line "...\"
-/\\$/bincomplete
+/\\$/bicmplte
# multi-line quoted string "...\n..."?
/"/bdqstring
# multi-line quoted string '...\n...'? (but not contraction in string "it's")
@@ -159,7 +158,7 @@ s/.*\n//
}
:folded
# here-doc -- swallow it
-/<<[ ]*[-\\']*[A-Za-z0-9_]/bheredoc
+/<<[ ]*[-\\'"]*[A-Za-z0-9_]/bheredoc
# comment or empty line -- discard since final non-comment, non-empty line
# before closing ")", "done", "elsif", "else", or "fi" will need to be
# re-visited to drop "suspect" marking since final line of those constructs
@@ -172,7 +171,7 @@ s/.*\n//
/"[^"]*#[^"]*"/!s/[ ]#.*$//
}
# one-liner "case ... esac"
-/^[ ]*case[ ]*..*esac/bcheckchain
+/^[ ]*case[ ]*..*esac/bchkchn
# multi-line "case ... esac"
/^[ ]*case[ ]..*[ ]in/bcase
# multi-line "for ... done" or "while ... done"
@@ -201,32 +200,32 @@ s/.*\n//
/^[ ]*fi[ ]*[<>|]/bdone
/^[ ]*fi[ ]*)/bdone
# nested one-liner "(...) &&"
-/^[ ]*(.*)[ ]*&&[ ]*$/bcheckchain
+/^[ ]*(.*)[ ]*&&[ ]*$/bchkchn
# nested one-liner "(...)"
-/^[ ]*(.*)[ ]*$/bcheckchain
+/^[ ]*(.*)[ ]*$/bchkchn
# nested one-liner "(...) >x" (or "2>x" or "<x" or "|x")
-/^[ ]*(.*)[ ]*[0-9]*[<>|]/bcheckchain
+/^[ ]*(.*)[ ]*[0-9]*[<>|]/bchkchn
# nested multi-line "(...\n...)"
/^[ ]*(/bnest
# multi-line "{...\n...}"
/^[ ]*{/bblock
# closing ")" on own line -- exit subshell
-/^[ ]*)/bclosesolo
+/^[ ]*)/bclssolo
# "$((...))" -- arithmetic expansion; not closing ")"
-/\$(([^)][^)]*))[^)]*$/bcheckchain
+/\$(([^)][^)]*))[^)]*$/bchkchn
# "$(...)" -- command substitution; not closing ")"
-/\$([^)][^)]*)[^)]*$/bcheckchain
+/\$([^)][^)]*)[^)]*$/bchkchn
# multi-line "$(...\n...)" -- command substitution; treat as nested subshell
/\$([^)]*$/bnest
# "=(...)" -- Bash array assignment; not closing ")"
-/=(/bcheckchain
+/=(/bchkchn
# closing "...) &&"
/)[ ]*&&[ ]*$/bclose
# closing "...)"
/)[ ]*$/bclose
# closing "...) >x" (or "2>x" or "<x" or "|x")
/)[ ]*[<>|]/bclose
-:checkchain
+:chkchn
# mark suspect if line uses ";" internally rather than "&&" (but not ";" in a
# string and not ";;" in one-liner "case...esac")
/;/{
@@ -245,7 +244,7 @@ n
bslurp
# found incomplete line "...\" -- slurp up next line
-:incomplete
+:icmplte
N
s/\\\n//
bslurp
@@ -281,13 +280,13 @@ bfolded
# found here-doc -- swallow it to avoid false hits within its body (but keep
# the command to which it was attached)
:heredoc
-s/^\(.*\)<<[ ]*[-\\']*\([A-Za-z0-9_][A-Za-z0-9_]*\)'*/<\2>\1<</
+s/^\(.*\)<<[ ]*[-\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\2>\1<</
s/[ ]*<<//
-:hereslurpsub
+:heredsub
N
/^<\([^>]*\)>.*\n[ ]*\1[ ]*$/!{
s/\n.*$//
- bhereslurpsub
+ bheredsub
}
s/^<[^>]*>//
s/\n.*$//
@@ -317,43 +316,43 @@ x
# is 'done' or 'fi' cuddled with ")" to close subshell?
/done.*)/bclose
/fi.*)/bclose
-bcheckchain
+bchkchn
# found nested multi-line "(...\n...)" -- pass through untouched
:nest
x
-:nestslurp
+:nstslurp
n
# closing ")" on own line -- stop nested slurp
-/^[ ]*)/bnestclose
+/^[ ]*)/bnstclose
# comment -- not closing ")" if in comment
-/^[ ]*#/bnestcontinue
+/^[ ]*#/bnstcnt
# "$((...))" -- arithmetic expansion; not closing ")"
-/\$(([^)][^)]*))[^)]*$/bnestcontinue
+/\$(([^)][^)]*))[^)]*$/bnstcnt
# "$(...)" -- command substitution; not closing ")"
-/\$([^)][^)]*)[^)]*$/bnestcontinue
+/\$([^)][^)]*)[^)]*$/bnstcnt
# closing "...)" -- stop nested slurp
-/)/bnestclose
-:nestcontinue
+/)/bnstclose
+:nstcnt
x
-bnestslurp
-:nestclose
+bnstslurp
+:nstclose
s/^/>>/
# is it "))" which closes nested and parent subshells?
/)[ ]*)/bslurp
-bcheckchain
+bchkchn
# found multi-line "{...\n...}" block -- pass through untouched
:block
x
n
# closing "}" -- stop block slurp
-/}/bcheckchain
+/}/bchkchn
bblock
# found closing ")" on own line -- drop "suspect" from final line of subshell
# since that line legitimately lacks "&&" and exit subshell loop
-:closesolo
+:clssolo
x
s/?!AMP?!//
p
diff --git a/t/chainlint/here-doc.expect b/t/chainlint/here-doc.expect
index aff6568716..534b065e38 100644
--- a/t/chainlint/here-doc.expect
+++ b/t/chainlint/here-doc.expect
@@ -4,4 +4,6 @@ cat >foo &&
cat >bar &&
+cat >boo &&
+
horticulture
diff --git a/t/chainlint/here-doc.test b/t/chainlint/here-doc.test
index f2bb14b693..ad4ce8afd9 100644
--- a/t/chainlint/here-doc.test
+++ b/t/chainlint/here-doc.test
@@ -21,6 +21,13 @@ boz
woz
FUMP
+# LINT: swallow "quoted" here-doc
+cat <<"zump" >boo &&
+snoz
+boz
+woz
+zump
+
# LINT: swallow here-doc (EOF is last line of test)
horticulture <<\EOF
gomez
diff --git a/t/chainlint/subshell-here-doc.expect b/t/chainlint/subshell-here-doc.expect
index 7663ea7fc4..74723e7340 100644
--- a/t/chainlint/subshell-here-doc.expect
+++ b/t/chainlint/subshell-here-doc.expect
@@ -6,5 +6,6 @@
(
cat >bup &&
cat >bup2 &&
+ cat >bup3 &&
meep
>)
diff --git a/t/chainlint/subshell-here-doc.test b/t/chainlint/subshell-here-doc.test
index b6b5a9b33a..f6b3ba4214 100644
--- a/t/chainlint/subshell-here-doc.test
+++ b/t/chainlint/subshell-here-doc.test
@@ -31,5 +31,9 @@
glink
FIZZ
ARBITRARY2
+ cat <<-"ARBITRARY3" >bup3 &&
+ glink
+ FIZZ
+ ARBITRARY3
meep
)
diff --git a/t/check-non-portable-shell.pl b/t/check-non-portable-shell.pl
index d5823f71d8..b45bdac688 100755
--- a/t/check-non-portable-shell.pl
+++ b/t/check-non-portable-shell.pl
@@ -41,6 +41,9 @@ while (<>) {
/^\s*[^#]\s*which\s/ and err 'which is not portable (use type)';
/\btest\s+[^=]*==/ and err '"test a == b" is not portable (use =)';
/\bwc -l.*"\s*=/ and err '`"$(wc -l)"` is not portable (use test_line_count)';
+ /\bhead\s+-c\b/ and err 'head -c is not portable (use test_copy_bytes BYTES <file >out)';
+ /(?:\$\(seq|^\s*seq\b)/ and err 'seq is not portable (use test_seq)';
+ /\bgrep\b.*--file\b/ and err 'grep --file FILE is not portable (use grep -f FILE)';
/\bexport\s+[A-Za-z0-9_]*=/ and err '"export FOO=bar" is not portable (use FOO=bar && export FOO)';
/^\s*([A-Z0-9_]+=(\w+|(["']).*?\3)\s+)+(\w+)/ and exists($func{$4}) and
err '"FOO=bar shell_func" assignment extends beyond "shell_func"';
diff --git a/t/helper/test-dump-cache-tree.c b/t/helper/test-dump-cache-tree.c
index 98a4891f1d..6a3f88f5f5 100644
--- a/t/helper/test-dump-cache-tree.c
+++ b/t/helper/test-dump-cache-tree.c
@@ -33,7 +33,7 @@ static int dump_cache_tree(struct cache_tree *it,
}
else {
dump_one(it, pfx, "");
- if (oidcmp(&it->oid, &ref->oid) ||
+ if (!oideq(&it->oid, &ref->oid) ||
ref->entry_count != it->entry_count ||
ref->subtree_nr != it->subtree_nr) {
/* claims to be valid but is lying */
diff --git a/t/helper/test-reach.c b/t/helper/test-reach.c
new file mode 100644
index 0000000000..eb21103998
--- /dev/null
+++ b/t/helper/test-reach.c
@@ -0,0 +1,130 @@
+#include "test-tool.h"
+#include "cache.h"
+#include "commit.h"
+#include "commit-reach.h"
+#include "config.h"
+#include "parse-options.h"
+#include "ref-filter.h"
+#include "string-list.h"
+#include "tag.h"
+
+static void print_sorted_commit_ids(struct commit_list *list)
+{
+ int i;
+ struct string_list s = STRING_LIST_INIT_DUP;
+
+ while (list) {
+ string_list_append(&s, oid_to_hex(&list->item->object.oid));
+ list = list->next;
+ }
+
+ string_list_sort(&s);
+
+ for (i = 0; i < s.nr; i++)
+ printf("%s\n", s.items[i].string);
+
+ string_list_clear(&s, 0);
+}
+
+int cmd__reach(int ac, const char **av)
+{
+ struct object_id oid_A, oid_B;
+ struct commit *A, *B;
+ struct commit_list *X, *Y;
+ struct commit **X_array;
+ int X_nr, X_alloc;
+ struct strbuf buf = STRBUF_INIT;
+ struct repository *r = the_repository;
+
+ setup_git_directory();
+
+ if (ac < 2)
+ exit(1);
+
+ A = B = NULL;
+ X = Y = NULL;
+ X_nr = 0;
+ X_alloc = 16;
+ ALLOC_ARRAY(X_array, X_alloc);
+
+ while (strbuf_getline(&buf, stdin) != EOF) {
+ struct object_id oid;
+ struct object *o;
+ struct commit *c;
+ if (buf.len < 3)
+ continue;
+
+ if (get_oid_committish(buf.buf + 2, &oid))
+ die("failed to resolve %s", buf.buf + 2);
+
+ o = parse_object(r, &oid);
+ o = deref_tag_noverify(o);
+
+ if (!o)
+ die("failed to load commit for input %s resulting in oid %s\n",
+ buf.buf, oid_to_hex(&oid));
+
+ c = object_as_type(r, o, OBJ_COMMIT, 0);
+
+ if (!c)
+ die("failed to load commit for input %s resulting in oid %s\n",
+ buf.buf, oid_to_hex(&oid));
+
+ switch (buf.buf[0]) {
+ case 'A':
+ oidcpy(&oid_A, &oid);
+ A = c;
+ break;
+
+ case 'B':
+ oidcpy(&oid_B, &oid);
+ B = c;
+ break;
+
+ case 'X':
+ commit_list_insert(c, &X);
+ ALLOC_GROW(X_array, X_nr + 1, X_alloc);
+ X_array[X_nr++] = c;
+ break;
+
+ case 'Y':
+ commit_list_insert(c, &Y);
+ break;
+
+ default:
+ die("unexpected start of line: %c", buf.buf[0]);
+ }
+ }
+ strbuf_release(&buf);
+
+ if (!strcmp(av[1], "ref_newer"))
+ printf("%s(A,B):%d\n", av[1], ref_newer(&oid_A, &oid_B));
+ else if (!strcmp(av[1], "in_merge_bases"))
+ printf("%s(A,B):%d\n", av[1], in_merge_bases(A, B));
+ else if (!strcmp(av[1], "is_descendant_of"))
+ printf("%s(A,X):%d\n", av[1], is_descendant_of(A, X));
+ else if (!strcmp(av[1], "get_merge_bases_many")) {
+ struct commit_list *list = get_merge_bases_many(A, X_nr, X_array);
+ printf("%s(A,X):\n", av[1]);
+ print_sorted_commit_ids(list);
+ } else if (!strcmp(av[1], "reduce_heads")) {
+ struct commit_list *list = reduce_heads(X);
+ printf("%s(X):\n", av[1]);
+ print_sorted_commit_ids(list);
+ } else if (!strcmp(av[1], "can_all_from_reach")) {
+ printf("%s(X,Y):%d\n", av[1], can_all_from_reach(X, Y, 1));
+ } else if (!strcmp(av[1], "commit_contains")) {
+ struct ref_filter filter;
+ struct contains_cache cache;
+ init_contains_cache(&cache);
+
+ if (ac > 2 && !strcmp(av[2], "--tag"))
+ filter.with_commit_tag_algo = 1;
+ else
+ filter.with_commit_tag_algo = 0;
+
+ printf("%s(_,A,X,_):%d\n", av[1], commit_contains(&filter, A, X, &cache));
+ }
+
+ exit(0);
+}
diff --git a/t/helper/test-read-midx.c b/t/helper/test-read-midx.c
new file mode 100644
index 0000000000..831b586d02
--- /dev/null
+++ b/t/helper/test-read-midx.c
@@ -0,0 +1,51 @@
+#include "test-tool.h"
+#include "cache.h"
+#include "midx.h"
+#include "repository.h"
+#include "object-store.h"
+
+static int read_midx_file(const char *object_dir)
+{
+ uint32_t i;
+ struct multi_pack_index *m = load_multi_pack_index(object_dir, 1);
+
+ if (!m)
+ return 1;
+
+ printf("header: %08x %d %d %d\n",
+ m->signature,
+ m->version,
+ m->num_chunks,
+ m->num_packs);
+
+ printf("chunks:");
+
+ if (m->chunk_pack_names)
+ printf(" pack-names");
+ if (m->chunk_oid_fanout)
+ printf(" oid-fanout");
+ if (m->chunk_oid_lookup)
+ printf(" oid-lookup");
+ if (m->chunk_object_offsets)
+ printf(" object-offsets");
+ if (m->chunk_large_offsets)
+ printf(" large-offsets");
+
+ printf("\nnum_objects: %d\n", m->num_objects);
+
+ printf("packs:\n");
+ for (i = 0; i < m->num_packs; i++)
+ printf("%s\n", m->pack_names[i]);
+
+ printf("object-dir: %s\n", m->object_dir);
+
+ return 0;
+}
+
+int cmd__read_midx(int argc, const char **argv)
+{
+ if (argc != 2)
+ usage("read-midx <object-dir>");
+
+ return read_midx_file(argv[1]);
+}
diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c
index 0edafcfd65..bef50c4dcc 100644
--- a/t/helper/test-tool.c
+++ b/t/helper/test-tool.c
@@ -27,7 +27,9 @@ static struct test_cmd cmds[] = {
{ "online-cpus", cmd__online_cpus },
{ "path-utils", cmd__path_utils },
{ "prio-queue", cmd__prio_queue },
+ { "reach", cmd__reach },
{ "read-cache", cmd__read_cache },
+ { "read-midx", cmd__read_midx },
{ "ref-store", cmd__ref_store },
{ "regex", cmd__regex },
{ "repository", cmd__repository },
diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h
index e926c416ea..321982e4bc 100644
--- a/t/helper/test-tool.h
+++ b/t/helper/test-tool.h
@@ -1,6 +1,8 @@
#ifndef __TEST_TOOL_H__
#define __TEST_TOOL_H__
+#include "git-compat-util.h"
+
int cmd__chmtime(int argc, const char **argv);
int cmd__config(int argc, const char **argv);
int cmd__ctype(int argc, const char **argv);
@@ -21,7 +23,9 @@ int cmd__mktemp(int argc, const char **argv);
int cmd__online_cpus(int argc, const char **argv);
int cmd__path_utils(int argc, const char **argv);
int cmd__prio_queue(int argc, const char **argv);
+int cmd__reach(int argc, const char **argv);
int cmd__read_cache(int argc, const char **argv);
+int cmd__read_midx(int argc, const char **argv);
int cmd__ref_store(int argc, const char **argv);
int cmd__regex(int argc, const char **argv);
int cmd__repository(int argc, const char **argv);
diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh
index 25a77ee5cb..2ca9fb69d6 100644
--- a/t/lib-rebase.sh
+++ b/t/lib-rebase.sh
@@ -14,8 +14,8 @@
# specified line.
#
# "<cmd> <lineno>" -- add a line with the specified command
-# ("squash", "fixup", "edit", "reword" or "drop") and the SHA1 taken
-# from the specified line.
+# ("pick", "squash", "fixup", "edit", "reword" or "drop") and the
+# SHA1 taken from the specified line.
#
# "exec_cmd_with_args" -- add an "exec cmd with args" line.
#
@@ -47,7 +47,7 @@ set_fake_editor () {
action=pick
for line in $FAKE_LINES; do
case $line in
- squash|fixup|edit|reword|drop)
+ pick|squash|fixup|edit|reword|drop)
action="$line";;
exec*)
echo "$line" | sed 's/_/ /g' >> "$1";;
diff --git a/t/lib-submodule-update.sh b/t/lib-submodule-update.sh
index 5b56b23166..016391723c 100755
--- a/t/lib-submodule-update.sh
+++ b/t/lib-submodule-update.sh
@@ -235,7 +235,7 @@ reset_work_tree_to_interested () {
then
mkdir -p submodule_update/.git/modules/sub1/modules &&
cp -r submodule_update_repo/.git/modules/sub1/modules/sub2 submodule_update/.git/modules/sub1/modules/sub2
- # core.worktree is unset for sub2 as it is not checked out
+ GIT_WORK_TREE=. git -C submodule_update/.git/modules/sub1/modules/sub2 config --unset core.worktree
fi &&
# indicate we are interested in the submodule:
git -C submodule_update config submodule.sub1.url "bogus" &&
@@ -709,8 +709,7 @@ test_submodule_recursing_with_args_common() {
git branch -t remove_sub1 origin/remove_sub1 &&
$command remove_sub1 &&
test_superproject_content origin/remove_sub1 &&
- ! test -e sub1 &&
- test_must_fail git config -f .git/modules/sub1/config core.worktree
+ ! test -e sub1
)
'
# ... absorbing a .git directory along the way.
diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh
index 34859fe4a5..850f651e4e 100755
--- a/t/t0000-basic.sh
+++ b/t/t0000-basic.sh
@@ -116,7 +116,7 @@ check_sub_test_lib_test () {
name="$1" # stdin is the expected output from the test
(
cd "$name" &&
- ! test -s err &&
+ test_must_be_empty err &&
sed -e 's/^> //' -e 's/Z$//' >expect &&
test_cmp expect out
)
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index ca85aae51e..182da069f1 100755
--- a/t/t0001-init.sh
+++ b/t/t0001-init.sh
@@ -167,9 +167,8 @@ test_expect_success 'reinit' '
) &&
test_i18ngrep "Initialized empty" again/out1 &&
test_i18ngrep "Reinitialized existing" again/out2 &&
- >again/empty &&
- test_i18ncmp again/empty again/err1 &&
- test_i18ncmp again/empty again/err2
+ test_must_be_empty again/err1 &&
+ test_must_be_empty again/err2
'
test_expect_success 'init with --template' '
diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh
index 5c37c2e1f8..22499bce5f 100755
--- a/t/t0003-attributes.sh
+++ b/t/t0003-attributes.sh
@@ -208,9 +208,8 @@ test_expect_success 'attribute test: --all option' '
'
test_expect_success 'attribute test: --cached option' '
- : >empty &&
git check-attr --cached --stdin --all <stdin-all | sort >actual &&
- test_cmp empty actual &&
+ test_must_be_empty actual &&
git add .gitattributes a/.gitattributes a/b/.gitattributes &&
git check-attr --cached --stdin --all <stdin-all | sort >actual &&
test_cmp specified-all actual
diff --git a/t/t0019/parse_json.perl b/t/t0019/parse_json.perl
index ca4e5bfa78..fea87fb81b 100644
--- a/t/t0019/parse_json.perl
+++ b/t/t0019/parse_json.perl
@@ -34,6 +34,9 @@ sub dump_item {
} elsif (ref($value) eq 'HASH') {
print "$label_in hash\n";
dump_hash($label_in, $value);
+ } elsif (ref $value) {
+ my $bool = $value ? 1 : 0;
+ print "$label_in $bool\n";
} elsif (defined $value) {
print "$label_in $value\n";
} else {
diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh
index 5f056982a5..854da0ae16 100755
--- a/t/t0020-crlf.sh
+++ b/t/t0020-crlf.sh
@@ -160,7 +160,7 @@ test_expect_success 'checkout with autocrlf=input' '
git config core.autocrlf input &&
git read-tree --reset -u HEAD &&
test_must_fail has_cr one &&
- test_must_fail has_cr two &&
+ test_must_fail has_cr dir/two &&
git update-index -- one dir/two &&
test "$one" = $(git hash-object --stdin <one) &&
test "$two" = $(git hash-object --stdin <dir/two) &&
diff --git a/t/t0028-working-tree-encoding.sh b/t/t0028-working-tree-encoding.sh
index 12b8eb963a..7e87b5a200 100755
--- a/t/t0028-working-tree-encoding.sh
+++ b/t/t0028-working-tree-encoding.sh
@@ -203,7 +203,11 @@ test_expect_success 'error if encoding garbage is already in Git' '
test_i18ngrep "error: BOM is required" err.out
'
-test_expect_success 'check roundtrip encoding' '
+test_lazy_prereq ICONV_SHIFT_JIS '
+ iconv -f UTF-8 -t SHIFT-JIS </dev/null
+'
+
+test_expect_success ICONV_SHIFT_JIS 'check roundtrip encoding' '
test_when_finished "rm -f roundtrip.shift roundtrip.utf16" &&
test_when_finished "git reset --hard HEAD" &&
diff --git a/t/t0030-stripspace.sh b/t/t0030-stripspace.sh
index b77948c618..5ce47e8af5 100755
--- a/t/t0030-stripspace.sh
+++ b/t/t0030-stripspace.sh
@@ -320,22 +320,20 @@ test_expect_success \
test_expect_success \
'spaces with newline at end should be replaced with empty string' '
- printf "" >expect &&
-
echo | git stripspace >actual &&
- test_cmp expect actual &&
+ test_must_be_empty actual &&
echo "$sss" | git stripspace >actual &&
- test_cmp expect actual &&
+ test_must_be_empty actual &&
echo "$sss$sss" | git stripspace >actual &&
- test_cmp expect actual &&
+ test_must_be_empty actual &&
echo "$sss$sss$sss" | git stripspace >actual &&
- test_cmp expect actual &&
+ test_must_be_empty actual &&
echo "$sss$sss$sss$sss" | git stripspace >actual &&
- test_cmp expect actual
+ test_must_be_empty actual
'
test_expect_success \
@@ -349,19 +347,17 @@ test_expect_success \
test_expect_success \
'spaces without newline at end should be replaced with empty string' '
- printf "" >expect &&
-
printf "" | git stripspace >actual &&
- test_cmp expect actual &&
+ test_must_be_empty actual &&
printf "$sss$sss" | git stripspace >actual &&
- test_cmp expect actual &&
+ test_must_be_empty actual &&
printf "$sss$sss$sss" | git stripspace >actual &&
- test_cmp expect actual &&
+ test_must_be_empty actual &&
printf "$sss$sss$sss$sss" | git stripspace >actual &&
- test_cmp expect actual
+ test_must_be_empty actual
'
test_expect_success \
diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh
index 04d474c84f..5b0560fa20 100755
--- a/t/t0040-parse-options.sh
+++ b/t/t0040-parse-options.sh
@@ -286,11 +286,9 @@ test_expect_success 'OPT_CALLBACK() and OPT_BIT() work' '
test_cmp expect output
'
->expect
-
test_expect_success 'OPT_CALLBACK() and callback errors work' '
test_must_fail test-parse-options --no-length >output 2>output.err &&
- test_i18ncmp expect output &&
+ test_must_be_empty output &&
test_must_be_empty output.err
'
diff --git a/t/t0061-run-command.sh b/t/t0061-run-command.sh
index c887ed5b45..3e131c5325 100755
--- a/t/t0061-run-command.sh
+++ b/t/t0061-run-command.sh
@@ -11,7 +11,6 @@ cat >hello-script <<-EOF
#!$SHELL_PATH
cat hello-script
EOF
->empty
test_expect_success 'start_command reports ENOENT' '
test-tool run-command start-command-ENOENT ./does-not-exist
@@ -23,7 +22,7 @@ test_expect_success 'run_command can run a command' '
test-tool run-command run-command ./hello.sh >actual 2>err &&
test_cmp hello-script actual &&
- test_cmp empty err
+ test_must_be_empty err
'
test_expect_success !MINGW 'run_command can run a script without a #! line' '
@@ -34,7 +33,7 @@ test_expect_success !MINGW 'run_command can run a script without a #! line' '
test-tool run-command run-command ./hello >actual 2>err &&
test_cmp hello-script actual &&
- test_cmp empty err
+ test_must_be_empty err
'
test_expect_success 'run_command does not try to execute a directory' '
@@ -47,7 +46,7 @@ test_expect_success 'run_command does not try to execute a directory' '
PATH=$PWD/bin1:$PWD/bin2:$PATH \
test-tool run-command run-command greet >actual 2>err &&
test_cmp bin2/greet actual &&
- test_cmp empty err
+ test_must_be_empty err
'
test_expect_success POSIXPERM 'run_command passes over non-executable file' '
@@ -64,7 +63,7 @@ test_expect_success POSIXPERM 'run_command passes over non-executable file' '
PATH=$PWD/bin1:$PWD/bin2:$PATH \
test-tool run-command run-command greet >actual 2>err &&
test_cmp bin2/greet actual &&
- test_cmp empty err
+ test_must_be_empty err
'
test_expect_success POSIXPERM 'run_command reports EACCES' '
diff --git a/t/t0090-cache-tree.sh b/t/t0090-cache-tree.sh
index 28ea93f509..7de40141ca 100755
--- a/t/t0090-cache-tree.sh
+++ b/t/t0090-cache-tree.sh
@@ -239,7 +239,7 @@ test_expect_success 'no phantom error when switching trees' '
>newdir/one &&
git add newdir/one &&
git checkout 2>errors &&
- ! test -s errors
+ test_must_be_empty errors
'
test_expect_success 'switching trees does not invalidate shared index' '
diff --git a/t/t0203-gettext-setlocale-sanity.sh b/t/t0203-gettext-setlocale-sanity.sh
index 71b0d74b4d..0ce1f22eff 100755
--- a/t/t0203-gettext-setlocale-sanity.sh
+++ b/t/t0203-gettext-setlocale-sanity.sh
@@ -11,7 +11,7 @@ test_expect_success 'git show a ISO-8859-1 commit under C locale' '
. "$TEST_DIRECTORY"/t3901/8859-1.txt &&
test_commit "iso-c-commit" iso-under-c &&
git show >out 2>err &&
- ! test -s err &&
+ test_must_be_empty err &&
grep -q "iso-c-commit" out
'
@@ -19,7 +19,7 @@ test_expect_success GETTEXT_LOCALE 'git show a ISO-8859-1 commit under a UTF-8 l
. "$TEST_DIRECTORY"/t3901/8859-1.txt &&
test_commit "iso-utf8-commit" iso-under-utf8 &&
LANGUAGE=is LC_ALL="$is_IS_locale" git show >out 2>err &&
- ! test -s err &&
+ test_must_be_empty err &&
grep -q "iso-utf8-commit" out
'
diff --git a/t/t1090-sparse-checkout-scope.sh b/t/t1090-sparse-checkout-scope.sh
index 1f61eb3e88..25d7c700f6 100755
--- a/t/t1090-sparse-checkout-scope.sh
+++ b/t/t1090-sparse-checkout-scope.sh
@@ -31,6 +31,20 @@ test_expect_success 'perform sparse checkout of master' '
test_path_is_file c
'
+test_expect_success 'checkout -b checkout.optimizeNewBranch interaction' '
+ cp .git/info/sparse-checkout .git/info/sparse-checkout.bak &&
+ test_when_finished "
+ mv -f .git/info/sparse-checkout.bak .git/info/sparse-checkout
+ git checkout master
+ " &&
+ echo "/b" >>.git/info/sparse-checkout &&
+ test "$(git ls-files -t b)" = "S b" &&
+ git -c checkout.optimizeNewBranch=true checkout -b fast &&
+ test "$(git ls-files -t b)" = "S b" &&
+ git checkout -b slow &&
+ test "$(git ls-files -t b)" = "H b"
+'
+
test_expect_success 'merge feature branch into sparse checkout of master' '
git merge feature &&
test_path_is_file a &&
diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index 4976e2fcd3..cdf1fed5d1 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -346,12 +346,9 @@ test_expect_success 'working --list' '
git config --list > output &&
test_cmp expect output
'
-cat > expect << EOF
-EOF
-
test_expect_success '--list without repo produces empty output' '
git --git-dir=nonexistent config --list >output &&
- test_cmp expect output
+ test_must_be_empty output
'
cat > expect << EOF
diff --git a/t/t1404-update-ref-errors.sh b/t/t1404-update-ref-errors.sh
index 2a42a589a4..51a4f4c0ac 100755
--- a/t/t1404-update-ref-errors.sh
+++ b/t/t1404-update-ref-errors.sh
@@ -559,9 +559,9 @@ test_expect_success 'no bogus intermediate values during delete' '
{
# Note: the following command is intentionally run in the
# background. We increase the timeout so that `update-ref`
- # attempts to acquire the `packed-refs` lock for longer than
- # it takes for us to do the check then delete it:
- git -c core.packedrefstimeout=3000 update-ref -d $prefix/foo &
+ # attempts to acquire the `packed-refs` lock for much longer
+ # than it takes for us to do the check then delete it:
+ git -c core.packedrefstimeout=30000 update-ref -d $prefix/foo &
} &&
pid2=$! &&
# Give update-ref plenty of time to get to the point where it tries
diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh
index 8293131001..388b0611d8 100755
--- a/t/t1410-reflog.sh
+++ b/t/t1410-reflog.sh
@@ -290,9 +290,8 @@ test_expect_success 'stale dirs do not cause d/f conflicts (reflogs off)' '
# same as before, but we only create a reflog for "one" if
# it already exists, which it does not
git -c core.logallrefupdates=false branch one master &&
- : >expect &&
git log -g --format="%gd %gs" one >actual &&
- test_cmp expect actual
+ test_must_be_empty actual
'
# Triggering the bug detected by this test requires a newline to fall
diff --git a/t/t1411-reflog-show.sh b/t/t1411-reflog-show.sh
index 4d62ceef9c..985daf1def 100755
--- a/t/t1411-reflog-show.sh
+++ b/t/t1411-reflog-show.sh
@@ -136,13 +136,12 @@ test_expect_success '--date magic does not override explicit @{0} syntax' '
test_cmp expect actual
'
-: >expect
test_expect_success 'empty reflog file' '
git branch empty &&
git reflog expire --expire=all refs/heads/empty &&
git log -g empty >actual &&
- test_cmp expect actual
+ test_must_be_empty actual
'
# This guards against the alternative of showing the diffs vs. the
diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh
index 7b7602ddb4..0f2dd26f74 100755
--- a/t/t1450-fsck.sh
+++ b/t/t1450-fsck.sh
@@ -16,8 +16,7 @@ test_expect_success setup '
git checkout HEAD^0 &&
test_commit B fileB two &&
git tag -d A B &&
- git reflog expire --expire=now --all &&
- >empty
+ git reflog expire --expire=now --all
'
test_expect_success 'loose objects borrowed from alternate are not missing' '
@@ -29,12 +28,12 @@ test_expect_success 'loose objects borrowed from alternate are not missing' '
test_commit C fileC one &&
git fsck --no-dangling >../actual 2>&1
) &&
- test_cmp empty actual
+ test_must_be_empty actual
'
test_expect_success 'HEAD is part of refs, valid objects appear valid' '
git fsck >actual 2>&1 &&
- test_cmp empty actual
+ test_must_be_empty actual
'
# Corruption tests follow. Make sure to remove all traces of the
@@ -346,12 +345,12 @@ test_expect_success 'tag with NUL in header' '
test_expect_success 'cleaned up' '
git fsck >actual 2>&1 &&
- test_cmp empty actual
+ test_must_be_empty actual
'
test_expect_success 'rev-list --verify-objects' '
git rev-list --verify-objects --all >/dev/null 2>out &&
- test_cmp empty out
+ test_must_be_empty out
'
test_expect_success 'rev-list --verify-objects with bad sha1' '
diff --git a/t/t1501-work-tree.sh b/t/t1501-work-tree.sh
index afcdfafe45..3498d3d55e 100755
--- a/t/t1501-work-tree.sh
+++ b/t/t1501-work-tree.sh
@@ -41,7 +41,7 @@ test_expect_success 'setup: helper for testing rev-parse' '
# rev-parse --show-prefix should output
# a single newline when at the top of the work tree,
# but we test for that separately.
- test -z "$4" && ! test -s actual.prefix ||
+ test -z "$4" && test_must_be_empty actual.prefix ||
test_cmp expected.prefix actual.prefix
fi
}
diff --git a/t/t1510-repo-setup.sh b/t/t1510-repo-setup.sh
index 972bd9c785..9974457f56 100755
--- a/t/t1510-repo-setup.sh
+++ b/t/t1510-repo-setup.sh
@@ -234,14 +234,14 @@ test_expect_success '#0: nonbare repo, no explicit configuration' '
try_repo 0 unset unset unset "" unset \
.git "$here/0" "$here/0" "(null)" \
.git "$here/0" "$here/0" sub/ 2>message &&
- ! test -s message
+ test_must_be_empty message
'
test_expect_success '#1: GIT_WORK_TREE without explicit GIT_DIR is accepted' '
try_repo 1 "$here" unset unset "" unset \
"$here/1/.git" "$here" "$here" 1/ \
"$here/1/.git" "$here" "$here" 1/sub/ 2>message &&
- ! test -s message
+ test_must_be_empty message
'
test_expect_success '#2: worktree defaults to cwd with explicit GIT_DIR' '
@@ -268,7 +268,7 @@ test_expect_success '#4: core.worktree without GIT_DIR set is accepted' '
try_case 4 unset unset \
.git "$here/4/sub" "$here/4" "(null)" \
"$here/4/.git" "$here/4/sub" "$here/4/sub" "(null)" 2>message &&
- ! test -s message
+ test_must_be_empty message
'
test_expect_success '#5: core.worktree + GIT_WORK_TREE is accepted' '
@@ -279,7 +279,7 @@ test_expect_success '#5: core.worktree + GIT_WORK_TREE is accepted' '
try_repo 5a .. unset "$here/5a" "" unset \
"$here/5a/.git" "$here" "$here" 5a/ \
"$here/5a/.git" "$here/5a" "$here/5a" sub/ &&
- ! test -s message
+ test_must_be_empty message
'
test_expect_success '#6: setting GIT_DIR brings core.worktree to life' '
@@ -376,7 +376,7 @@ test_expect_success '#9: GIT_WORK_TREE accepted with gitfile' '
try_repo 9 wt unset unset gitfile unset \
"$here/9.git" "$here/9/wt" "$here/9" "(null)" \
"$here/9.git" "$here/9/sub/wt" "$here/9/sub" "(null)" 2>message &&
- ! test -s message
+ test_must_be_empty message
'
test_expect_success '#10: GIT_DIR can point to gitfile' '
@@ -402,7 +402,7 @@ test_expect_success '#12: core.worktree with gitfile is accepted' '
try_repo 12 unset unset "$here/12" gitfile unset \
"$here/12.git" "$here/12" "$here/12" "(null)" \
"$here/12.git" "$here/12" "$here/12" sub/ 2>message &&
- ! test -s message
+ test_must_be_empty message
'
test_expect_success '#13: core.worktree+GIT_WORK_TREE accepted (with gitfile)' '
@@ -410,7 +410,7 @@ test_expect_success '#13: core.worktree+GIT_WORK_TREE accepted (with gitfile)' '
try_repo 13 non-existent-too unset non-existent gitfile unset \
"$here/13.git" "$here/13/non-existent-too" "$here/13" "(null)" \
"$here/13.git" "$here/13/sub/non-existent-too" "$here/13/sub" "(null)" 2>message &&
- ! test -s message
+ test_must_be_empty message
'
# case #14.
@@ -565,7 +565,7 @@ test_expect_success '#17: GIT_WORK_TREE without explicit GIT_DIR is accepted (ba
try_repo 17c "$here/17c" unset unset "" true \
.git "$here/17c" "$here/17c" "(null)" \
"$here/17c/.git" "$here/17c" "$here/17c" sub/ 2>message &&
- ! test -s message
+ test_must_be_empty message
'
test_expect_success '#18: bare .git named by GIT_DIR has no worktree' '
@@ -594,7 +594,7 @@ test_expect_success '#20a: core.worktree without GIT_DIR accepted (inside .git)'
"$here/20a/.git" "$here/20a" "$here/20a" .git/wt/ &&
try_case 20a/.git/wt/sub unset unset \
"$here/20a/.git" "$here/20a" "$here/20a" .git/wt/sub/ &&
- ! test -s message
+ test_must_be_empty message
'
test_expect_success '#20b/c: core.worktree and core.bare conflict' '
@@ -626,7 +626,7 @@ test_expect_success '#21: setup, core.worktree warns before overriding core.bare
export GIT_WORK_TREE &&
git status >/dev/null
) 2>message &&
- ! test -s message
+ test_must_be_empty message
'
run_wt_tests 21
@@ -742,7 +742,7 @@ test_expect_success '#25: GIT_WORK_TREE accepted if GIT_DIR unset (bare gitfile
try_repo 25 "$here/25" unset unset gitfile true \
"$here/25.git" "$here/25" "$here/25" "(null)" \
"$here/25.git" "$here/25" "$here/25" "sub/" 2>message &&
- ! test -s message
+ test_must_be_empty message
'
test_expect_success '#26: bare repo has no worktree (GIT_DIR -> gitfile case)' '
@@ -780,7 +780,7 @@ test_expect_success '#29: setup' '
export GIT_WORK_TREE &&
git status
) 2>message &&
- ! test -s message
+ test_must_be_empty message
'
run_wt_tests 29 gitfile
diff --git a/t/t1600-index.sh b/t/t1600-index.sh
index c4422312f4..42962ed7d4 100755
--- a/t/t1600-index.sh
+++ b/t/t1600-index.sh
@@ -41,8 +41,7 @@ test_expect_success 'no warning with bogus GIT_INDEX_VERSION and existing index'
GIT_INDEX_VERSION=1 &&
export GIT_INDEX_VERSION &&
git add a 2>actual.err &&
- >expect.err &&
- test_i18ncmp expect.err actual.err
+ test_must_be_empty actual.err
)
'
diff --git a/t/t1700-split-index.sh b/t/t1700-split-index.sh
index 39133bcbc8..b3b4d83eaf 100755
--- a/t/t1700-split-index.sh
+++ b/t/t1700-split-index.sh
@@ -143,9 +143,7 @@ test_expect_success 'remove file not in base index' '
test_expect_success 'remove file in base index' '
git update-index --force-remove one &&
git ls-files --stage >ls-files.actual &&
- cat >ls-files.expect <<-EOF &&
- EOF
- test_cmp ls-files.expect ls-files.actual &&
+ test_must_be_empty ls-files.actual &&
test-tool dump-split-index .git/index | sed "/^own/d" >actual &&
cat >expect <<-EOF &&
diff --git a/t/t2013-checkout-submodule.sh b/t/t2013-checkout-submodule.sh
index 6ef15738e4..8f86b5f4b2 100755
--- a/t/t2013-checkout-submodule.sh
+++ b/t/t2013-checkout-submodule.sh
@@ -44,7 +44,7 @@ test_expect_success '"checkout <submodule>" honors diff.ignoreSubmodules' '
git config diff.ignoreSubmodules dirty &&
echo x> submodule/untracked &&
git checkout HEAD >actual 2>&1 &&
- ! test -s actual
+ test_must_be_empty actual
'
test_expect_success '"checkout <submodule>" honors submodule.*.ignore from .gitmodules' '
@@ -52,7 +52,7 @@ test_expect_success '"checkout <submodule>" honors submodule.*.ignore from .gitm
git config -f .gitmodules submodule.submodule.path submodule &&
git config -f .gitmodules submodule.submodule.ignore untracked &&
git checkout HEAD >actual 2>&1 &&
- ! test -s actual
+ test_must_be_empty actual
'
test_expect_success '"checkout <submodule>" honors submodule.*.ignore from .git/config' '
@@ -60,7 +60,7 @@ test_expect_success '"checkout <submodule>" honors submodule.*.ignore from .git/
git config submodule.submodule.path submodule &&
git config submodule.submodule.ignore all &&
git checkout HEAD >actual 2>&1 &&
- ! test -s actual
+ test_must_be_empty actual
'
KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1
diff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh
index 166942c1bd..07d292317c 100755
--- a/t/t2025-worktree-add.sh
+++ b/t/t2025-worktree-add.sh
@@ -252,6 +252,11 @@ test_expect_success 'add -B' '
test_cmp_rev master^ poodle
'
+test_expect_success 'add --quiet' '
+ git worktree add --quiet another-worktree master 2>actual &&
+ test_must_be_empty actual
+'
+
test_expect_success 'local clone from linked checkout' '
git clone --local here here-clone &&
( cd here-clone && git fsck )
diff --git a/t/t2200-add-update.sh b/t/t2200-add-update.sh
index 314c73c5a7..f764b7e3f5 100755
--- a/t/t2200-add-update.sh
+++ b/t/t2200-add-update.sh
@@ -88,9 +88,8 @@ test_expect_success 'non-qualified update in subdir updates from the root' '
echo even more >>sub2 &&
git add -u
) &&
- : >expect &&
git diff-files --name-only >actual &&
- test_cmp expect actual
+ test_must_be_empty actual
'
test_expect_success 'replace a file with a symlink' '
diff --git a/t/t2203-add-intent.sh b/t/t2203-add-intent.sh
index e7a400b4c7..68e54d5c44 100755
--- a/t/t2203-add-intent.sh
+++ b/t/t2203-add-intent.sh
@@ -195,8 +195,7 @@ test_expect_success 'rename detection finds the right names' '
test_cmp expected.4 actual.4 &&
git diff --cached --stat >actual.5 &&
- : >expected.5 &&
- test_cmp expected.5 actual.5
+ test_must_be_empty actual.5
)
'
@@ -241,8 +240,7 @@ test_expect_success 'diff-files/diff-cached shows ita as new/not-new files' '
echo " create mode 100644 new-ita" >expected &&
test_cmp expected actual &&
git diff --cached --summary >actual2 &&
- : >expected2 &&
- test_cmp expected2 actual2
+ test_must_be_empty actual2
'
diff --git a/t/t2204-add-ignored.sh b/t/t2204-add-ignored.sh
index 8340ac2f07..2e07365bbb 100755
--- a/t/t2204-add-ignored.sh
+++ b/t/t2204-add-ignored.sh
@@ -31,7 +31,7 @@ do
rm -f .git/index &&
test_must_fail git add "$i" 2>err &&
git ls-files "$i" >out &&
- ! test -s out
+ test_must_be_empty out
'
test_expect_success "complaints for ignored $i output" '
@@ -42,7 +42,7 @@ do
rm -f .git/index &&
test_must_fail git add "$i" file 2>err &&
git ls-files "$i" >out &&
- ! test -s out
+ test_must_be_empty out
'
test_expect_success "complaints for ignored $i with unignored file output" '
test_i18ngrep -e "Use -f if" err
@@ -57,7 +57,7 @@ do
cd dir &&
test_must_fail git add "$i" 2>err &&
git ls-files "$i" >out &&
- ! test -s out
+ test_must_be_empty out
)
'
@@ -77,7 +77,7 @@ do
cd sub &&
test_must_fail git add "$i" 2>err &&
git ls-files "$i" >out &&
- ! test -s out
+ test_must_be_empty out
)
'
diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh
index 3b47647ed5..1ec7cb57c7 100755
--- a/t/t3001-ls-files-others-exclude.sh
+++ b/t/t3001-ls-files-others-exclude.sh
@@ -277,9 +277,8 @@ test_expect_success 'hide empty ignored sub-directory with --no-empty-directory'
'
test_expect_success 'pattern matches prefix completely' '
- : >expect &&
git ls-files -i -o --exclude "/three/a.3[abc]" >actual &&
- test_cmp expect actual
+ test_must_be_empty actual
'
test_expect_success 'ls-files with "**" patterns' '
@@ -295,9 +294,8 @@ EOF
test_expect_success 'ls-files with "**" patterns and no slashes' '
- : >expect &&
git ls-files -o -i --exclude "one**a.1" >actual &&
- test_cmp expect actual
+ test_must_be_empty actual
'
test_done
diff --git a/t/t3004-ls-files-basic.sh b/t/t3004-ls-files-basic.sh
index 9c7adbdbe1..9fd5a1f188 100755
--- a/t/t3004-ls-files-basic.sh
+++ b/t/t3004-ls-files-basic.sh
@@ -8,16 +8,14 @@ command-line arguments.
. ./test-lib.sh
->empty
-
test_expect_success 'ls-files in empty repository' '
git ls-files >actual &&
- test_cmp empty actual
+ test_must_be_empty actual
'
test_expect_success 'ls-files with nonexistent path' '
git ls-files doesnotexist >actual &&
- test_cmp empty actual
+ test_must_be_empty actual
'
test_expect_success 'ls-files with nonsense option' '
diff --git a/t/t3035-merge-sparse.sh b/t/t3035-merge-sparse.sh
index 0c0b433bd3..c4b4a94324 100755
--- a/t/t3035-merge-sparse.sh
+++ b/t/t3035-merge-sparse.sh
@@ -17,7 +17,6 @@ test_commit_this () {
}
test_expect_success 'setup' '
- : >empty &&
test_file checked-out init &&
test_file modify_delete modify_delete_init &&
test_commit_this init &&
@@ -38,7 +37,7 @@ test_expect_success 'reset --hard works after the conflict' '
test_expect_success 'is reset properly' '
git status --porcelain -- modify_delete >out &&
- test_cmp empty out &&
+ test_must_be_empty out &&
test_path_is_missing modify_delete
'
@@ -52,7 +51,7 @@ test_expect_success 'Merge abort works after the conflict' '
test_expect_success 'is aborted properly' '
git status --porcelain -- modify_delete >out &&
- test_cmp empty out &&
+ test_must_be_empty out &&
test_path_is_missing modify_delete
'
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index dbca665da4..93f21ab078 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -1305,4 +1305,50 @@ test_expect_success 'tracking with unexpected .fetch refspec' '
)
'
+test_expect_success 'configured committerdate sort' '
+ git init sort &&
+ (
+ cd sort &&
+ git config branch.sort committerdate &&
+ test_commit initial &&
+ git checkout -b a &&
+ test_commit a &&
+ git checkout -b c &&
+ test_commit c &&
+ git checkout -b b &&
+ test_commit b &&
+ git branch >actual &&
+ cat >expect <<-\EOF &&
+ master
+ a
+ c
+ * b
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'option override configured sort' '
+ (
+ cd sort &&
+ git config branch.sort committerdate &&
+ git branch --sort=refname >actual &&
+ cat >expect <<-\EOF &&
+ a
+ * b
+ c
+ master
+ EOF
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'invalid sort parameter in configuration' '
+ (
+ cd sort &&
+ git config branch.sort "v:notvalid" &&
+ test_must_fail git branch
+ )
+'
+
test_done
diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh
index 2237c7f4af..88ebed1dfa 100755
--- a/t/t3206-range-diff.sh
+++ b/t/t3206-range-diff.sh
@@ -133,13 +133,64 @@ test_expect_success 'changed message' '
Z
+ Also a silly comment here!
+
- Zdiff --git a/file b/file
- Z--- a/file
- Z+++ b/file
+ Z diff --git a/file b/file
+ Z --- a/file
+ Z +++ b/file
3: 147e64e = 3: b9cb956 s/11/B/
4: a63e992 = 4: 8add5f1 s/12/B/
EOF
test_cmp expected actual
'
+test_expect_success 'dual-coloring' '
+ sed -e "s|^:||" >expect <<-\EOF &&
+ :<YELLOW>1: a4b3333 = 1: f686024 s/5/A/<RESET>
+ :<RED>2: f51d370 <RESET><YELLOW>!<RESET><GREEN> 2: 4ab067d<RESET><YELLOW> s/4/A/<RESET>
+ : <REVERSE><CYAN>@@ -2,6 +2,8 @@<RESET>
+ : <RESET>
+ : s/4/A/<RESET>
+ : <RESET>
+ : <REVERSE><GREEN>+<RESET><BOLD> Also a silly comment here!<RESET>
+ : <REVERSE><GREEN>+<RESET>
+ : diff --git a/file b/file<RESET>
+ : --- a/file<RESET>
+ : +++ b/file<RESET>
+ :<RED>3: 0559556 <RESET><YELLOW>!<RESET><GREEN> 3: b9cb956<RESET><YELLOW> s/11/B/<RESET>
+ : <REVERSE><CYAN>@@ -10,7 +10,7 @@<RESET>
+ : 9<RESET>
+ : 10<RESET>
+ : <RED> -11<RESET>
+ : <REVERSE><RED>-<RESET><FAINT;GREEN>+BB<RESET>
+ : <REVERSE><GREEN>+<RESET><BOLD;GREEN>+B<RESET>
+ : 12<RESET>
+ : 13<RESET>
+ : 14<RESET>
+ :<RED>4: d966c5c <RESET><YELLOW>!<RESET><GREEN> 4: 8add5f1<RESET><YELLOW> s/12/B/<RESET>
+ : <REVERSE><CYAN>@@ -8,7 +8,7 @@<RESET>
+ : <CYAN> @@<RESET>
+ : 9<RESET>
+ : 10<RESET>
+ : <REVERSE><RED>-<RESET><FAINT> BB<RESET>
+ : <REVERSE><GREEN>+<RESET><BOLD> B<RESET>
+ : <RED> -12<RESET>
+ : <GREEN> +B<RESET>
+ : 13<RESET>
+ EOF
+ git range-diff changed...changed-message --color --dual-color >actual.raw &&
+ test_decode_color >actual <actual.raw &&
+ test_cmp expect actual
+'
+
+for prev in topic master..topic
+do
+ test_expect_success "format-patch --range-diff=$prev" '
+ git format-patch --stdout --cover-letter --range-diff=$prev \
+ master..unmodified >actual &&
+ grep "= 1: .* s/5/A" actual &&
+ grep "= 2: .* s/4/A" actual &&
+ grep "= 3: .* s/11/B" actual &&
+ grep "= 4: .* s/12/B" actual
+ '
+done
+
test_done
diff --git a/t/t3210-pack-refs.sh b/t/t3210-pack-refs.sh
index 7333d7d545..9ea5fa4fd2 100755
--- a/t/t3210-pack-refs.sh
+++ b/t/t3210-pack-refs.sh
@@ -127,7 +127,7 @@ test_expect_success 'explicit pack-refs with dangling packed reference' '
git reflog expire --expire=all --all &&
git prune --expire=all &&
git pack-refs --all 2>result &&
- test_cmp /dev/null result
+ test_must_be_empty result
'
test_expect_success 'delete ref with dangling packed version' '
@@ -139,7 +139,7 @@ test_expect_success 'delete ref with dangling packed version' '
git reflog expire --expire=all --all &&
git prune --expire=all &&
git branch -d lamb 2>result &&
- test_cmp /dev/null result
+ test_must_be_empty result
'
test_expect_success 'delete ref while another dangling packed ref' '
@@ -150,7 +150,7 @@ test_expect_success 'delete ref while another dangling packed ref' '
git reflog expire --expire=all --all &&
git prune --expire=all &&
git branch -d lamb 2>result &&
- test_cmp /dev/null result
+ test_must_be_empty result
'
test_expect_success 'pack ref directly below refs/' '
diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh
index ac62dc0e8f..84bbf88cf9 100755
--- a/t/t3301-notes.sh
+++ b/t/t3301-notes.sh
@@ -481,10 +481,8 @@ test_expect_success 'list specific note with "git notes list <object>"' '
'
test_expect_success 'listing non-existing notes fails' '
- cat >expect <<-EOF &&
- EOF
test_must_fail git notes list HEAD >actual &&
- test_cmp expect actual
+ test_must_be_empty actual
'
test_expect_success 'append to existing note with "git notes append"' '
diff --git a/t/t3308-notes-merge.sh b/t/t3308-notes-merge.sh
index ab946a5153..d60588ec8f 100755
--- a/t/t3308-notes-merge.sh
+++ b/t/t3308-notes-merge.sh
@@ -183,7 +183,7 @@ test_expect_success 'merge empty notes ref (z => y)' '
git notes add -m "foo" &&
git notes remove &&
git notes >output_notes_z &&
- test_cmp /dev/null output_notes_z &&
+ test_must_be_empty output_notes_z &&
# Do the merge (z => y)
git config core.notesRef refs/notes/y &&
git notes merge z &&
diff --git a/t/t3310-notes-merge-manual-resolve.sh b/t/t3310-notes-merge-manual-resolve.sh
index 68436eed82..2dea846e25 100755
--- a/t/t3310-notes-merge-manual-resolve.sh
+++ b/t/t3310-notes-merge-manual-resolve.sh
@@ -337,7 +337,7 @@ EOF
git notes merge --commit &&
# No .git/NOTES_MERGE_* files left
test_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
- test_cmp /dev/null output &&
+ test_must_be_empty output &&
# Merge commit has pre-merge y and pre-merge z as parents
test "$(git rev-parse refs/notes/m^1)" = "$(cat pre_merge_y)" &&
test "$(git rev-parse refs/notes/m^2)" = "$(cat pre_merge_z)" &&
@@ -399,7 +399,7 @@ test_expect_success 'abort notes merge' '
git notes merge --abort &&
# No .git/NOTES_MERGE_* files left
test_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
- test_cmp /dev/null output &&
+ test_must_be_empty output &&
# m has not moved (still == y)
test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)" &&
# Verify that other notes refs has not changed (w, x, y and z)
@@ -466,7 +466,7 @@ EOF
git notes merge --commit &&
# No .git/NOTES_MERGE_* files left
test_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
- test_cmp /dev/null output &&
+ test_must_be_empty output &&
# Merge commit has pre-merge y and pre-merge z as parents
test "$(git rev-parse refs/notes/m^1)" = "$(cat pre_merge_y)" &&
test "$(git rev-parse refs/notes/m^2)" = "$(cat pre_merge_z)" &&
@@ -555,7 +555,7 @@ test_expect_success 'resolve situation by aborting the notes merge' '
git notes merge --abort &&
# No .git/NOTES_MERGE_* files left
test_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
- test_cmp /dev/null output &&
+ test_must_be_empty output &&
# m has not moved (still == w)
test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" &&
# Verify that other notes refs has not changed (w, x, y and z)
diff --git a/t/t3401-rebase-and-am-rename.sh b/t/t3401-rebase-and-am-rename.sh
index 8f832957fc..e0b5111993 100755
--- a/t/t3401-rebase-and-am-rename.sh
+++ b/t/t3401-rebase-and-am-rename.sh
@@ -5,7 +5,7 @@ test_description='git rebase + directory rename tests'
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-rebase.sh
-test_expect_success 'setup testcase' '
+test_expect_success 'setup testcase where directory rename should be detected' '
test_create_repo dir-rename &&
(
cd dir-rename &&
@@ -102,4 +102,112 @@ test_expect_failure 'am: directory rename detected' '
)
'
+test_expect_success 'setup testcase where directory rename should NOT be detected' '
+ test_create_repo no-dir-rename &&
+ (
+ cd no-dir-rename &&
+
+ mkdir x &&
+ test_seq 1 10 >x/a &&
+ test_seq 11 20 >x/b &&
+ test_seq 21 30 >x/c &&
+ echo original >project_info &&
+ git add x project_info &&
+ git commit -m "Initial" &&
+
+ git branch O &&
+ git branch A &&
+ git branch B &&
+
+ git checkout A &&
+ echo v2 >project_info &&
+ git add project_info &&
+ git commit -m "Modify project_info" &&
+
+ git checkout B &&
+ mkdir y &&
+ git mv x/c y/c &&
+ echo v1 >project_info &&
+ git add project_info &&
+ git commit -m "Rename x/c to y/c, modify project_info"
+ )
+'
+
+test_expect_success 'rebase --interactive: NO directory rename' '
+ test_when_finished "git -C no-dir-rename rebase --abort" &&
+ (
+ cd no-dir-rename &&
+
+ git checkout B^0 &&
+
+ set_fake_editor &&
+ test_must_fail env FAKE_LINES="1" git rebase --interactive A &&
+
+ git ls-files -s >out &&
+ test_line_count = 6 out &&
+
+ test_path_is_file x/a &&
+ test_path_is_file x/b &&
+ test_path_is_missing x/c
+ )
+'
+
+test_expect_success 'rebase (am): NO directory rename' '
+ test_when_finished "git -C no-dir-rename rebase --abort" &&
+ (
+ cd no-dir-rename &&
+
+ git checkout B^0 &&
+
+ set_fake_editor &&
+ test_must_fail git rebase A &&
+
+ git ls-files -s >out &&
+ test_line_count = 6 out &&
+
+ test_path_is_file x/a &&
+ test_path_is_file x/b &&
+ test_path_is_missing x/c
+ )
+'
+
+test_expect_success 'rebase --merge: NO directory rename' '
+ test_when_finished "git -C no-dir-rename rebase --abort" &&
+ (
+ cd no-dir-rename &&
+
+ git checkout B^0 &&
+
+ set_fake_editor &&
+ test_must_fail git rebase --merge A &&
+
+ git ls-files -s >out &&
+ test_line_count = 6 out &&
+
+ test_path_is_file x/a &&
+ test_path_is_file x/b &&
+ test_path_is_missing x/c
+ )
+'
+
+test_expect_success 'am: NO directory rename' '
+ test_when_finished "git -C no-dir-rename am --abort" &&
+ (
+ cd no-dir-rename &&
+
+ git checkout A^0 &&
+
+ git format-patch -1 B &&
+
+ test_must_fail git am --3way 0001*.patch &&
+
+ git ls-files -s >out &&
+ test_line_count = 6 out &&
+
+ test_path_is_file x/a &&
+ test_path_is_file x/b &&
+ test_path_is_missing x/c
+ )
+'
+
test_done
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
index 4c7b1ea356..86bba5ed7c 100755
--- a/t/t3404-rebase-interactive.sh
+++ b/t/t3404-rebase-interactive.sh
@@ -796,16 +796,15 @@ test_expect_success 'always cherry-pick with --no-ff' '
git tag original-no-ff-branch &&
set_fake_editor &&
git rebase -i --no-ff A &&
- touch empty &&
for p in 0 1 2
do
test ! $(git rev-parse HEAD~$p) = $(git rev-parse original-no-ff-branch~$p) &&
git diff HEAD~$p original-no-ff-branch~$p > out &&
- test_cmp empty out
+ test_must_be_empty out
done &&
test $(git rev-parse HEAD~3) = $(git rev-parse original-no-ff-branch~3) &&
git diff HEAD~3 original-no-ff-branch~3 > out &&
- test_cmp empty out
+ test_must_be_empty out
'
test_expect_success 'set up commits with funny messages' '
@@ -1427,9 +1426,21 @@ test_expect_success 'rebase -i --gpg-sign=<key-id> overrides commit.gpgSign' '
test_expect_success 'valid author header after --root swap' '
rebase_setup_and_clean author-header no-conflict-branch &&
set_fake_editor &&
- FAKE_LINES="2 1" git rebase -i --root &&
- git cat-file commit HEAD^ >out &&
- grep "^author ..*> [0-9][0-9]* [-+][0-9][0-9][0-9][0-9]$" out
+ git commit --amend --author="Au ${SQ}thor <author@example.com>" --no-edit &&
+ git cat-file commit HEAD | grep ^author >expected &&
+ FAKE_LINES="5 1" git rebase -i --root &&
+ git cat-file commit HEAD^ | grep ^author >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'valid author header when author contains single quote' '
+ rebase_setup_and_clean author-header no-conflict-branch &&
+ set_fake_editor &&
+ git commit --amend --author="Au ${SQ}thor <author@example.com>" --no-edit &&
+ git cat-file commit HEAD | grep ^author >expected &&
+ FAKE_LINES="2" git rebase -i HEAD~2 &&
+ git cat-file commit HEAD | grep ^author >actual &&
+ test_cmp expected actual
'
test_done
diff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh
index e243700660..0c4eefec76 100755
--- a/t/t3420-rebase-autostash.sh
+++ b/t/t3420-rebase-autostash.sh
@@ -202,7 +202,7 @@ testrebase () {
echo dirty >>file3 &&
test_must_fail git rebase$type related-onto-branch &&
test_path_is_file $dotest/autostash &&
- ! grep dirty file3 &&
+ test_path_is_missing file3 &&
rm -rf $dotest &&
git reset --hard &&
git checkout feature-branch
@@ -216,7 +216,7 @@ testrebase () {
echo dirty >>file3 &&
test_must_fail git rebase$type related-onto-branch &&
test_path_is_file $dotest/autostash &&
- ! grep dirty file3 &&
+ test_path_is_missing file3 &&
echo "conflicting-plus-goodbye" >file2 &&
git add file2 &&
git rebase --continue &&
@@ -233,7 +233,7 @@ testrebase () {
echo dirty >>file3 &&
test_must_fail git rebase$type related-onto-branch &&
test_path_is_file $dotest/autostash &&
- ! grep dirty file3 &&
+ test_path_is_missing file3 &&
git rebase --skip &&
test_path_is_missing $dotest/autostash &&
grep dirty file3 &&
@@ -248,7 +248,7 @@ testrebase () {
echo dirty >>file3 &&
test_must_fail git rebase$type related-onto-branch &&
test_path_is_file $dotest/autostash &&
- ! grep dirty file3 &&
+ test_path_is_missing file3 &&
git rebase --abort &&
test_path_is_missing $dotest/autostash &&
grep dirty file3 &&
diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh
index 5829dfd12c..04e5d42bd3 100755
--- a/t/t3600-rm.sh
+++ b/t/t3600-rm.sh
@@ -380,7 +380,7 @@ test_expect_success 'rm does not complain when no .gitmodules file is found' '
git submodule update &&
git rm .gitmodules &&
git rm submod >actual 2>actual.err &&
- ! test -s actual.err &&
+ test_must_be_empty actual.err &&
! test -d submod &&
! test -f submod/.git &&
git status -s -uno >actual &&
@@ -398,7 +398,7 @@ test_expect_success 'rm will error out on a modified .gitmodules file unless sta
git diff-files --quiet -- submod &&
git add .gitmodules &&
git rm submod >actual 2>actual.err &&
- ! test -s actual.err &&
+ test_must_be_empty actual.err &&
! test -d submod &&
! test -f submod/.git &&
git status -s -uno >actual &&
@@ -692,7 +692,7 @@ test_expect_success 'checking out a commit after submodule removal needs manual
test_cmp expected actual &&
rm -rf submod &&
git status -s -uno --ignore-submodules=none >actual &&
- ! test -s actual
+ test_must_be_empty actual
'
test_expect_success 'rm of d/f when d has become a non-directory' '
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 1f871d3cca..6450bc6698 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -724,7 +724,7 @@ test_expect_success 'store updates stash ref and reflog' '
git add bazzy &&
STASH_ID=$(git stash create) &&
git reset --hard &&
- ! grep quux bazzy &&
+ test_path_is_missing bazzy &&
git stash store -m quuxery $STASH_ID &&
test $(git rev-parse stash) = $STASH_ID &&
git reflog --format=%H stash| grep $STASH_ID &&
diff --git a/t/t4011-diff-symlink.sh b/t/t4011-diff-symlink.sh
index 108c012a3a..5ae19b987d 100755
--- a/t/t4011-diff-symlink.sh
+++ b/t/t4011-diff-symlink.sh
@@ -126,7 +126,7 @@ test_expect_success SYMLINKS 'diff symlinks with non-existing targets' '
ln -s take\ over brain &&
test_must_fail git diff --no-index pinky brain >output 2>output.err &&
grep narf output &&
- ! test -s output.err
+ test_must_be_empty output.err
'
test_expect_success SYMLINKS 'setup symlinks with attributes' '
diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh
index f8d853595b..73f7038253 100755
--- a/t/t4013-diff-various.sh
+++ b/t/t4013-diff-various.sh
@@ -140,7 +140,7 @@ do
expect="$TEST_DIRECTORY/t4013/diff.$test"
actual="$pfx-diff.$test"
- test_expect_success "git $cmd # magic is ${magic:-"(not used)"}" '
+ test_expect_success "git $cmd # magic is ${magic:-(not used)}" '
{
echo "$ git $cmd"
case "$magic" in
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 53880da7bb..909c743c13 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -1717,4 +1717,38 @@ test_expect_success 'format-patch --pretty=mboxrd' '
test_cmp expect actual
'
+test_expect_success 'interdiff: setup' '
+ git checkout -b boop master &&
+ test_commit fnorp blorp &&
+ test_commit fleep blorp
+'
+
+test_expect_success 'interdiff: cover-letter' '
+ sed "y/q/ /" >expect <<-\EOF &&
+ +fleep
+ --q
+ EOF
+ git format-patch --cover-letter --interdiff=boop~2 -1 boop &&
+ test_i18ngrep "^Interdiff:$" 0000-cover-letter.patch &&
+ test_i18ngrep ! "^Interdiff:$" 0001-fleep.patch &&
+ sed "1,/^@@ /d; /^-- $/q" <0000-cover-letter.patch >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'interdiff: reroll-count' '
+ git format-patch --cover-letter --interdiff=boop~2 -v2 -1 boop &&
+ test_i18ngrep "^Interdiff ..* v1:$" v2-0000-cover-letter.patch
+'
+
+test_expect_success 'interdiff: solo-patch' '
+ cat >expect <<-\EOF &&
+ +fleep
+
+ EOF
+ git format-patch --interdiff=boop~2 -1 boop &&
+ test_i18ngrep "^Interdiff:$" 0001-fleep.patch &&
+ sed "1,/^ @@ /d; /^$/q" <0001-fleep.patch >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh
index 35fc8b5c2a..a9fb226c5a 100755
--- a/t/t4015-diff-whitespace.sh
+++ b/t/t4015-diff-whitespace.sh
@@ -776,8 +776,6 @@ test_expect_success 'checkdiff allows new blank lines' '
git diff --check
'
-cat <<EOF >expect
-EOF
test_expect_success 'whitespace-only changes not reported' '
git reset --hard &&
echo >x "hello world" &&
@@ -785,7 +783,7 @@ test_expect_success 'whitespace-only changes not reported' '
git commit -m "hello 1" &&
echo >x "hello world" &&
git diff -b >actual &&
- test_cmp expect actual
+ test_must_be_empty actual
'
cat <<EOF >expect
diff --git a/t/t4019-diff-wserror.sh b/t/t4019-diff-wserror.sh
index a5019759bc..c6135c7548 100755
--- a/t/t4019-diff-wserror.sh
+++ b/t/t4019-diff-wserror.sh
@@ -260,7 +260,7 @@ test_expect_success 'trailing empty lines (2)' '
echo "F -whitespace" >.gitattributes &&
git diff --check >output &&
- ! test -s output
+ test_must_be_empty output
'
diff --git a/t/t4027-diff-submodule.sh b/t/t4027-diff-submodule.sh
index 6304130ad4..9aa8e2b39b 100755
--- a/t/t4027-diff-submodule.sh
+++ b/t/t4027-diff-submodule.sh
@@ -104,19 +104,19 @@ test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match)'
expect_from_to >expect.body $subprev $subprev-dirty &&
test_cmp expect.body actual.body &&
git diff --ignore-submodules HEAD >actual2 &&
- ! test -s actual2 &&
+ test_must_be_empty actual2 &&
git diff --ignore-submodules=untracked HEAD >actual3 &&
sed -e "1,/^@@/d" actual3 >actual3.body &&
expect_from_to >expect.body $subprev $subprev-dirty &&
test_cmp expect.body actual3.body &&
git diff --ignore-submodules=dirty HEAD >actual4 &&
- ! test -s actual4
+ test_must_be_empty actual4
'
test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match) [.gitmodules]' '
git config diff.ignoreSubmodules dirty &&
git diff HEAD >actual &&
- ! test -s actual &&
+ test_must_be_empty actual &&
git config --add -f .gitmodules submodule.subname.ignore none &&
git config --add -f .gitmodules submodule.subname.path sub &&
git diff HEAD >actual &&
@@ -126,7 +126,7 @@ test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match)
git config -f .gitmodules submodule.subname.ignore all &&
git config -f .gitmodules submodule.subname.path sub &&
git diff HEAD >actual2 &&
- ! test -s actual2 &&
+ test_must_be_empty actual2 &&
git config -f .gitmodules submodule.subname.ignore untracked &&
git diff HEAD >actual3 &&
sed -e "1,/^@@/d" actual3 >actual3.body &&
@@ -134,7 +134,7 @@ test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match)
test_cmp expect.body actual3.body &&
git config -f .gitmodules submodule.subname.ignore dirty &&
git diff HEAD >actual4 &&
- ! test -s actual4 &&
+ test_must_be_empty actual4 &&
git config submodule.subname.ignore none &&
git config submodule.subname.path sub &&
git diff HEAD >actual &&
@@ -172,24 +172,24 @@ test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match)'
expect_from_to >expect.body $subprev $subprev-dirty &&
test_cmp expect.body actual.body &&
git diff --ignore-submodules=all HEAD >actual2 &&
- ! test -s actual2 &&
+ test_must_be_empty actual2 &&
git diff --ignore-submodules=untracked HEAD >actual3 &&
- ! test -s actual3 &&
+ test_must_be_empty actual3 &&
git diff --ignore-submodules=dirty HEAD >actual4 &&
- ! test -s actual4
+ test_must_be_empty actual4
'
test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match) [.gitmodules]' '
git config --add -f .gitmodules submodule.subname.ignore all &&
git config --add -f .gitmodules submodule.subname.path sub &&
git diff HEAD >actual2 &&
- ! test -s actual2 &&
+ test_must_be_empty actual2 &&
git config -f .gitmodules submodule.subname.ignore untracked &&
git diff HEAD >actual3 &&
- ! test -s actual3 &&
+ test_must_be_empty actual3 &&
git config -f .gitmodules submodule.subname.ignore dirty &&
git diff HEAD >actual4 &&
- ! test -s actual4 &&
+ test_must_be_empty actual4 &&
git config submodule.subname.ignore none &&
git config submodule.subname.path sub &&
git diff HEAD >actual &&
@@ -211,7 +211,7 @@ test_expect_success 'git diff between submodule commits' '
expect_from_to >expect.body $subtip $subprev &&
test_cmp expect.body actual.body &&
git diff --ignore-submodules HEAD^..HEAD >actual &&
- ! test -s actual
+ test_must_be_empty actual
'
test_expect_success 'git diff between submodule commits [.gitmodules]' '
@@ -227,7 +227,7 @@ test_expect_success 'git diff between submodule commits [.gitmodules]' '
test_cmp expect.body actual.body &&
git config -f .gitmodules submodule.subname.ignore all &&
git diff HEAD^..HEAD >actual &&
- ! test -s actual &&
+ test_must_be_empty actual &&
git config submodule.subname.ignore dirty &&
git config submodule.subname.path sub &&
git diff HEAD^..HEAD >actual &&
@@ -239,10 +239,9 @@ test_expect_success 'git diff between submodule commits [.gitmodules]' '
'
test_expect_success 'git diff (empty submodule dir)' '
- : >empty &&
rm -rf sub/* sub/.git &&
git diff > actual.empty &&
- test_cmp empty actual.empty
+ test_must_be_empty actual.empty
'
test_expect_success 'conflicted submodule setup' '
diff --git a/t/t4041-diff-submodule-option.sh b/t/t4041-diff-submodule-option.sh
index 4e3499ef84..619bf97098 100755
--- a/t/t4041-diff-submodule-option.sh
+++ b/t/t4041-diff-submodule-option.sh
@@ -257,9 +257,7 @@ test_expect_success 'typechanged submodule(blob->submodule)' '
commit_file sm1 &&
test_expect_success 'submodule is up to date' '
git diff-index -p --submodule=log HEAD >actual &&
- cat >expected <<-EOF &&
- EOF
- test_cmp expected actual
+ test_must_be_empty actual
'
test_expect_success 'submodule contains untracked content' '
@@ -273,17 +271,17 @@ test_expect_success 'submodule contains untracked content' '
test_expect_success 'submodule contains untracked content (untracked ignored)' '
git diff-index -p --ignore-submodules=untracked --submodule=log HEAD >actual &&
- ! test -s actual
+ test_must_be_empty actual
'
test_expect_success 'submodule contains untracked content (dirty ignored)' '
git diff-index -p --ignore-submodules=dirty --submodule=log HEAD >actual &&
- ! test -s actual
+ test_must_be_empty actual
'
test_expect_success 'submodule contains untracked content (all ignored)' '
git diff-index -p --ignore-submodules=all --submodule=log HEAD >actual &&
- ! test -s actual
+ test_must_be_empty actual
'
test_expect_success 'submodule contains untracked and modifed content' '
@@ -308,13 +306,13 @@ test_expect_success 'submodule contains untracked and modifed content (untracked
test_expect_success 'submodule contains untracked and modifed content (dirty ignored)' '
echo new > sm1/foo6 &&
git diff-index -p --ignore-submodules=dirty --submodule=log HEAD >actual &&
- ! test -s actual
+ test_must_be_empty actual
'
test_expect_success 'submodule contains untracked and modifed content (all ignored)' '
echo new > sm1/foo6 &&
git diff-index -p --ignore-submodules --submodule=log HEAD >actual &&
- ! test -s actual
+ test_must_be_empty actual
'
test_expect_success 'submodule contains modifed content' '
@@ -368,7 +366,7 @@ test_expect_success 'modified submodule contains untracked content (dirty ignore
test_expect_success 'modified submodule contains untracked content (all ignored)' '
git diff-index -p --ignore-submodules=all --submodule=log HEAD >actual &&
- ! test -s actual
+ test_must_be_empty actual
'
test_expect_success 'modified submodule contains untracked and modifed content' '
@@ -407,7 +405,7 @@ test_expect_success 'modified submodule contains untracked and modifed content (
test_expect_success 'modified submodule contains untracked and modifed content (all ignored)' '
echo modification >> sm1/foo6 &&
git diff-index -p --ignore-submodules --submodule=log HEAD >actual &&
- ! test -s actual
+ test_must_be_empty actual
'
test_expect_success 'modified submodule contains modifed content' '
diff --git a/t/t4047-diff-dirstat.sh b/t/t4047-diff-dirstat.sh
index 447a8ffa3a..7fec2cb9cd 100755
--- a/t/t4047-diff-dirstat.sh
+++ b/t/t4047-diff-dirstat.sh
@@ -940,7 +940,7 @@ test_expect_success 'diff.dirstat=0,lines' '
test_expect_success '--dirstat=future_param,lines,0 should fail loudly' '
test_must_fail git diff --dirstat=future_param,lines,0 HEAD^..HEAD >actual_diff_dirstat 2>actual_error &&
test_debug "cat actual_error" &&
- test_cmp /dev/null actual_diff_dirstat &&
+ test_must_be_empty actual_diff_dirstat &&
test_i18ngrep -q "future_param" actual_error &&
test_i18ngrep -q "\--dirstat" actual_error
'
@@ -948,7 +948,7 @@ test_expect_success '--dirstat=future_param,lines,0 should fail loudly' '
test_expect_success '--dirstat=dummy1,cumulative,2dummy should report both unrecognized parameters' '
test_must_fail git diff --dirstat=dummy1,cumulative,2dummy HEAD^..HEAD >actual_diff_dirstat 2>actual_error &&
test_debug "cat actual_error" &&
- test_cmp /dev/null actual_diff_dirstat &&
+ test_must_be_empty actual_diff_dirstat &&
test_i18ngrep -q "dummy1" actual_error &&
test_i18ngrep -q "2dummy" actual_error &&
test_i18ngrep -q "\--dirstat" actual_error
diff --git a/t/t4051-diff-function-context.sh b/t/t4051-diff-function-context.sh
index 2d76a971c4..4838a1df8b 100755
--- a/t/t4051-diff-function-context.sh
+++ b/t/t4051-diff-function-context.sh
@@ -174,7 +174,7 @@ test_expect_success ' context does not include other functions' '
'
test_expect_success ' context does not include preceding empty lines' '
- test "$(first_context_line <long_common_tail.diff.diff)" != " "
+ test "$(first_context_line <long_common_tail.diff)" != " "
'
check_diff changed_hello_appended 'changed function plus appended function'
diff --git a/t/t4060-diff-submodule-option-diff-format.sh b/t/t4060-diff-submodule-option-diff-format.sh
index 0eba4620f0..9dcb69df5c 100755
--- a/t/t4060-diff-submodule-option-diff-format.sh
+++ b/t/t4060-diff-submodule-option-diff-format.sh
@@ -392,9 +392,7 @@ test_expect_success 'typechanged submodule(blob->submodule)' '
commit_file sm1 &&
test_expect_success 'submodule is up to date' '
git diff-index -p --submodule=diff HEAD >actual &&
- cat >expected <<-EOF &&
- EOF
- test_cmp expected actual
+ test_must_be_empty actual
'
test_expect_success 'submodule contains untracked content' '
@@ -408,17 +406,17 @@ test_expect_success 'submodule contains untracked content' '
test_expect_success 'submodule contains untracked content (untracked ignored)' '
git diff-index -p --ignore-submodules=untracked --submodule=diff HEAD >actual &&
- ! test -s actual
+ test_must_be_empty actual
'
test_expect_success 'submodule contains untracked content (dirty ignored)' '
git diff-index -p --ignore-submodules=dirty --submodule=diff HEAD >actual &&
- ! test -s actual
+ test_must_be_empty actual
'
test_expect_success 'submodule contains untracked content (all ignored)' '
git diff-index -p --ignore-submodules=all --submodule=diff HEAD >actual &&
- ! test -s actual
+ test_must_be_empty actual
'
test_expect_success 'submodule contains untracked and modified content' '
@@ -458,13 +456,13 @@ test_expect_success 'submodule contains untracked and modified content (untracke
test_expect_success 'submodule contains untracked and modified content (dirty ignored)' '
echo new > sm1/foo6 &&
git diff-index -p --ignore-submodules=dirty --submodule=diff HEAD >actual &&
- ! test -s actual
+ test_must_be_empty actual
'
test_expect_success 'submodule contains untracked and modified content (all ignored)' '
echo new > sm1/foo6 &&
git diff-index -p --ignore-submodules --submodule=diff HEAD >actual &&
- ! test -s actual
+ test_must_be_empty actual
'
test_expect_success 'submodule contains modified content' '
@@ -549,7 +547,7 @@ test_expect_success 'modified submodule contains untracked content (dirty ignore
test_expect_success 'modified submodule contains untracked content (all ignored)' '
git diff-index -p --ignore-submodules=all --submodule=diff HEAD >actual &&
- ! test -s actual
+ test_must_be_empty actual
'
test_expect_success 'modified submodule contains untracked and modified content' '
@@ -609,7 +607,7 @@ test_expect_success 'modified submodule contains untracked and modified content
test_expect_success 'modified submodule contains untracked and modified content (all ignored)' '
echo modification >> sm1/foo6 &&
git diff-index -p --ignore-submodules --submodule=diff HEAD >actual &&
- ! test -s actual
+ test_must_be_empty actual
'
# NOT OK
diff --git a/t/t4116-apply-reverse.sh b/t/t4116-apply-reverse.sh
index ce8567f496..b99e65c086 100755
--- a/t/t4116-apply-reverse.sh
+++ b/t/t4116-apply-reverse.sh
@@ -42,7 +42,7 @@ test_expect_success 'apply in reverse' '
git reset --hard second &&
git apply --reverse --binary --index patch &&
git diff >diff &&
- test_cmp /dev/null diff
+ test_must_be_empty diff
'
diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh
index 4fc27c51f7..7e32237a2a 100755
--- a/t/t4124-apply-ws-rule.sh
+++ b/t/t4124-apply-ws-rule.sh
@@ -100,7 +100,7 @@ test_expect_success 'whitespace=warn, default rule' '
test_expect_success 'whitespace=error-all, default rule' '
test_must_fail apply_patch --whitespace=error-all &&
- ! test -s target
+ test_must_be_empty target
'
diff --git a/t/t4132-apply-removal.sh b/t/t4132-apply-removal.sh
index a2bc1cd37d..fec1d6fa51 100755
--- a/t/t4132-apply-removal.sh
+++ b/t/t4132-apply-removal.sh
@@ -49,8 +49,7 @@ test_expect_success setup '
sed -e "s/TS0/$timeGMT/" -e "s/TS1/$epocGMT/" <d >removeGMT.patch &&
sed -e "s/TS0/$timeWest/" -e "s/TS1/$epocWest2/" <d >removeWest2.patch &&
- echo something >something &&
- >empty
+ echo something >something
'
for patch in *.patch
@@ -81,7 +80,7 @@ do
git add file &&
git apply --index $patch &&
test -f file &&
- test_cmp empty file
+ test_must_be_empty file
;;
remove*)
# must remove the file
diff --git a/t/t4150-am.sh b/t/t4150-am.sh
index 01867a9898..55b577d919 100755
--- a/t/t4150-am.sh
+++ b/t/t4150-am.sh
@@ -652,7 +652,7 @@ test_expect_success 'am -3 -q is quiet' '
git checkout -f lorem2 &&
git reset base3way --hard &&
git am -3 -q lorem-move.patch >output.out 2>&1 &&
- ! test -s output.out
+ test_must_be_empty output.out
'
test_expect_success 'am pauses on conflict' '
@@ -875,7 +875,7 @@ test_expect_success 'am -q is quiet' '
git checkout first &&
test_tick &&
git am -q <patch1 >output.out 2>&1 &&
- ! test -s output.out
+ test_must_be_empty output.out
'
test_expect_success 'am empty-file does not infloop' '
diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh
index 65da74c766..428b3c1e9e 100755
--- a/t/t4200-rerere.sh
+++ b/t/t4200-rerere.sh
@@ -577,4 +577,69 @@ test_expect_success 'multiple identical conflicts' '
count_pre_post 0 0
'
+test_expect_success 'rerere with unexpected conflict markers does not crash' '
+ git reset --hard &&
+
+ git checkout -b branch-1 master &&
+ echo "bar" >test &&
+ git add test &&
+ git commit -q -m two &&
+
+ git reset --hard &&
+ git checkout -b branch-2 master &&
+ echo "foo" >test &&
+ git add test &&
+ git commit -q -a -m one &&
+
+ test_must_fail git merge branch-1 &&
+ echo "<<<<<<< a" >test &&
+ git rerere &&
+
+ git rerere clear
+'
+
+test_expect_success 'rerere with inner conflict markers' '
+ git reset --hard &&
+
+ git checkout -b A master &&
+ echo "bar" >test &&
+ git add test &&
+ git commit -q -m two &&
+ echo "baz" >test &&
+ git add test &&
+ git commit -q -m three &&
+
+ git reset --hard &&
+ git checkout -b B master &&
+ echo "foo" >test &&
+ git add test &&
+ git commit -q -a -m one &&
+
+ test_must_fail git merge A~ &&
+ git add test &&
+ git commit -q -m "will solve conflicts later" &&
+ test_must_fail git merge A &&
+
+ echo "resolved" >test &&
+ git add test &&
+ git commit -q -m "solved conflict" &&
+
+ echo "resolved" >expect &&
+
+ git reset --hard HEAD~~ &&
+ test_must_fail git merge A~ &&
+ git add test &&
+ git commit -q -m "will solve conflicts later" &&
+ test_must_fail git merge A &&
+ cat test >actual &&
+ test_cmp expect actual &&
+
+ git add test &&
+ git commit -m "rerere solved conflict" &&
+ git reset --hard HEAD~ &&
+ test_must_fail git merge A &&
+ cat test >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh
index 58c2773676..d3a7ce6bbb 100755
--- a/t/t4201-shortlog.sh
+++ b/t/t4201-shortlog.sh
@@ -192,7 +192,7 @@ test_expect_success 'shortlog with revision pseudo options' '
test_expect_success 'shortlog with --output=<file>' '
git shortlog --output=shortlog -1 master >output &&
- test ! -s output &&
+ test_must_be_empty output &&
test_line_count = 3 shortlog
'
diff --git a/t/t4203-mailmap.sh b/t/t4203-mailmap.sh
index 0dd8b65d7c..43b1522ea2 100755
--- a/t/t4203-mailmap.sh
+++ b/t/t4203-mailmap.sh
@@ -461,11 +461,9 @@ test_expect_success 'Grep author with log.mailmap' '
test_cmp expect actual
'
->expect
-
test_expect_success 'Only grep replaced author with --use-mailmap' '
git log --use-mailmap --author "<cto@coompany.xx>" >actual &&
- test_cmp expect actual
+ test_must_be_empty actual
'
# git blame
diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh
index 2052cadb11..978a8a66ff 100755
--- a/t/t4205-log-pretty-formats.sh
+++ b/t/t4205-log-pretty-formats.sh
@@ -598,4 +598,27 @@ test_expect_success ':only and :unfold work together' '
test_cmp expect actual
'
+test_expect_success 'trailer parsing not fooled by --- line' '
+ git commit --allow-empty -F - <<-\EOF &&
+ this is the subject
+
+ This is the body. The message has a "---" line which would confuse a
+ message+patch parser. But here we know we have only a commit message,
+ so we get it right.
+
+ trailer: wrong
+ ---
+ This is more body.
+
+ trailer: right
+ EOF
+
+ {
+ echo "trailer: right" &&
+ echo
+ } >expect &&
+ git log --no-walk --format="%(trailers)" >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh
index 436b13ad21..ef1322148e 100755
--- a/t/t4211-line-log.sh
+++ b/t/t4211-line-log.sh
@@ -101,7 +101,7 @@ test_expect_success '-L with --first-parent and a merge' '
test_expect_success '-L with --output' '
git checkout parallel-change &&
git log --output=log -L :main:b.c >output &&
- test ! -s output &&
+ test_must_be_empty output &&
test_line_count = 70 log
'
diff --git a/t/t4212-log-corrupt.sh b/t/t4212-log-corrupt.sh
index 22aa8b7c0e..03b952c90d 100755
--- a/t/t4212-log-corrupt.sh
+++ b/t/t4212-log-corrupt.sh
@@ -26,22 +26,20 @@ test_expect_success 'git log with broken author email' '
echo
echo " foo"
} >expect.out &&
- : >expect.err &&
git log broken_email >actual.out 2>actual.err &&
test_cmp expect.out actual.out &&
- test_cmp expect.err actual.err
+ test_must_be_empty actual.err
'
test_expect_success 'git log --format with broken author email' '
echo "A U Thor+author@example.com+Thu Apr 7 15:13:13 2005 -0700" >expect.out &&
- : >expect.err &&
git log --format="%an+%ae+%ad" broken_email >actual.out 2>actual.err &&
test_cmp expect.out actual.out &&
- test_cmp expect.err actual.err
+ test_must_be_empty actual.err
'
munge_author_date () {
diff --git a/t/t4256-am-format-flowed.sh b/t/t4256-am-format-flowed.sh
new file mode 100755
index 0000000000..6340310e9a
--- /dev/null
+++ b/t/t4256-am-format-flowed.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+test_description='test format=flowed support of git am'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ cp "$TEST_DIRECTORY/t4256/1/mailinfo.c.orig" mailinfo.c &&
+ git add mailinfo.c &&
+ git commit -m initial
+'
+
+test_expect_success 'am with format=flowed' '
+ git am <"$TEST_DIRECTORY/t4256/1/patch" >stdout 2>stderr &&
+ test_i18ngrep "warning: Patch sent with format=flowed" stderr &&
+ test_cmp "$TEST_DIRECTORY/t4256/1/mailinfo.c" mailinfo.c
+'
+
+test_done
diff --git a/t/t4256/1/mailinfo.c b/t/t4256/1/mailinfo.c
new file mode 100644
index 0000000000..b395adbdf2
--- /dev/null
+++ b/t/t4256/1/mailinfo.c
@@ -0,0 +1,1245 @@
+#include "cache.h"
+#include "config.h"
+#include "utf8.h"
+#include "strbuf.h"
+#include "mailinfo.h"
+
+static void cleanup_space(struct strbuf *sb)
+{
+ size_t pos, cnt;
+ for (pos = 0; pos < sb->len; pos++) {
+ if (isspace(sb->buf[pos])) {
+ sb->buf[pos] = ' ';
+ for (cnt = 0; isspace(sb->buf[pos + cnt + 1]); cnt++);
+ strbuf_remove(sb, pos + 1, cnt);
+ }
+ }
+}
+
+static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email)
+{
+ struct strbuf *src = name;
+ if (name->len < 3 || 60 < name->len || strchr(name->buf, '@') ||
+ strchr(name->buf, '<') || strchr(name->buf, '>'))
+ src = email;
+ else if (name == out)
+ return;
+ strbuf_reset(out);
+ strbuf_addbuf(out, src);
+}
+
+static void parse_bogus_from(struct mailinfo *mi, const struct strbuf *line)
+{
+ /* John Doe <johndoe> */
+
+ char *bra, *ket;
+ /* This is fallback, so do not bother if we already have an
+ * e-mail address.
+ */
+ if (mi->email.len)
+ return;
+
+ bra = strchr(line->buf, '<');
+ if (!bra)
+ return;
+ ket = strchr(bra, '>');
+ if (!ket)
+ return;
+
+ strbuf_reset(&mi->email);
+ strbuf_add(&mi->email, bra + 1, ket - bra - 1);
+
+ strbuf_reset(&mi->name);
+ strbuf_add(&mi->name, line->buf, bra - line->buf);
+ strbuf_trim(&mi->name);
+ get_sane_name(&mi->name, &mi->name, &mi->email);
+}
+
+static const char *unquote_comment(struct strbuf *outbuf, const char *in)
+{
+ int c;
+ int take_next_literally = 0;
+
+ strbuf_addch(outbuf, '(');
+
+ while ((c = *in++) != 0) {
+ if (take_next_literally == 1) {
+ take_next_literally = 0;
+ } else {
+ switch (c) {
+ case '\\':
+ take_next_literally = 1;
+ continue;
+ case '(':
+ in = unquote_comment(outbuf, in);
+ continue;
+ case ')':
+ strbuf_addch(outbuf, ')');
+ return in;
+ }
+ }
+
+ strbuf_addch(outbuf, c);
+ }
+
+ return in;
+}
+
+static const char *unquote_quoted_string(struct strbuf *outbuf, const char *in)
+{
+ int c;
+ int take_next_literally = 0;
+
+ while ((c = *in++) != 0) {
+ if (take_next_literally == 1) {
+ take_next_literally = 0;
+ } else {
+ switch (c) {
+ case '\\':
+ take_next_literally = 1;
+ continue;
+ case '"':
+ return in;
+ }
+ }
+
+ strbuf_addch(outbuf, c);
+ }
+
+ return in;
+}
+
+static void unquote_quoted_pair(struct strbuf *line)
+{
+ struct strbuf outbuf;
+ const char *in = line->buf;
+ int c;
+
+ strbuf_init(&outbuf, line->len);
+
+ while ((c = *in++) != 0) {
+ switch (c) {
+ case '"':
+ in = unquote_quoted_string(&outbuf, in);
+ continue;
+ case '(':
+ in = unquote_comment(&outbuf, in);
+ continue;
+ }
+
+ strbuf_addch(&outbuf, c);
+ }
+
+ strbuf_swap(&outbuf, line);
+ strbuf_release(&outbuf);
+
+}
+
+static void handle_from(struct mailinfo *mi, const struct strbuf *from)
+{
+ char *at;
+ size_t el;
+ struct strbuf f;
+
+ strbuf_init(&f, from->len);
+ strbuf_addbuf(&f, from);
+
+ unquote_quoted_pair(&f);
+
+ at = strchr(f.buf, '@');
+ if (!at) {
+ parse_bogus_from(mi, from);
+ goto out;
+ }
+
+ /*
+ * If we already have one email, don't take any confusing lines
+ */
+ if (mi->email.len && strchr(at + 1, '@'))
+ goto out;
+
+ /* Pick up the string around '@', possibly delimited with <>
+ * pair; that is the email part.
+ */
+ while (at > f.buf) {
+ char c = at[-1];
+ if (isspace(c))
+ break;
+ if (c == '<') {
+ at[-1] = ' ';
+ break;
+ }
+ at--;
+ }
+ el = strcspn(at, " \n\t\r\v\f>");
+ strbuf_reset(&mi->email);
+ strbuf_add(&mi->email, at, el);
+ strbuf_remove(&f, at - f.buf, el + (at[el] ? 1 : 0));
+
+ /* The remainder is name. It could be
+ *
+ * - "John Doe <john.doe@xz>" (a), or
+ * - "john.doe@xz (John Doe)" (b), or
+ * - "John (zzz) Doe <john.doe@xz> (Comment)" (c)
+ *
+ * but we have removed the email part, so
+ *
+ * - remove extra spaces which could stay after email (case 'c'), and
+ * - trim from both ends, possibly removing the () pair at the end
+ * (cases 'a' and 'b').
+ */
+ cleanup_space(&f);
+ strbuf_trim(&f);
+ if (f.buf[0] == '(' && f.len && f.buf[f.len - 1] == ')') {
+ strbuf_remove(&f, 0, 1);
+ strbuf_setlen(&f, f.len - 1);
+ }
+
+ get_sane_name(&mi->name, &f, &mi->email);
+out:
+ strbuf_release(&f);
+}
+
+static void handle_header(struct strbuf **out, const struct strbuf *line)
+{
+ if (!*out) {
+ *out = xmalloc(sizeof(struct strbuf));
+ strbuf_init(*out, line->len);
+ } else
+ strbuf_reset(*out);
+
+ strbuf_addbuf(*out, line);
+}
+
+/* NOTE NOTE NOTE. We do not claim we do full MIME. We just attempt
+ * to have enough heuristics to grok MIME encoded patches often found
+ * on our mailing lists. For example, we do not even treat header lines
+ * case insensitively.
+ */
+
+static int slurp_attr(const char *line, const char *name, struct strbuf *attr)
+{
+ const char *ends, *ap = strcasestr(line, name);
+ size_t sz;
+
+ strbuf_setlen(attr, 0);
+ if (!ap)
+ return 0;
+ ap += strlen(name);
+ if (*ap == '"') {
+ ap++;
+ ends = "\"";
+ }
+ else
+ ends = "; \t";
+ sz = strcspn(ap, ends);
+ strbuf_add(attr, ap, sz);
+ return 1;
+}
+
+static int has_attr_value(const char *line, const char *name, const char *value)
+{
+ struct strbuf sb = STRBUF_INIT;
+ int rc = slurp_attr(line, name, &sb) && !strcasecmp(sb.buf, value);
+ strbuf_release(&sb);
+ return rc;
+}
+
+static void handle_content_type(struct mailinfo *mi, struct strbuf *line)
+{
+ struct strbuf *boundary = xmalloc(sizeof(struct strbuf));
+ strbuf_init(boundary, line->len);
+
+ mi->format_flowed = has_attr_value(line->buf, "format=", "flowed");
+ mi->delsp = has_attr_value(line->buf, "delsp=", "yes");
+
+ if (slurp_attr(line->buf, "boundary=", boundary)) {
+ strbuf_insert(boundary, 0, "--", 2);
+ if (++mi->content_top >= &mi->content[MAX_BOUNDARIES]) {
+ error("Too many boundaries to handle");
+ mi->input_error = -1;
+ mi->content_top = &mi->content[MAX_BOUNDARIES] - 1;
+ return;
+ }
+ *(mi->content_top) = boundary;
+ boundary = NULL;
+ }
+ slurp_attr(line->buf, "charset=", &mi->charset);
+
+ if (boundary) {
+ strbuf_release(boundary);
+ free(boundary);
+ }
+}
+
+static void handle_content_transfer_encoding(struct mailinfo *mi,
+ const struct strbuf *line)
+{
+ if (strcasestr(line->buf, "base64"))
+ mi->transfer_encoding = TE_BASE64;
+ else if (strcasestr(line->buf, "quoted-printable"))
+ mi->transfer_encoding = TE_QP;
+ else
+ mi->transfer_encoding = TE_DONTCARE;
+}
+
+static int is_multipart_boundary(struct mailinfo *mi, const struct strbuf *line)
+{
+ struct strbuf *content_top = *(mi->content_top);
+
+ return ((content_top->len <= line->len) &&
+ !memcmp(line->buf, content_top->buf, content_top->len));
+}
+
+static void cleanup_subject(struct mailinfo *mi, struct strbuf *subject)
+{
+ size_t at = 0;
+
+ while (at < subject->len) {
+ char *pos;
+ size_t remove;
+
+ switch (subject->buf[at]) {
+ case 'r': case 'R':
+ if (subject->len <= at + 3)
+ break;
+ if ((subject->buf[at + 1] == 'e' ||
+ subject->buf[at + 1] == 'E') &&
+ subject->buf[at + 2] == ':') {
+ strbuf_remove(subject, at, 3);
+ continue;
+ }
+ at++;
+ break;
+ case ' ': case '\t': case ':':
+ strbuf_remove(subject, at, 1);
+ continue;
+ case '[':
+ pos = strchr(subject->buf + at, ']');
+ if (!pos)
+ break;
+ remove = pos - subject->buf + at + 1;
+ if (!mi->keep_non_patch_brackets_in_subject ||
+ (7 <= remove &&
+ memmem(subject->buf + at, remove, "PATCH", 5)))
+ strbuf_remove(subject, at, remove);
+ else {
+ at += remove;
+ /*
+ * If the input had a space after the ], keep
+ * it. We don't bother with finding the end of
+ * the space, since we later normalize it
+ * anyway.
+ */
+ if (isspace(subject->buf[at]))
+ at += 1;
+ }
+ continue;
+ }
+ break;
+ }
+ strbuf_trim(subject);
+}
+
+#define MAX_HDR_PARSED 10
+static const char *header[MAX_HDR_PARSED] = {
+ "From","Subject","Date",
+};
+
+static inline int cmp_header(const struct strbuf *line, const char *hdr)
+{
+ int len = strlen(hdr);
+ return !strncasecmp(line->buf, hdr, len) && line->len > len &&
+ line->buf[len] == ':' && isspace(line->buf[len + 1]);
+}
+
+static int is_format_patch_separator(const char *line, int len)
+{
+ static const char SAMPLE[] =
+ "From e6807f3efca28b30decfecb1732a56c7db1137ee Mon Sep 17 00:00:00 2001\n";
+ const char *cp;
+
+ if (len != strlen(SAMPLE))
+ return 0;
+ if (!skip_prefix(line, "From ", &cp))
+ return 0;
+ if (strspn(cp, "0123456789abcdef") != 40)
+ return 0;
+ cp += 40;
+ return !memcmp(SAMPLE + (cp - line), cp, strlen(SAMPLE) - (cp - line));
+}
+
+static struct strbuf *decode_q_segment(const struct strbuf *q_seg, int rfc2047)
+{
+ const char *in = q_seg->buf;
+ int c;
+ struct strbuf *out = xmalloc(sizeof(struct strbuf));
+ strbuf_init(out, q_seg->len);
+
+ while ((c = *in++) != 0) {
+ if (c == '=') {
+ int ch, d = *in;
+ if (d == '\n' || !d)
+ break; /* drop trailing newline */
+ ch = hex2chr(in);
+ if (ch >= 0) {
+ strbuf_addch(out, ch);
+ in += 2;
+ continue;
+ }
+ /* garbage -- fall through */
+ }
+ if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */
+ c = 0x20;
+ strbuf_addch(out, c);
+ }
+ return out;
+}
+
+static struct strbuf *decode_b_segment(const struct strbuf *b_seg)
+{
+ /* Decode in..ep, possibly in-place to ot */
+ int c, pos = 0, acc = 0;
+ const char *in = b_seg->buf;
+ struct strbuf *out = xmalloc(sizeof(struct strbuf));
+ strbuf_init(out, b_seg->len);
+
+ while ((c = *in++) != 0) {
+ if (c == '+')
+ c = 62;
+ else if (c == '/')
+ c = 63;
+ else if ('A' <= c && c <= 'Z')
+ c -= 'A';
+ else if ('a' <= c && c <= 'z')
+ c -= 'a' - 26;
+ else if ('0' <= c && c <= '9')
+ c -= '0' - 52;
+ else
+ continue; /* garbage */
+ switch (pos++) {
+ case 0:
+ acc = (c << 2);
+ break;
+ case 1:
+ strbuf_addch(out, (acc | (c >> 4)));
+ acc = (c & 15) << 4;
+ break;
+ case 2:
+ strbuf_addch(out, (acc | (c >> 2)));
+ acc = (c & 3) << 6;
+ break;
+ case 3:
+ strbuf_addch(out, (acc | c));
+ acc = pos = 0;
+ break;
+ }
+ }
+ return out;
+}
+
+static int convert_to_utf8(struct mailinfo *mi,
+ struct strbuf *line, const char *charset)
+{
+ char *out;
+
+ if (!mi->metainfo_charset || !charset || !*charset)
+ return 0;
+
+ if (same_encoding(mi->metainfo_charset, charset))
+ return 0;
+ out = reencode_string(line->buf, mi->metainfo_charset, charset);
+ if (!out) {
+ mi->input_error = -1;
+ return error("cannot convert from %s to %s",
+ charset, mi->metainfo_charset);
+ }
+ strbuf_attach(line, out, strlen(out), strlen(out));
+ return 0;
+}
+
+static void decode_header(struct mailinfo *mi, struct strbuf *it)
+{
+ char *in, *ep, *cp;
+ struct strbuf outbuf = STRBUF_INIT, *dec;
+ struct strbuf charset_q = STRBUF_INIT, piecebuf = STRBUF_INIT;
+ int found_error = 1; /* pessimism */
+
+ in = it->buf;
+ while (in - it->buf <= it->len && (ep = strstr(in, "=?")) != NULL) {
+ int encoding;
+ strbuf_reset(&charset_q);
+ strbuf_reset(&piecebuf);
+
+ if (in != ep) {
+ /*
+ * We are about to process an encoded-word
+ * that begins at ep, but there is something
+ * before the encoded word.
+ */
+ char *scan;
+ for (scan = in; scan < ep; scan++)
+ if (!isspace(*scan))
+ break;
+
+ if (scan != ep || in == it->buf) {
+ /*
+ * We should not lose that "something",
+ * unless we have just processed an
+ * encoded-word, and there is only LWS
+ * before the one we are about to process.
+ */
+ strbuf_add(&outbuf, in, ep - in);
+ }
+ }
+ /* E.g.
+ * ep : "=?iso-2022-jp?B?GyR...?= foo"
+ * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz"
+ */
+ ep += 2;
+
+ if (ep - it->buf >= it->len || !(cp = strchr(ep, '?')))
+ goto release_return;
+
+ if (cp + 3 - it->buf > it->len)
+ goto release_return;
+ strbuf_add(&charset_q, ep, cp - ep);
+
+ encoding = cp[1];
+ if (!encoding || cp[2] != '?')
+ goto release_return;
+ ep = strstr(cp + 3, "?=");
+ if (!ep)
+ goto release_return;
+ strbuf_add(&piecebuf, cp + 3, ep - cp - 3);
+ switch (tolower(encoding)) {
+ default:
+ goto release_return;
+ case 'b':
+ dec = decode_b_segment(&piecebuf);
+ break;
+ case 'q':
+ dec = decode_q_segment(&piecebuf, 1);
+ break;
+ }
+ if (convert_to_utf8(mi, dec, charset_q.buf))
+ goto release_return;
+
+ strbuf_addbuf(&outbuf, dec);
+ strbuf_release(dec);
+ free(dec);
+ in = ep + 2;
+ }
+ strbuf_addstr(&outbuf, in);
+ strbuf_reset(it);
+ strbuf_addbuf(it, &outbuf);
+ found_error = 0;
+release_return:
+ strbuf_release(&outbuf);
+ strbuf_release(&charset_q);
+ strbuf_release(&piecebuf);
+
+ if (found_error)
+ mi->input_error = -1;
+}
+
+static int check_header(struct mailinfo *mi,
+ const struct strbuf *line,
+ struct strbuf *hdr_data[], int overwrite)
+{
+ int i, ret = 0, len;
+ struct strbuf sb = STRBUF_INIT;
+
+ /* search for the interesting parts */
+ for (i = 0; header[i]; i++) {
+ int len = strlen(header[i]);
+ if ((!hdr_data[i] || overwrite) && cmp_header(line, header[i])) {
+ /* Unwrap inline B and Q encoding, and optionally
+ * normalize the meta information to utf8.
+ */
+ strbuf_add(&sb, line->buf + len + 2, line->len - len - 2);
+ decode_header(mi, &sb);
+ handle_header(&hdr_data[i], &sb);
+ ret = 1;
+ goto check_header_out;
+ }
+ }
+
+ /* Content stuff */
+ if (cmp_header(line, "Content-Type")) {
+ len = strlen("Content-Type: ");
+ strbuf_add(&sb, line->buf + len, line->len - len);
+ decode_header(mi, &sb);
+ strbuf_insert(&sb, 0, "Content-Type: ", len);
+ handle_content_type(mi, &sb);
+ ret = 1;
+ goto check_header_out;
+ }
+ if (cmp_header(line, "Content-Transfer-Encoding")) {
+ len = strlen("Content-Transfer-Encoding: ");
+ strbuf_add(&sb, line->buf + len, line->len - len);
+ decode_header(mi, &sb);
+ handle_content_transfer_encoding(mi, &sb);
+ ret = 1;
+ goto check_header_out;
+ }
+ if (cmp_header(line, "Message-Id")) {
+ len = strlen("Message-Id: ");
+ strbuf_add(&sb, line->buf + len, line->len - len);
+ decode_header(mi, &sb);
+ if (mi->add_message_id)
+ mi->message_id = strbuf_detach(&sb, NULL);
+ ret = 1;
+ goto check_header_out;
+ }
+
+check_header_out:
+ strbuf_release(&sb);
+ return ret;
+}
+
+/*
+ * Returns 1 if the given line or any line beginning with the given line is an
+ * in-body header (that is, check_header will succeed when passed
+ * mi->s_hdr_data).
+ */
+static int is_inbody_header(const struct mailinfo *mi,
+ const struct strbuf *line)
+{
+ int i;
+ for (i = 0; header[i]; i++)
+ if (!mi->s_hdr_data[i] && cmp_header(line, header[i]))
+ return 1;
+ return 0;
+}
+
+static void decode_transfer_encoding(struct mailinfo *mi, struct strbuf *line)
+{
+ struct strbuf *ret;
+
+ switch (mi->transfer_encoding) {
+ case TE_QP:
+ ret = decode_q_segment(line, 0);
+ break;
+ case TE_BASE64:
+ ret = decode_b_segment(line);
+ break;
+ case TE_DONTCARE:
+ default:
+ return;
+ }
+ strbuf_reset(line);
+ strbuf_addbuf(line, ret);
+ strbuf_release(ret);
+ free(ret);
+}
+
+static inline int patchbreak(const struct strbuf *line)
+{
+ size_t i;
+
+ /* Beginning of a "diff -" header? */
+ if (starts_with(line->buf, "diff -"))
+ return 1;
+
+ /* CVS "Index: " line? */
+ if (starts_with(line->buf, "Index: "))
+ return 1;
+
+ /*
+ * "--- <filename>" starts patches without headers
+ * "---<sp>*" is a manual separator
+ */
+ if (line->len < 4)
+ return 0;
+
+ if (starts_with(line->buf, "---")) {
+ /* space followed by a filename? */
+ if (line->buf[3] == ' ' && !isspace(line->buf[4]))
+ return 1;
+ /* Just whitespace? */
+ for (i = 3; i < line->len; i++) {
+ unsigned char c = line->buf[i];
+ if (c == '\n')
+ return 1;
+ if (!isspace(c))
+ break;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+static int is_scissors_line(const char *line)
+{
+ const char *c;
+ int scissors = 0, gap = 0;
+ const char *first_nonblank = NULL, *last_nonblank = NULL;
+ int visible, perforation = 0, in_perforation = 0;
+
+ for (c = line; *c; c++) {
+ if (isspace(*c)) {
+ if (in_perforation) {
+ perforation++;
+ gap++;
+ }
+ continue;
+ }
+ last_nonblank = c;
+ if (first_nonblank == NULL)
+ first_nonblank = c;
+ if (*c == '-') {
+ in_perforation = 1;
+ perforation++;
+ continue;
+ }
+ if ((!memcmp(c, ">8", 2) || !memcmp(c, "8<", 2) ||
+ !memcmp(c, ">%", 2) || !memcmp(c, "%<", 2))) {
+ in_perforation = 1;
+ perforation += 2;
+ scissors += 2;
+ c++;
+ continue;
+ }
+ in_perforation = 0;
+ }
+
+ /*
+ * The mark must be at least 8 bytes long (e.g. "-- >8 --").
+ * Even though there can be arbitrary cruft on the same line
+ * (e.g. "cut here"), in order to avoid misidentification, the
+ * perforation must occupy more than a third of the visible
+ * width of the line, and dashes and scissors must occupy more
+ * than half of the perforation.
+ */
+
+ if (first_nonblank && last_nonblank)
+ visible = last_nonblank - first_nonblank + 1;
+ else
+ visible = 0;
+ return (scissors && 8 <= visible &&
+ visible < perforation * 3 &&
+ gap * 2 < perforation);
+}
+
+static void flush_inbody_header_accum(struct mailinfo *mi)
+{
+ if (!mi->inbody_header_accum.len)
+ return;
+ if (!check_header(mi, &mi->inbody_header_accum, mi->s_hdr_data, 0))
+ BUG("inbody_header_accum, if not empty, must always contain a valid in-body header");
+ strbuf_reset(&mi->inbody_header_accum);
+}
+
+static int check_inbody_header(struct mailinfo *mi, const struct strbuf *line)
+{
+ if (mi->inbody_header_accum.len &&
+ (line->buf[0] == ' ' || line->buf[0] == '\t')) {
+ if (mi->use_scissors && is_scissors_line(line->buf)) {
+ /*
+ * This is a scissors line; do not consider this line
+ * as a header continuation line.
+ */
+ flush_inbody_header_accum(mi);
+ return 0;
+ }
+ strbuf_strip_suffix(&mi->inbody_header_accum, "\n");
+ strbuf_addbuf(&mi->inbody_header_accum, line);
+ return 1;
+ }
+
+ flush_inbody_header_accum(mi);
+
+ if (starts_with(line->buf, ">From") && isspace(line->buf[5]))
+ return is_format_patch_separator(line->buf + 1, line->len - 1);
+ if (starts_with(line->buf, "[PATCH]") && isspace(line->buf[7])) {
+ int i;
+ for (i = 0; header[i]; i++)
+ if (!strcmp("Subject", header[i])) {
+ handle_header(&mi->s_hdr_data[i], line);
+ return 1;
+ }
+ return 0;
+ }
+ if (is_inbody_header(mi, line)) {
+ strbuf_addbuf(&mi->inbody_header_accum, line);
+ return 1;
+ }
+ return 0;
+}
+
+static int handle_commit_msg(struct mailinfo *mi, struct strbuf *line)
+{
+ assert(!mi->filter_stage);
+
+ if (mi->header_stage) {
+ if (!line->len || (line->len == 1 && line->buf[0] == '\n')) {
+ if (mi->inbody_header_accum.len) {
+ flush_inbody_header_accum(mi);
+ mi->header_stage = 0;
+ }
+ return 0;
+ }
+ }
+
+ if (mi->use_inbody_headers && mi->header_stage) {
+ mi->header_stage = check_inbody_header(mi, line);
+ if (mi->header_stage)
+ return 0;
+ } else
+ /* Only trim the first (blank) line of the commit message
+ * when ignoring in-body headers.
+ */
+ mi->header_stage = 0;
+
+ /* normalize the log message to UTF-8. */
+ if (convert_to_utf8(mi, line, mi->charset.buf))
+ return 0; /* mi->input_error already set */
+
+ if (mi->use_scissors && is_scissors_line(line->buf)) {
+ int i;
+
+ strbuf_setlen(&mi->log_message, 0);
+ mi->header_stage = 1;
+
+ /*
+ * We may have already read "secondary headers"; purge
+ * them to give ourselves a clean restart.
+ */
+ for (i = 0; header[i]; i++) {
+ if (mi->s_hdr_data[i])
+ strbuf_release(mi->s_hdr_data[i]);
+ mi->s_hdr_data[i] = NULL;
+ }
+ return 0;
+ }
+
+ if (patchbreak(line)) {
+ if (mi->message_id)
+ strbuf_addf(&mi->log_message,
+ "Message-Id: %s\n", mi->message_id);
+ return 1;
+ }
+
+ strbuf_addbuf(&mi->log_message, line);
+ return 0;
+}
+
+static void handle_patch(struct mailinfo *mi, const struct strbuf *line)
+{
+ fwrite(line->buf, 1, line->len, mi->patchfile);
+ mi->patch_lines++;
+}
+
+static void handle_filter(struct mailinfo *mi, struct strbuf *line)
+{
+ switch (mi->filter_stage) {
+ case 0:
+ if (!handle_commit_msg(mi, line))
+ break;
+ mi->filter_stage++;
+ /* fallthrough */
+ case 1:
+ handle_patch(mi, line);
+ break;
+ }
+}
+
+static int is_rfc2822_header(const struct strbuf *line)
+{
+ /*
+ * The section that defines the loosest possible
+ * field name is "3.6.8 Optional fields".
+ *
+ * optional-field = field-name ":" unstructured CRLF
+ * field-name = 1*ftext
+ * ftext = %d33-57 / %59-126
+ */
+ int ch;
+ char *cp = line->buf;
+
+ /* Count mbox From headers as headers */
+ if (starts_with(cp, "From ") || starts_with(cp, ">From "))
+ return 1;
+
+ while ((ch = *cp++)) {
+ if (ch == ':')
+ return 1;
+ if ((33 <= ch && ch <= 57) ||
+ (59 <= ch && ch <= 126))
+ continue;
+ break;
+ }
+ return 0;
+}
+
+static int read_one_header_line(struct strbuf *line, FILE *in)
+{
+ struct strbuf continuation = STRBUF_INIT;
+
+ /* Get the first part of the line. */
+ if (strbuf_getline_lf(line, in))
+ return 0;
+
+ /*
+ * Is it an empty line or not a valid rfc2822 header?
+ * If so, stop here, and return false ("not a header")
+ */
+ strbuf_rtrim(line);
+ if (!line->len || !is_rfc2822_header(line)) {
+ /* Re-add the newline */
+ strbuf_addch(line, '\n');
+ return 0;
+ }
+
+ /*
+ * Now we need to eat all the continuation lines..
+ * Yuck, 2822 header "folding"
+ */
+ for (;;) {
+ int peek;
+
+ peek = fgetc(in);
+ if (peek == EOF)
+ break;
+ ungetc(peek, in);
+ if (peek != ' ' && peek != '\t')
+ break;
+ if (strbuf_getline_lf(&continuation, in))
+ break;
+ continuation.buf[0] = ' ';
+ strbuf_rtrim(&continuation);
+ strbuf_addbuf(line, &continuation);
+ }
+ strbuf_release(&continuation);
+
+ return 1;
+}
+
+static int find_boundary(struct mailinfo *mi, struct strbuf *line)
+{
+ while (!strbuf_getline_lf(line, mi->input)) {
+ if (*(mi->content_top) && is_multipart_boundary(mi, line))
+ return 1;
+ }
+ return 0;
+}
+
+static int handle_boundary(struct mailinfo *mi, struct strbuf *line)
+{
+ struct strbuf newline = STRBUF_INIT;
+
+ strbuf_addch(&newline, '\n');
+again:
+ if (line->len >= (*(mi->content_top))->len + 2 &&
+ !memcmp(line->buf + (*(mi->content_top))->len, "--", 2)) {
+ /* we hit an end boundary */
+ /* pop the current boundary off the stack */
+ strbuf_release(*(mi->content_top));
+ FREE_AND_NULL(*(mi->content_top));
+
+ /* technically won't happen as is_multipart_boundary()
+ will fail first. But just in case..
+ */
+ if (--mi->content_top < mi->content) {
+ error("Detected mismatched boundaries, can't recover");
+ mi->input_error = -1;
+ mi->content_top = mi->content;
+ strbuf_release(&newline);
+ return 0;
+ }
+ handle_filter(mi, &newline);
+ strbuf_release(&newline);
+ if (mi->input_error)
+ return 0;
+
+ /* skip to the next boundary */
+ if (!find_boundary(mi, line))
+ return 0;
+ goto again;
+ }
+
+ /* set some defaults */
+ mi->transfer_encoding = TE_DONTCARE;
+ strbuf_reset(&mi->charset);
+
+ /* slurp in this section's info */
+ while (read_one_header_line(line, mi->input))
+ check_header(mi, line, mi->p_hdr_data, 0);
+
+ strbuf_release(&newline);
+ /* replenish line */
+ if (strbuf_getline_lf(line, mi->input))
+ return 0;
+ strbuf_addch(line, '\n');
+ return 1;
+}
+
+static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,
+ struct strbuf *prev)
+{
+ size_t len = line->len;
+ const char *rest;
+
+ if (!mi->format_flowed) {
+ handle_filter(mi, line);
+ return;
+ }
+
+ if (line->buf[len - 1] == '\n') {
+ len--;
+ if (len && line->buf[len - 1] == '\r')
+ len--;
+ }
+
+ /* Keep signature separator as-is. */
+ if (skip_prefix(line->buf, "-- ", &rest) && rest - line->buf == len) {
+ if (prev->len) {
+ handle_filter(mi, prev);
+ strbuf_reset(prev);
+ }
+ handle_filter(mi, line);
+ return;
+ }
+
+ /* Unstuff space-stuffed line. */
+ if (len && line->buf[0] == ' ') {
+ strbuf_remove(line, 0, 1);
+ len--;
+ }
+
+ /* Save flowed line for later, but without the soft line break. */
+ if (len && line->buf[len - 1] == ' ') {
+ strbuf_add(prev, line->buf, len - !!mi->delsp);
+ return;
+ }
+
+ /* Prepend any previous partial lines */
+ strbuf_insert(line, 0, prev->buf, prev->len);
+ strbuf_reset(prev);
+
+ handle_filter(mi, line);
+}
+
+static void handle_body(struct mailinfo *mi, struct strbuf *line)
+{
+ struct strbuf prev = STRBUF_INIT;
+
+ /* Skip up to the first boundary */
+ if (*(mi->content_top)) {
+ if (!find_boundary(mi, line))
+ goto handle_body_out;
+ }
+
+ do {
+ /* process any boundary lines */
+ if (*(mi->content_top) && is_multipart_boundary(mi, line)) {
+ /* flush any leftover */
+ if (prev.len) {
+ handle_filter(mi, &prev);
+ strbuf_reset(&prev);
+ }
+ if (!handle_boundary(mi, line))
+ goto handle_body_out;
+ }
+
+ /* Unwrap transfer encoding */
+ decode_transfer_encoding(mi, line);
+
+ switch (mi->transfer_encoding) {
+ case TE_BASE64:
+ case TE_QP:
+ {
+ struct strbuf **lines, **it, *sb;
+
+ /* Prepend any previous partial lines */
+ strbuf_insert(line, 0, prev.buf, prev.len);
+ strbuf_reset(&prev);
+
+ /*
+ * This is a decoded line that may contain
+ * multiple new lines. Pass only one chunk
+ * at a time to handle_filter()
+ */
+ lines = strbuf_split(line, '\n');
+ for (it = lines; (sb = *it); it++) {
+ if (*(it + 1) == NULL) /* The last line */
+ if (sb->buf[sb->len - 1] != '\n') {
+ /* Partial line, save it for later. */
+ strbuf_addbuf(&prev, sb);
+ break;
+ }
+ handle_filter_flowed(mi, sb, &prev);
+ }
+ /*
+ * The partial chunk is saved in "prev" and will be
+ * appended by the next iteration of read_line_with_nul().
+ */
+ strbuf_list_free(lines);
+ break;
+ }
+ default:
+ handle_filter_flowed(mi, line, &prev);
+ }
+
+ if (mi->input_error)
+ break;
+ } while (!strbuf_getwholeline(line, mi->input, '\n'));
+
+ if (prev.len)
+ handle_filter(mi, &prev);
+
+ flush_inbody_header_accum(mi);
+
+handle_body_out:
+ strbuf_release(&prev);
+}
+
+static void output_header_lines(FILE *fout, const char *hdr, const struct strbuf *data)
+{
+ const char *sp = data->buf;
+ while (1) {
+ char *ep = strchr(sp, '\n');
+ int len;
+ if (!ep)
+ len = strlen(sp);
+ else
+ len = ep - sp;
+ fprintf(fout, "%s: %.*s\n", hdr, len, sp);
+ if (!ep)
+ break;
+ sp = ep + 1;
+ }
+}
+
+static void handle_info(struct mailinfo *mi)
+{
+ struct strbuf *hdr;
+ int i;
+
+ for (i = 0; header[i]; i++) {
+ /* only print inbody headers if we output a patch file */
+ if (mi->patch_lines && mi->s_hdr_data[i])
+ hdr = mi->s_hdr_data[i];
+ else if (mi->p_hdr_data[i])
+ hdr = mi->p_hdr_data[i];
+ else
+ continue;
+
+ if (!strcmp(header[i], "Subject")) {
+ if (!mi->keep_subject) {
+ cleanup_subject(mi, hdr);
+ cleanup_space(hdr);
+ }
+ output_header_lines(mi->output, "Subject", hdr);
+ } else if (!strcmp(header[i], "From")) {
+ cleanup_space(hdr);
+ handle_from(mi, hdr);
+ fprintf(mi->output, "Author: %s\n", mi->name.buf);
+ fprintf(mi->output, "Email: %s\n", mi->email.buf);
+ } else {
+ cleanup_space(hdr);
+ fprintf(mi->output, "%s: %s\n", header[i], hdr->buf);
+ }
+ }
+ fprintf(mi->output, "\n");
+}
+
+int mailinfo(struct mailinfo *mi, const char *msg, const char *patch)
+{
+ FILE *cmitmsg;
+ int peek;
+ struct strbuf line = STRBUF_INIT;
+
+ cmitmsg = fopen(msg, "w");
+ if (!cmitmsg) {
+ perror(msg);
+ return -1;
+ }
+ mi->patchfile = fopen(patch, "w");
+ if (!mi->patchfile) {
+ perror(patch);
+ fclose(cmitmsg);
+ return -1;
+ }
+
+ mi->p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*(mi->p_hdr_data)));
+ mi->s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*(mi->s_hdr_data)));
+
+ do {
+ peek = fgetc(mi->input);
+ if (peek == EOF) {
+ fclose(cmitmsg);
+ return error("empty patch: '%s'", patch);
+ }
+ } while (isspace(peek));
+ ungetc(peek, mi->input);
+
+ /* process the email header */
+ while (read_one_header_line(&line, mi->input))
+ check_header(mi, &line, mi->p_hdr_data, 1);
+
+ handle_body(mi, &line);
+ fwrite(mi->log_message.buf, 1, mi->log_message.len, cmitmsg);
+ fclose(cmitmsg);
+ fclose(mi->patchfile);
+
+ handle_info(mi);
+ strbuf_release(&line);
+ return mi->input_error;
+}
+
+static int git_mailinfo_config(const char *var, const char *value, void *mi_)
+{
+ struct mailinfo *mi = mi_;
+
+ if (!starts_with(var, "mailinfo."))
+ return git_default_config(var, value, NULL);
+ if (!strcmp(var, "mailinfo.scissors")) {
+ mi->use_scissors = git_config_bool(var, value);
+ return 0;
+ }
+ /* perhaps others here */
+ return 0;
+}
+
+void setup_mailinfo(struct mailinfo *mi)
+{
+ memset(mi, 0, sizeof(*mi));
+ strbuf_init(&mi->name, 0);
+ strbuf_init(&mi->email, 0);
+ strbuf_init(&mi->charset, 0);
+ strbuf_init(&mi->log_message, 0);
+ strbuf_init(&mi->inbody_header_accum, 0);
+ mi->header_stage = 1;
+ mi->use_inbody_headers = 1;
+ mi->content_top = mi->content;
+ git_config(git_mailinfo_config, mi);
+}
+
+void clear_mailinfo(struct mailinfo *mi)
+{
+ int i;
+
+ strbuf_release(&mi->name);
+ strbuf_release(&mi->email);
+ strbuf_release(&mi->charset);
+ strbuf_release(&mi->inbody_header_accum);
+ free(mi->message_id);
+
+ if (mi->p_hdr_data)
+ for (i = 0; mi->p_hdr_data[i]; i++)
+ strbuf_release(mi->p_hdr_data[i]);
+ free(mi->p_hdr_data);
+ if (mi->s_hdr_data)
+ for (i = 0; mi->s_hdr_data[i]; i++)
+ strbuf_release(mi->s_hdr_data[i]);
+ free(mi->s_hdr_data);
+
+ while (mi->content < mi->content_top) {
+ free(*(mi->content_top));
+ mi->content_top--;
+ }
+
+ strbuf_release(&mi->log_message);
+}
diff --git a/t/t4256/1/mailinfo.c.orig b/t/t4256/1/mailinfo.c.orig
new file mode 100644
index 0000000000..3281a37d51
--- /dev/null
+++ b/t/t4256/1/mailinfo.c.orig
@@ -0,0 +1,1185 @@
+#include "cache.h"
+#include "config.h"
+#include "utf8.h"
+#include "strbuf.h"
+#include "mailinfo.h"
+
+static void cleanup_space(struct strbuf *sb)
+{
+ size_t pos, cnt;
+ for (pos = 0; pos < sb->len; pos++) {
+ if (isspace(sb->buf[pos])) {
+ sb->buf[pos] = ' ';
+ for (cnt = 0; isspace(sb->buf[pos + cnt + 1]); cnt++);
+ strbuf_remove(sb, pos + 1, cnt);
+ }
+ }
+}
+
+static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email)
+{
+ struct strbuf *src = name;
+ if (name->len < 3 || 60 < name->len || strchr(name->buf, '@') ||
+ strchr(name->buf, '<') || strchr(name->buf, '>'))
+ src = email;
+ else if (name == out)
+ return;
+ strbuf_reset(out);
+ strbuf_addbuf(out, src);
+}
+
+static void parse_bogus_from(struct mailinfo *mi, const struct strbuf *line)
+{
+ /* John Doe <johndoe> */
+
+ char *bra, *ket;
+ /* This is fallback, so do not bother if we already have an
+ * e-mail address.
+ */
+ if (mi->email.len)
+ return;
+
+ bra = strchr(line->buf, '<');
+ if (!bra)
+ return;
+ ket = strchr(bra, '>');
+ if (!ket)
+ return;
+
+ strbuf_reset(&mi->email);
+ strbuf_add(&mi->email, bra + 1, ket - bra - 1);
+
+ strbuf_reset(&mi->name);
+ strbuf_add(&mi->name, line->buf, bra - line->buf);
+ strbuf_trim(&mi->name);
+ get_sane_name(&mi->name, &mi->name, &mi->email);
+}
+
+static const char *unquote_comment(struct strbuf *outbuf, const char *in)
+{
+ int c;
+ int take_next_literally = 0;
+
+ strbuf_addch(outbuf, '(');
+
+ while ((c = *in++) != 0) {
+ if (take_next_literally == 1) {
+ take_next_literally = 0;
+ } else {
+ switch (c) {
+ case '\\':
+ take_next_literally = 1;
+ continue;
+ case '(':
+ in = unquote_comment(outbuf, in);
+ continue;
+ case ')':
+ strbuf_addch(outbuf, ')');
+ return in;
+ }
+ }
+
+ strbuf_addch(outbuf, c);
+ }
+
+ return in;
+}
+
+static const char *unquote_quoted_string(struct strbuf *outbuf, const char *in)
+{
+ int c;
+ int take_next_literally = 0;
+
+ while ((c = *in++) != 0) {
+ if (take_next_literally == 1) {
+ take_next_literally = 0;
+ } else {
+ switch (c) {
+ case '\\':
+ take_next_literally = 1;
+ continue;
+ case '"':
+ return in;
+ }
+ }
+
+ strbuf_addch(outbuf, c);
+ }
+
+ return in;
+}
+
+static void unquote_quoted_pair(struct strbuf *line)
+{
+ struct strbuf outbuf;
+ const char *in = line->buf;
+ int c;
+
+ strbuf_init(&outbuf, line->len);
+
+ while ((c = *in++) != 0) {
+ switch (c) {
+ case '"':
+ in = unquote_quoted_string(&outbuf, in);
+ continue;
+ case '(':
+ in = unquote_comment(&outbuf, in);
+ continue;
+ }
+
+ strbuf_addch(&outbuf, c);
+ }
+
+ strbuf_swap(&outbuf, line);
+ strbuf_release(&outbuf);
+
+}
+
+static void handle_from(struct mailinfo *mi, const struct strbuf *from)
+{
+ char *at;
+ size_t el;
+ struct strbuf f;
+
+ strbuf_init(&f, from->len);
+ strbuf_addbuf(&f, from);
+
+ unquote_quoted_pair(&f);
+
+ at = strchr(f.buf, '@');
+ if (!at) {
+ parse_bogus_from(mi, from);
+ goto out;
+ }
+
+ /*
+ * If we already have one email, don't take any confusing lines
+ */
+ if (mi->email.len && strchr(at + 1, '@'))
+ goto out;
+
+ /* Pick up the string around '@', possibly delimited with <>
+ * pair; that is the email part.
+ */
+ while (at > f.buf) {
+ char c = at[-1];
+ if (isspace(c))
+ break;
+ if (c == '<') {
+ at[-1] = ' ';
+ break;
+ }
+ at--;
+ }
+ el = strcspn(at, " \n\t\r\v\f>");
+ strbuf_reset(&mi->email);
+ strbuf_add(&mi->email, at, el);
+ strbuf_remove(&f, at - f.buf, el + (at[el] ? 1 : 0));
+
+ /* The remainder is name. It could be
+ *
+ * - "John Doe <john.doe@xz>" (a), or
+ * - "john.doe@xz (John Doe)" (b), or
+ * - "John (zzz) Doe <john.doe@xz> (Comment)" (c)
+ *
+ * but we have removed the email part, so
+ *
+ * - remove extra spaces which could stay after email (case 'c'), and
+ * - trim from both ends, possibly removing the () pair at the end
+ * (cases 'a' and 'b').
+ */
+ cleanup_space(&f);
+ strbuf_trim(&f);
+ if (f.buf[0] == '(' && f.len && f.buf[f.len - 1] == ')') {
+ strbuf_remove(&f, 0, 1);
+ strbuf_setlen(&f, f.len - 1);
+ }
+
+ get_sane_name(&mi->name, &f, &mi->email);
+out:
+ strbuf_release(&f);
+}
+
+static void handle_header(struct strbuf **out, const struct strbuf *line)
+{
+ if (!*out) {
+ *out = xmalloc(sizeof(struct strbuf));
+ strbuf_init(*out, line->len);
+ } else
+ strbuf_reset(*out);
+
+ strbuf_addbuf(*out, line);
+}
+
+/* NOTE NOTE NOTE. We do not claim we do full MIME. We just attempt
+ * to have enough heuristics to grok MIME encoded patches often found
+ * on our mailing lists. For example, we do not even treat header lines
+ * case insensitively.
+ */
+
+static int slurp_attr(const char *line, const char *name, struct strbuf *attr)
+{
+ const char *ends, *ap = strcasestr(line, name);
+ size_t sz;
+
+ strbuf_setlen(attr, 0);
+ if (!ap)
+ return 0;
+ ap += strlen(name);
+ if (*ap == '"') {
+ ap++;
+ ends = "\"";
+ }
+ else
+ ends = "; \t";
+ sz = strcspn(ap, ends);
+ strbuf_add(attr, ap, sz);
+ return 1;
+}
+
+static void handle_content_type(struct mailinfo *mi, struct strbuf *line)
+{
+ struct strbuf *boundary = xmalloc(sizeof(struct strbuf));
+ strbuf_init(boundary, line->len);
+
+ if (slurp_attr(line->buf, "boundary=", boundary)) {
+ strbuf_insert(boundary, 0, "--", 2);
+ if (++mi->content_top >= &mi->content[MAX_BOUNDARIES]) {
+ error("Too many boundaries to handle");
+ mi->input_error = -1;
+ mi->content_top = &mi->content[MAX_BOUNDARIES] - 1;
+ return;
+ }
+ *(mi->content_top) = boundary;
+ boundary = NULL;
+ }
+ slurp_attr(line->buf, "charset=", &mi->charset);
+
+ if (boundary) {
+ strbuf_release(boundary);
+ free(boundary);
+ }
+}
+
+static void handle_content_transfer_encoding(struct mailinfo *mi,
+ const struct strbuf *line)
+{
+ if (strcasestr(line->buf, "base64"))
+ mi->transfer_encoding = TE_BASE64;
+ else if (strcasestr(line->buf, "quoted-printable"))
+ mi->transfer_encoding = TE_QP;
+ else
+ mi->transfer_encoding = TE_DONTCARE;
+}
+
+static int is_multipart_boundary(struct mailinfo *mi, const struct strbuf *line)
+{
+ struct strbuf *content_top = *(mi->content_top);
+
+ return ((content_top->len <= line->len) &&
+ !memcmp(line->buf, content_top->buf, content_top->len));
+}
+
+static void cleanup_subject(struct mailinfo *mi, struct strbuf *subject)
+{
+ size_t at = 0;
+
+ while (at < subject->len) {
+ char *pos;
+ size_t remove;
+
+ switch (subject->buf[at]) {
+ case 'r': case 'R':
+ if (subject->len <= at + 3)
+ break;
+ if ((subject->buf[at + 1] == 'e' ||
+ subject->buf[at + 1] == 'E') &&
+ subject->buf[at + 2] == ':') {
+ strbuf_remove(subject, at, 3);
+ continue;
+ }
+ at++;
+ break;
+ case ' ': case '\t': case ':':
+ strbuf_remove(subject, at, 1);
+ continue;
+ case '[':
+ pos = strchr(subject->buf + at, ']');
+ if (!pos)
+ break;
+ remove = pos - subject->buf + at + 1;
+ if (!mi->keep_non_patch_brackets_in_subject ||
+ (7 <= remove &&
+ memmem(subject->buf + at, remove, "PATCH", 5)))
+ strbuf_remove(subject, at, remove);
+ else {
+ at += remove;
+ /*
+ * If the input had a space after the ], keep
+ * it. We don't bother with finding the end of
+ * the space, since we later normalize it
+ * anyway.
+ */
+ if (isspace(subject->buf[at]))
+ at += 1;
+ }
+ continue;
+ }
+ break;
+ }
+ strbuf_trim(subject);
+}
+
+#define MAX_HDR_PARSED 10
+static const char *header[MAX_HDR_PARSED] = {
+ "From","Subject","Date",
+};
+
+static inline int cmp_header(const struct strbuf *line, const char *hdr)
+{
+ int len = strlen(hdr);
+ return !strncasecmp(line->buf, hdr, len) && line->len > len &&
+ line->buf[len] == ':' && isspace(line->buf[len + 1]);
+}
+
+static int is_format_patch_separator(const char *line, int len)
+{
+ static const char SAMPLE[] =
+ "From e6807f3efca28b30decfecb1732a56c7db1137ee Mon Sep 17 00:00:00 2001\n";
+ const char *cp;
+
+ if (len != strlen(SAMPLE))
+ return 0;
+ if (!skip_prefix(line, "From ", &cp))
+ return 0;
+ if (strspn(cp, "0123456789abcdef") != 40)
+ return 0;
+ cp += 40;
+ return !memcmp(SAMPLE + (cp - line), cp, strlen(SAMPLE) - (cp - line));
+}
+
+static struct strbuf *decode_q_segment(const struct strbuf *q_seg, int rfc2047)
+{
+ const char *in = q_seg->buf;
+ int c;
+ struct strbuf *out = xmalloc(sizeof(struct strbuf));
+ strbuf_init(out, q_seg->len);
+
+ while ((c = *in++) != 0) {
+ if (c == '=') {
+ int ch, d = *in;
+ if (d == '\n' || !d)
+ break; /* drop trailing newline */
+ ch = hex2chr(in);
+ if (ch >= 0) {
+ strbuf_addch(out, ch);
+ in += 2;
+ continue;
+ }
+ /* garbage -- fall through */
+ }
+ if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */
+ c = 0x20;
+ strbuf_addch(out, c);
+ }
+ return out;
+}
+
+static struct strbuf *decode_b_segment(const struct strbuf *b_seg)
+{
+ /* Decode in..ep, possibly in-place to ot */
+ int c, pos = 0, acc = 0;
+ const char *in = b_seg->buf;
+ struct strbuf *out = xmalloc(sizeof(struct strbuf));
+ strbuf_init(out, b_seg->len);
+
+ while ((c = *in++) != 0) {
+ if (c == '+')
+ c = 62;
+ else if (c == '/')
+ c = 63;
+ else if ('A' <= c && c <= 'Z')
+ c -= 'A';
+ else if ('a' <= c && c <= 'z')
+ c -= 'a' - 26;
+ else if ('0' <= c && c <= '9')
+ c -= '0' - 52;
+ else
+ continue; /* garbage */
+ switch (pos++) {
+ case 0:
+ acc = (c << 2);
+ break;
+ case 1:
+ strbuf_addch(out, (acc | (c >> 4)));
+ acc = (c & 15) << 4;
+ break;
+ case 2:
+ strbuf_addch(out, (acc | (c >> 2)));
+ acc = (c & 3) << 6;
+ break;
+ case 3:
+ strbuf_addch(out, (acc | c));
+ acc = pos = 0;
+ break;
+ }
+ }
+ return out;
+}
+
+static int convert_to_utf8(struct mailinfo *mi,
+ struct strbuf *line, const char *charset)
+{
+ char *out;
+
+ if (!mi->metainfo_charset || !charset || !*charset)
+ return 0;
+
+ if (same_encoding(mi->metainfo_charset, charset))
+ return 0;
+ out = reencode_string(line->buf, mi->metainfo_charset, charset);
+ if (!out) {
+ mi->input_error = -1;
+ return error("cannot convert from %s to %s",
+ charset, mi->metainfo_charset);
+ }
+ strbuf_attach(line, out, strlen(out), strlen(out));
+ return 0;
+}
+
+static void decode_header(struct mailinfo *mi, struct strbuf *it)
+{
+ char *in, *ep, *cp;
+ struct strbuf outbuf = STRBUF_INIT, *dec;
+ struct strbuf charset_q = STRBUF_INIT, piecebuf = STRBUF_INIT;
+ int found_error = 1; /* pessimism */
+
+ in = it->buf;
+ while (in - it->buf <= it->len && (ep = strstr(in, "=?")) != NULL) {
+ int encoding;
+ strbuf_reset(&charset_q);
+ strbuf_reset(&piecebuf);
+
+ if (in != ep) {
+ /*
+ * We are about to process an encoded-word
+ * that begins at ep, but there is something
+ * before the encoded word.
+ */
+ char *scan;
+ for (scan = in; scan < ep; scan++)
+ if (!isspace(*scan))
+ break;
+
+ if (scan != ep || in == it->buf) {
+ /*
+ * We should not lose that "something",
+ * unless we have just processed an
+ * encoded-word, and there is only LWS
+ * before the one we are about to process.
+ */
+ strbuf_add(&outbuf, in, ep - in);
+ }
+ }
+ /* E.g.
+ * ep : "=?iso-2022-jp?B?GyR...?= foo"
+ * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz"
+ */
+ ep += 2;
+
+ if (ep - it->buf >= it->len || !(cp = strchr(ep, '?')))
+ goto release_return;
+
+ if (cp + 3 - it->buf > it->len)
+ goto release_return;
+ strbuf_add(&charset_q, ep, cp - ep);
+
+ encoding = cp[1];
+ if (!encoding || cp[2] != '?')
+ goto release_return;
+ ep = strstr(cp + 3, "?=");
+ if (!ep)
+ goto release_return;
+ strbuf_add(&piecebuf, cp + 3, ep - cp - 3);
+ switch (tolower(encoding)) {
+ default:
+ goto release_return;
+ case 'b':
+ dec = decode_b_segment(&piecebuf);
+ break;
+ case 'q':
+ dec = decode_q_segment(&piecebuf, 1);
+ break;
+ }
+ if (convert_to_utf8(mi, dec, charset_q.buf))
+ goto release_return;
+
+ strbuf_addbuf(&outbuf, dec);
+ strbuf_release(dec);
+ free(dec);
+ in = ep + 2;
+ }
+ strbuf_addstr(&outbuf, in);
+ strbuf_reset(it);
+ strbuf_addbuf(it, &outbuf);
+ found_error = 0;
+release_return:
+ strbuf_release(&outbuf);
+ strbuf_release(&charset_q);
+ strbuf_release(&piecebuf);
+
+ if (found_error)
+ mi->input_error = -1;
+}
+
+static int check_header(struct mailinfo *mi,
+ const struct strbuf *line,
+ struct strbuf *hdr_data[], int overwrite)
+{
+ int i, ret = 0, len;
+ struct strbuf sb = STRBUF_INIT;
+
+ /* search for the interesting parts */
+ for (i = 0; header[i]; i++) {
+ int len = strlen(header[i]);
+ if ((!hdr_data[i] || overwrite) && cmp_header(line, header[i])) {
+ /* Unwrap inline B and Q encoding, and optionally
+ * normalize the meta information to utf8.
+ */
+ strbuf_add(&sb, line->buf + len + 2, line->len - len - 2);
+ decode_header(mi, &sb);
+ handle_header(&hdr_data[i], &sb);
+ ret = 1;
+ goto check_header_out;
+ }
+ }
+
+ /* Content stuff */
+ if (cmp_header(line, "Content-Type")) {
+ len = strlen("Content-Type: ");
+ strbuf_add(&sb, line->buf + len, line->len - len);
+ decode_header(mi, &sb);
+ strbuf_insert(&sb, 0, "Content-Type: ", len);
+ handle_content_type(mi, &sb);
+ ret = 1;
+ goto check_header_out;
+ }
+ if (cmp_header(line, "Content-Transfer-Encoding")) {
+ len = strlen("Content-Transfer-Encoding: ");
+ strbuf_add(&sb, line->buf + len, line->len - len);
+ decode_header(mi, &sb);
+ handle_content_transfer_encoding(mi, &sb);
+ ret = 1;
+ goto check_header_out;
+ }
+ if (cmp_header(line, "Message-Id")) {
+ len = strlen("Message-Id: ");
+ strbuf_add(&sb, line->buf + len, line->len - len);
+ decode_header(mi, &sb);
+ if (mi->add_message_id)
+ mi->message_id = strbuf_detach(&sb, NULL);
+ ret = 1;
+ goto check_header_out;
+ }
+
+check_header_out:
+ strbuf_release(&sb);
+ return ret;
+}
+
+/*
+ * Returns 1 if the given line or any line beginning with the given line is an
+ * in-body header (that is, check_header will succeed when passed
+ * mi->s_hdr_data).
+ */
+static int is_inbody_header(const struct mailinfo *mi,
+ const struct strbuf *line)
+{
+ int i;
+ for (i = 0; header[i]; i++)
+ if (!mi->s_hdr_data[i] && cmp_header(line, header[i]))
+ return 1;
+ return 0;
+}
+
+static void decode_transfer_encoding(struct mailinfo *mi, struct strbuf *line)
+{
+ struct strbuf *ret;
+
+ switch (mi->transfer_encoding) {
+ case TE_QP:
+ ret = decode_q_segment(line, 0);
+ break;
+ case TE_BASE64:
+ ret = decode_b_segment(line);
+ break;
+ case TE_DONTCARE:
+ default:
+ return;
+ }
+ strbuf_reset(line);
+ strbuf_addbuf(line, ret);
+ strbuf_release(ret);
+ free(ret);
+}
+
+static inline int patchbreak(const struct strbuf *line)
+{
+ size_t i;
+
+ /* Beginning of a "diff -" header? */
+ if (starts_with(line->buf, "diff -"))
+ return 1;
+
+ /* CVS "Index: " line? */
+ if (starts_with(line->buf, "Index: "))
+ return 1;
+
+ /*
+ * "--- <filename>" starts patches without headers
+ * "---<sp>*" is a manual separator
+ */
+ if (line->len < 4)
+ return 0;
+
+ if (starts_with(line->buf, "---")) {
+ /* space followed by a filename? */
+ if (line->buf[3] == ' ' && !isspace(line->buf[4]))
+ return 1;
+ /* Just whitespace? */
+ for (i = 3; i < line->len; i++) {
+ unsigned char c = line->buf[i];
+ if (c == '\n')
+ return 1;
+ if (!isspace(c))
+ break;
+ }
+ return 0;
+ }
+ return 0;
+}
+
+static int is_scissors_line(const char *line)
+{
+ const char *c;
+ int scissors = 0, gap = 0;
+ const char *first_nonblank = NULL, *last_nonblank = NULL;
+ int visible, perforation = 0, in_perforation = 0;
+
+ for (c = line; *c; c++) {
+ if (isspace(*c)) {
+ if (in_perforation) {
+ perforation++;
+ gap++;
+ }
+ continue;
+ }
+ last_nonblank = c;
+ if (first_nonblank == NULL)
+ first_nonblank = c;
+ if (*c == '-') {
+ in_perforation = 1;
+ perforation++;
+ continue;
+ }
+ if ((!memcmp(c, ">8", 2) || !memcmp(c, "8<", 2) ||
+ !memcmp(c, ">%", 2) || !memcmp(c, "%<", 2))) {
+ in_perforation = 1;
+ perforation += 2;
+ scissors += 2;
+ c++;
+ continue;
+ }
+ in_perforation = 0;
+ }
+
+ /*
+ * The mark must be at least 8 bytes long (e.g. "-- >8 --").
+ * Even though there can be arbitrary cruft on the same line
+ * (e.g. "cut here"), in order to avoid misidentification, the
+ * perforation must occupy more than a third of the visible
+ * width of the line, and dashes and scissors must occupy more
+ * than half of the perforation.
+ */
+
+ if (first_nonblank && last_nonblank)
+ visible = last_nonblank - first_nonblank + 1;
+ else
+ visible = 0;
+ return (scissors && 8 <= visible &&
+ visible < perforation * 3 &&
+ gap * 2 < perforation);
+}
+
+static void flush_inbody_header_accum(struct mailinfo *mi)
+{
+ if (!mi->inbody_header_accum.len)
+ return;
+ if (!check_header(mi, &mi->inbody_header_accum, mi->s_hdr_data, 0))
+ BUG("inbody_header_accum, if not empty, must always contain a valid in-body header");
+ strbuf_reset(&mi->inbody_header_accum);
+}
+
+static int check_inbody_header(struct mailinfo *mi, const struct strbuf *line)
+{
+ if (mi->inbody_header_accum.len &&
+ (line->buf[0] == ' ' || line->buf[0] == '\t')) {
+ if (mi->use_scissors && is_scissors_line(line->buf)) {
+ /*
+ * This is a scissors line; do not consider this line
+ * as a header continuation line.
+ */
+ flush_inbody_header_accum(mi);
+ return 0;
+ }
+ strbuf_strip_suffix(&mi->inbody_header_accum, "\n");
+ strbuf_addbuf(&mi->inbody_header_accum, line);
+ return 1;
+ }
+
+ flush_inbody_header_accum(mi);
+
+ if (starts_with(line->buf, ">From") && isspace(line->buf[5]))
+ return is_format_patch_separator(line->buf + 1, line->len - 1);
+ if (starts_with(line->buf, "[PATCH]") && isspace(line->buf[7])) {
+ int i;
+ for (i = 0; header[i]; i++)
+ if (!strcmp("Subject", header[i])) {
+ handle_header(&mi->s_hdr_data[i], line);
+ return 1;
+ }
+ return 0;
+ }
+ if (is_inbody_header(mi, line)) {
+ strbuf_addbuf(&mi->inbody_header_accum, line);
+ return 1;
+ }
+ return 0;
+}
+
+static int handle_commit_msg(struct mailinfo *mi, struct strbuf *line)
+{
+ assert(!mi->filter_stage);
+
+ if (mi->header_stage) {
+ if (!line->len || (line->len == 1 && line->buf[0] == '\n')) {
+ if (mi->inbody_header_accum.len) {
+ flush_inbody_header_accum(mi);
+ mi->header_stage = 0;
+ }
+ return 0;
+ }
+ }
+
+ if (mi->use_inbody_headers && mi->header_stage) {
+ mi->header_stage = check_inbody_header(mi, line);
+ if (mi->header_stage)
+ return 0;
+ } else
+ /* Only trim the first (blank) line of the commit message
+ * when ignoring in-body headers.
+ */
+ mi->header_stage = 0;
+
+ /* normalize the log message to UTF-8. */
+ if (convert_to_utf8(mi, line, mi->charset.buf))
+ return 0; /* mi->input_error already set */
+
+ if (mi->use_scissors && is_scissors_line(line->buf)) {
+ int i;
+
+ strbuf_setlen(&mi->log_message, 0);
+ mi->header_stage = 1;
+
+ /*
+ * We may have already read "secondary headers"; purge
+ * them to give ourselves a clean restart.
+ */
+ for (i = 0; header[i]; i++) {
+ if (mi->s_hdr_data[i])
+ strbuf_release(mi->s_hdr_data[i]);
+ mi->s_hdr_data[i] = NULL;
+ }
+ return 0;
+ }
+
+ if (patchbreak(line)) {
+ if (mi->message_id)
+ strbuf_addf(&mi->log_message,
+ "Message-Id: %s\n", mi->message_id);
+ return 1;
+ }
+
+ strbuf_addbuf(&mi->log_message, line);
+ return 0;
+}
+
+static void handle_patch(struct mailinfo *mi, const struct strbuf *line)
+{
+ fwrite(line->buf, 1, line->len, mi->patchfile);
+ mi->patch_lines++;
+}
+
+static void handle_filter(struct mailinfo *mi, struct strbuf *line)
+{
+ switch (mi->filter_stage) {
+ case 0:
+ if (!handle_commit_msg(mi, line))
+ break;
+ mi->filter_stage++;
+ /* fallthrough */
+ case 1:
+ handle_patch(mi, line);
+ break;
+ }
+}
+
+static int is_rfc2822_header(const struct strbuf *line)
+{
+ /*
+ * The section that defines the loosest possible
+ * field name is "3.6.8 Optional fields".
+ *
+ * optional-field = field-name ":" unstructured CRLF
+ * field-name = 1*ftext
+ * ftext = %d33-57 / %59-126
+ */
+ int ch;
+ char *cp = line->buf;
+
+ /* Count mbox From headers as headers */
+ if (starts_with(cp, "From ") || starts_with(cp, ">From "))
+ return 1;
+
+ while ((ch = *cp++)) {
+ if (ch == ':')
+ return 1;
+ if ((33 <= ch && ch <= 57) ||
+ (59 <= ch && ch <= 126))
+ continue;
+ break;
+ }
+ return 0;
+}
+
+static int read_one_header_line(struct strbuf *line, FILE *in)
+{
+ struct strbuf continuation = STRBUF_INIT;
+
+ /* Get the first part of the line. */
+ if (strbuf_getline_lf(line, in))
+ return 0;
+
+ /*
+ * Is it an empty line or not a valid rfc2822 header?
+ * If so, stop here, and return false ("not a header")
+ */
+ strbuf_rtrim(line);
+ if (!line->len || !is_rfc2822_header(line)) {
+ /* Re-add the newline */
+ strbuf_addch(line, '\n');
+ return 0;
+ }
+
+ /*
+ * Now we need to eat all the continuation lines..
+ * Yuck, 2822 header "folding"
+ */
+ for (;;) {
+ int peek;
+
+ peek = fgetc(in);
+ if (peek == EOF)
+ break;
+ ungetc(peek, in);
+ if (peek != ' ' && peek != '\t')
+ break;
+ if (strbuf_getline_lf(&continuation, in))
+ break;
+ continuation.buf[0] = ' ';
+ strbuf_rtrim(&continuation);
+ strbuf_addbuf(line, &continuation);
+ }
+ strbuf_release(&continuation);
+
+ return 1;
+}
+
+static int find_boundary(struct mailinfo *mi, struct strbuf *line)
+{
+ while (!strbuf_getline_lf(line, mi->input)) {
+ if (*(mi->content_top) && is_multipart_boundary(mi, line))
+ return 1;
+ }
+ return 0;
+}
+
+static int handle_boundary(struct mailinfo *mi, struct strbuf *line)
+{
+ struct strbuf newline = STRBUF_INIT;
+
+ strbuf_addch(&newline, '\n');
+again:
+ if (line->len >= (*(mi->content_top))->len + 2 &&
+ !memcmp(line->buf + (*(mi->content_top))->len, "--", 2)) {
+ /* we hit an end boundary */
+ /* pop the current boundary off the stack */
+ strbuf_release(*(mi->content_top));
+ FREE_AND_NULL(*(mi->content_top));
+
+ /* technically won't happen as is_multipart_boundary()
+ will fail first. But just in case..
+ */
+ if (--mi->content_top < mi->content) {
+ error("Detected mismatched boundaries, can't recover");
+ mi->input_error = -1;
+ mi->content_top = mi->content;
+ strbuf_release(&newline);
+ return 0;
+ }
+ handle_filter(mi, &newline);
+ strbuf_release(&newline);
+ if (mi->input_error)
+ return 0;
+
+ /* skip to the next boundary */
+ if (!find_boundary(mi, line))
+ return 0;
+ goto again;
+ }
+
+ /* set some defaults */
+ mi->transfer_encoding = TE_DONTCARE;
+ strbuf_reset(&mi->charset);
+
+ /* slurp in this section's info */
+ while (read_one_header_line(line, mi->input))
+ check_header(mi, line, mi->p_hdr_data, 0);
+
+ strbuf_release(&newline);
+ /* replenish line */
+ if (strbuf_getline_lf(line, mi->input))
+ return 0;
+ strbuf_addch(line, '\n');
+ return 1;
+}
+
+static void handle_body(struct mailinfo *mi, struct strbuf *line)
+{
+ struct strbuf prev = STRBUF_INIT;
+
+ /* Skip up to the first boundary */
+ if (*(mi->content_top)) {
+ if (!find_boundary(mi, line))
+ goto handle_body_out;
+ }
+
+ do {
+ /* process any boundary lines */
+ if (*(mi->content_top) && is_multipart_boundary(mi, line)) {
+ /* flush any leftover */
+ if (prev.len) {
+ handle_filter(mi, &prev);
+ strbuf_reset(&prev);
+ }
+ if (!handle_boundary(mi, line))
+ goto handle_body_out;
+ }
+
+ /* Unwrap transfer encoding */
+ decode_transfer_encoding(mi, line);
+
+ switch (mi->transfer_encoding) {
+ case TE_BASE64:
+ case TE_QP:
+ {
+ struct strbuf **lines, **it, *sb;
+
+ /* Prepend any previous partial lines */
+ strbuf_insert(line, 0, prev.buf, prev.len);
+ strbuf_reset(&prev);
+
+ /*
+ * This is a decoded line that may contain
+ * multiple new lines. Pass only one chunk
+ * at a time to handle_filter()
+ */
+ lines = strbuf_split(line, '\n');
+ for (it = lines; (sb = *it); it++) {
+ if (*(it + 1) == NULL) /* The last line */
+ if (sb->buf[sb->len - 1] != '\n') {
+ /* Partial line, save it for later. */
+ strbuf_addbuf(&prev, sb);
+ break;
+ }
+ handle_filter(mi, sb);
+ }
+ /*
+ * The partial chunk is saved in "prev" and will be
+ * appended by the next iteration of read_line_with_nul().
+ */
+ strbuf_list_free(lines);
+ break;
+ }
+ default:
+ handle_filter(mi, line);
+ }
+
+ if (mi->input_error)
+ break;
+ } while (!strbuf_getwholeline(line, mi->input, '\n'));
+
+ flush_inbody_header_accum(mi);
+
+handle_body_out:
+ strbuf_release(&prev);
+}
+
+static void output_header_lines(FILE *fout, const char *hdr, const struct strbuf *data)
+{
+ const char *sp = data->buf;
+ while (1) {
+ char *ep = strchr(sp, '\n');
+ int len;
+ if (!ep)
+ len = strlen(sp);
+ else
+ len = ep - sp;
+ fprintf(fout, "%s: %.*s\n", hdr, len, sp);
+ if (!ep)
+ break;
+ sp = ep + 1;
+ }
+}
+
+static void handle_info(struct mailinfo *mi)
+{
+ struct strbuf *hdr;
+ int i;
+
+ for (i = 0; header[i]; i++) {
+ /* only print inbody headers if we output a patch file */
+ if (mi->patch_lines && mi->s_hdr_data[i])
+ hdr = mi->s_hdr_data[i];
+ else if (mi->p_hdr_data[i])
+ hdr = mi->p_hdr_data[i];
+ else
+ continue;
+
+ if (!strcmp(header[i], "Subject")) {
+ if (!mi->keep_subject) {
+ cleanup_subject(mi, hdr);
+ cleanup_space(hdr);
+ }
+ output_header_lines(mi->output, "Subject", hdr);
+ } else if (!strcmp(header[i], "From")) {
+ cleanup_space(hdr);
+ handle_from(mi, hdr);
+ fprintf(mi->output, "Author: %s\n", mi->name.buf);
+ fprintf(mi->output, "Email: %s\n", mi->email.buf);
+ } else {
+ cleanup_space(hdr);
+ fprintf(mi->output, "%s: %s\n", header[i], hdr->buf);
+ }
+ }
+ fprintf(mi->output, "\n");
+}
+
+int mailinfo(struct mailinfo *mi, const char *msg, const char *patch)
+{
+ FILE *cmitmsg;
+ int peek;
+ struct strbuf line = STRBUF_INIT;
+
+ cmitmsg = fopen(msg, "w");
+ if (!cmitmsg) {
+ perror(msg);
+ return -1;
+ }
+ mi->patchfile = fopen(patch, "w");
+ if (!mi->patchfile) {
+ perror(patch);
+ fclose(cmitmsg);
+ return -1;
+ }
+
+ mi->p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*(mi->p_hdr_data)));
+ mi->s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*(mi->s_hdr_data)));
+
+ do {
+ peek = fgetc(mi->input);
+ if (peek == EOF) {
+ fclose(cmitmsg);
+ return error("empty patch: '%s'", patch);
+ }
+ } while (isspace(peek));
+ ungetc(peek, mi->input);
+
+ /* process the email header */
+ while (read_one_header_line(&line, mi->input))
+ check_header(mi, &line, mi->p_hdr_data, 1);
+
+ handle_body(mi, &line);
+ fwrite(mi->log_message.buf, 1, mi->log_message.len, cmitmsg);
+ fclose(cmitmsg);
+ fclose(mi->patchfile);
+
+ handle_info(mi);
+ strbuf_release(&line);
+ return mi->input_error;
+}
+
+static int git_mailinfo_config(const char *var, const char *value, void *mi_)
+{
+ struct mailinfo *mi = mi_;
+
+ if (!starts_with(var, "mailinfo."))
+ return git_default_config(var, value, NULL);
+ if (!strcmp(var, "mailinfo.scissors")) {
+ mi->use_scissors = git_config_bool(var, value);
+ return 0;
+ }
+ /* perhaps others here */
+ return 0;
+}
+
+void setup_mailinfo(struct mailinfo *mi)
+{
+ memset(mi, 0, sizeof(*mi));
+ strbuf_init(&mi->name, 0);
+ strbuf_init(&mi->email, 0);
+ strbuf_init(&mi->charset, 0);
+ strbuf_init(&mi->log_message, 0);
+ strbuf_init(&mi->inbody_header_accum, 0);
+ mi->header_stage = 1;
+ mi->use_inbody_headers = 1;
+ mi->content_top = mi->content;
+ git_config(git_mailinfo_config, mi);
+}
+
+void clear_mailinfo(struct mailinfo *mi)
+{
+ int i;
+
+ strbuf_release(&mi->name);
+ strbuf_release(&mi->email);
+ strbuf_release(&mi->charset);
+ strbuf_release(&mi->inbody_header_accum);
+ free(mi->message_id);
+
+ if (mi->p_hdr_data)
+ for (i = 0; mi->p_hdr_data[i]; i++)
+ strbuf_release(mi->p_hdr_data[i]);
+ free(mi->p_hdr_data);
+ if (mi->s_hdr_data)
+ for (i = 0; mi->s_hdr_data[i]; i++)
+ strbuf_release(mi->s_hdr_data[i]);
+ free(mi->s_hdr_data);
+
+ while (mi->content < mi->content_top) {
+ free(*(mi->content_top));
+ mi->content_top--;
+ }
+
+ strbuf_release(&mi->log_message);
+}
diff --git a/t/t4256/1/patch b/t/t4256/1/patch
new file mode 100644
index 0000000000..bd0d8b0251
--- /dev/null
+++ b/t/t4256/1/patch
@@ -0,0 +1,129 @@
+From: A <author@example.com>
+Subject: [PATCH] mailinfo: support format=flowed
+Message-ID: <aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa@example.com>
+Date: Sat, 25 Aug 2018 22:04:50 +0200
+User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:60.0) Gecko/20100101
+ Thunderbird/60.0
+MIME-Version: 1.0
+Content-Type: text/plain; charset=utf-8; format=flowed
+Content-Language: en-US
+Content-Transfer-Encoding: 7bit
+
+---
+ mailinfo.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
+ 1 file changed, 62 insertions(+), 2 deletions(-)
+
+diff --git a/mailinfo.c b/mailinfo.c
+index 3281a37d51..b395adbdf2 100644
+--- a/mailinfo.c
++++ b/mailinfo.c
+@@ -237,11 +237,22 @@ static int slurp_attr(const char *line, const char
+*name, struct strbuf *attr)
+ return 1;
+ }
+
++static int has_attr_value(const char *line, const char *name, const
+char *value)
++{
++ struct strbuf sb = STRBUF_INIT;
++ int rc = slurp_attr(line, name, &sb) && !strcasecmp(sb.buf, value);
++ strbuf_release(&sb);
++ return rc;
++}
++
+ static void handle_content_type(struct mailinfo *mi, struct strbuf *line)
+ {
+ struct strbuf *boundary = xmalloc(sizeof(struct strbuf));
+ strbuf_init(boundary, line->len);
+
++ mi->format_flowed = has_attr_value(line->buf, "format=", "flowed");
++ mi->delsp = has_attr_value(line->buf, "delsp=", "yes");
++
+ if (slurp_attr(line->buf, "boundary=", boundary)) {
+ strbuf_insert(boundary, 0, "--", 2);
+ if (++mi->content_top >= &mi->content[MAX_BOUNDARIES]) {
+@@ -964,6 +975,52 @@ static int handle_boundary(struct mailinfo *mi,
+struct strbuf *line)
+ return 1;
+ }
+
++static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,
++ struct strbuf *prev)
++{
++ size_t len = line->len;
++ const char *rest;
++
++ if (!mi->format_flowed) {
++ handle_filter(mi, line);
++ return;
++ }
++
++ if (line->buf[len - 1] == '\n') {
++ len--;
++ if (len && line->buf[len - 1] == '\r')
++ len--;
++ }
++
++ /* Keep signature separator as-is. */
++ if (skip_prefix(line->buf, "-- ", &rest) && rest - line->buf == len) {
++ if (prev->len) {
++ handle_filter(mi, prev);
++ strbuf_reset(prev);
++ }
++ handle_filter(mi, line);
++ return;
++ }
++
++ /* Unstuff space-stuffed line. */
++ if (len && line->buf[0] == ' ') {
++ strbuf_remove(line, 0, 1);
++ len--;
++ }
++
++ /* Save flowed line for later, but without the soft line break. */
++ if (len && line->buf[len - 1] == ' ') {
++ strbuf_add(prev, line->buf, len - !!mi->delsp);
++ return;
++ }
++
++ /* Prepend any previous partial lines */
++ strbuf_insert(line, 0, prev->buf, prev->len);
++ strbuf_reset(prev);
++
++ handle_filter(mi, line);
++}
++
+ static void handle_body(struct mailinfo *mi, struct strbuf *line)
+ {
+ struct strbuf prev = STRBUF_INIT;
+@@ -1012,7 +1069,7 @@ static void handle_body(struct mailinfo *mi,
+struct strbuf *line)
+ strbuf_addbuf(&prev, sb);
+ break;
+ }
+- handle_filter(mi, sb);
++ handle_filter_flowed(mi, sb, &prev);
+ }
+ /*
+ * The partial chunk is saved in "prev" and will be
+@@ -1022,13 +1079,16 @@ static void handle_body(struct mailinfo *mi,
+struct strbuf *line)
+ break;
+ }
+ default:
+- handle_filter(mi, line);
++ handle_filter_flowed(mi, line, &prev);
+ }
+
+ if (mi->input_error)
+ break;
+ } while (!strbuf_getwholeline(line, mi->input, '\n'));
+
++ if (prev.len)
++ handle_filter(mi, &prev);
++
+ flush_inbody_header_accum(mi);
+
+ handle_body_out:
+--
+2.18.0
diff --git a/t/t4300-merge-tree.sh b/t/t4300-merge-tree.sh
index 9015e47654..d87cc7d9ef 100755
--- a/t/t4300-merge-tree.sh
+++ b/t/t4300-merge-tree.sh
@@ -25,25 +25,19 @@ EXPECTED
'
test_expect_success 'file add !A, B' '
- cat >expected <<\EXPECTED &&
-EXPECTED
-
git reset --hard initial &&
test_commit "add-not-a-b" "ONE" "AAA" &&
git merge-tree initial add-not-a-b initial >actual &&
- test_cmp expected actual
+ test_must_be_empty actual
'
test_expect_success 'file add A, B (same)' '
- cat >expected <<\EXPECTED &&
-EXPECTED
-
git reset --hard initial &&
test_commit "add-a-b-same-A" "ONE" "AAA" &&
git reset --hard initial &&
test_commit "add-a-b-same-B" "ONE" "AAA" &&
git merge-tree initial add-a-b-same-A add-a-b-same-B >actual &&
- test_cmp expected actual
+ test_must_be_empty actual
'
test_expect_success 'file add A, B (different)' '
@@ -68,13 +62,10 @@ EXPECTED
'
test_expect_success 'file change A, !B' '
- cat >expected <<\EXPECTED &&
-EXPECTED
-
git reset --hard initial &&
test_commit "change-a-not-b" "initial-file" "BBB" &&
git merge-tree initial change-a-not-b initial >actual &&
- test_cmp expected actual
+ test_must_be_empty actual
'
test_expect_success 'file change !A, B' '
@@ -94,15 +85,12 @@ EXPECTED
'
test_expect_success 'file change A, B (same)' '
- cat >expected <<\EXPECTED &&
-EXPECTED
-
git reset --hard initial &&
test_commit "change-a-b-same-A" "initial-file" "AAA" &&
git reset --hard initial &&
test_commit "change-a-b-same-B" "initial-file" "AAA" &&
git merge-tree initial change-a-b-same-A change-a-b-same-B >actual &&
- test_cmp expected actual
+ test_must_be_empty actual
'
test_expect_success 'file change A, B (different)' '
@@ -175,16 +163,13 @@ AAA" &&
'
test_expect_success 'file remove A, !B' '
- cat >expected <<\EXPECTED &&
-EXPECTED
-
git reset --hard initial &&
test_commit "rm-a-not-b-base" "ONE" "AAA" &&
git rm ONE &&
git commit -m "rm-a-not-b" &&
git tag "rm-a-not-b" &&
git merge-tree rm-a-not-b-base rm-a-not-b rm-a-not-b-base >actual &&
- test_cmp expected actual
+ test_must_be_empty actual
'
test_expect_success 'file remove !A, B' '
@@ -206,16 +191,13 @@ EXPECTED
'
test_expect_success 'file remove A, B (same)' '
- cat >expected <<\EXPECTED &&
-EXPECTED
-
git reset --hard initial &&
test_commit "rm-a-b-base" "ONE" "AAA" &&
git rm ONE &&
git commit -m "rm-a-b" &&
git tag "rm-a-b" &&
git merge-tree rm-a-b-base rm-a-b rm-a-b >actual &&
- test_cmp expected actual
+ test_must_be_empty actual
'
test_expect_success 'file change A, remove B' '
@@ -260,13 +242,11 @@ EXPECTED
'
test_expect_success 'tree add A, B (same)' '
- cat >expect <<-\EOF &&
- EOF
git reset --hard initial &&
mkdir sub &&
test_commit "add sub/file" "sub/file" "file" add-tree-A &&
git merge-tree initial add-tree-A add-tree-A >actual &&
- test_cmp expect actual
+ test_must_be_empty actual
'
test_expect_success 'tree add A, B (different)' '
diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh
index f20f03c103..270da21ac3 100755
--- a/t/t5304-prune.sh
+++ b/t/t5304-prune.sh
@@ -112,8 +112,7 @@ test_expect_success 'prune: do not prune detached HEAD with no reflog' '
# (should be removed and disabled by previous test)
test_path_is_missing .git/logs &&
git prune -n >prune_actual &&
- : >prune_expected &&
- test_cmp prune_actual prune_expected
+ test_must_be_empty prune_actual
'
diff --git a/t/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh
index af38776054..1be3459c5b 100755
--- a/t/t5310-pack-bitmaps.sh
+++ b/t/t5310-pack-bitmaps.sh
@@ -335,7 +335,7 @@ test_expect_success 'truncated bitmap fails gracefully' '
git rev-list --use-bitmap-index --count --all >expect &&
bitmap=$(ls .git/objects/pack/*.bitmap) &&
test_when_finished "rm -f $bitmap" &&
- head -c 512 <$bitmap >$bitmap.tmp &&
+ test_copy_bytes 512 <$bitmap >$bitmap.tmp &&
mv -f $bitmap.tmp $bitmap &&
git rev-list --use-bitmap-index --count --all >actual 2>stderr &&
test_cmp expect actual &&
diff --git a/t/t5314-pack-cycle-detection.sh b/t/t5314-pack-cycle-detection.sh
index f31995d3d2..e525466de0 100755
--- a/t/t5314-pack-cycle-detection.sh
+++ b/t/t5314-pack-cycle-detection.sh
@@ -98,9 +98,8 @@ test_expect_success 'repack' '
# We first want to check that we do not have any internal errors,
# and also that we do not hit the last-ditch cycle-breaking code
# in write_object(), which will issue a warning to stderr.
- >expect &&
git repack -ad 2>stderr &&
- test_cmp expect stderr &&
+ test_must_be_empty stderr &&
# And then double-check that the resulting pack is usable (i.e.,
# we did not fail to notice any cycles). We know we are accessing
diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh
index 3c1ffad491..0c500f7ca2 100755
--- a/t/t5318-commit-graph.sh
+++ b/t/t5318-commit-graph.sh
@@ -134,7 +134,7 @@ test_expect_success 'Add one more commit' '
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
+ ls $objdir/pack| grep idx | grep -v -f existing-idx >new-idx
'
# Current graph structure:
diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh
new file mode 100755
index 0000000000..6f56b38674
--- /dev/null
+++ b/t/t5319-multi-pack-index.sh
@@ -0,0 +1,217 @@
+#!/bin/sh
+
+test_description='multi-pack-indexes'
+. ./test-lib.sh
+
+objdir=.git/objects
+
+midx_read_expect () {
+ NUM_PACKS=$1
+ NUM_OBJECTS=$2
+ NUM_CHUNKS=$3
+ OBJECT_DIR=$4
+ EXTRA_CHUNKS="$5"
+ {
+ cat <<-EOF &&
+ header: 4d494458 1 $NUM_CHUNKS $NUM_PACKS
+ chunks: pack-names oid-fanout oid-lookup object-offsets$EXTRA_CHUNKS
+ num_objects: $NUM_OBJECTS
+ packs:
+ EOF
+ if test $NUM_PACKS -ge 1
+ then
+ ls $OBJECT_DIR/pack/ | grep idx | sort
+ fi &&
+ printf "object-dir: $OBJECT_DIR\n"
+ } >expect &&
+ test-tool read-midx $OBJECT_DIR >actual &&
+ test_cmp expect actual
+}
+
+test_expect_success 'write midx with no packs' '
+ test_when_finished rm -f pack/multi-pack-index &&
+ git multi-pack-index --object-dir=. write &&
+ midx_read_expect 0 0 4 .
+'
+
+generate_objects () {
+ i=$1
+ iii=$(printf '%03i' $i)
+ {
+ test-tool genrandom "bar" 200 &&
+ test-tool genrandom "baz $iii" 50
+ } >wide_delta_$iii &&
+ {
+ test-tool genrandom "foo"$i 100 &&
+ test-tool genrandom "foo"$(( $i + 1 )) 100 &&
+ test-tool genrandom "foo"$(( $i + 2 )) 100
+ } >deep_delta_$iii &&
+ {
+ echo $iii &&
+ test-tool genrandom "$iii" 8192
+ } >file_$iii &&
+ git update-index --add file_$iii deep_delta_$iii wide_delta_$iii
+}
+
+commit_and_list_objects () {
+ {
+ echo 101 &&
+ test-tool genrandom 100 8192;
+ } >file_101 &&
+ git update-index --add file_101 &&
+ tree=$(git write-tree) &&
+ commit=$(git commit-tree $tree -p HEAD</dev/null) &&
+ {
+ echo $tree &&
+ git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\) .*/\\1/"
+ } >obj-list &&
+ git reset --hard $commit
+}
+
+test_expect_success 'create objects' '
+ test_commit initial &&
+ for i in $(test_seq 1 5)
+ do
+ generate_objects $i
+ done &&
+ commit_and_list_objects
+'
+
+test_expect_success 'write midx with one v1 pack' '
+ pack=$(git pack-objects --index-version=1 $objdir/pack/test <obj-list) &&
+ test_when_finished rm $objdir/pack/test-$pack.pack \
+ $objdir/pack/test-$pack.idx $objdir/pack/multi-pack-index &&
+ git multi-pack-index --object-dir=$objdir write &&
+ midx_read_expect 1 18 4 $objdir
+'
+
+midx_git_two_modes () {
+ if [ "$2" = "sorted" ]
+ then
+ git -c core.multiPackIndex=false $1 | sort >expect &&
+ git -c core.multiPackIndex=true $1 | sort >actual
+ else
+ git -c core.multiPackIndex=false $1 >expect &&
+ git -c core.multiPackIndex=true $1 >actual
+ fi &&
+ test_cmp expect actual
+}
+
+compare_results_with_midx () {
+ MSG=$1
+ test_expect_success "check normal git operations: $MSG" '
+ midx_git_two_modes "rev-list --objects --all" &&
+ midx_git_two_modes "log --raw" &&
+ midx_git_two_modes "count-objects --verbose" &&
+ midx_git_two_modes "cat-file --batch-all-objects --buffer --batch-check" &&
+ midx_git_two_modes "cat-file --batch-all-objects --buffer --batch-check --unsorted" sorted
+ '
+}
+
+test_expect_success 'write midx with one v2 pack' '
+ git pack-objects --index-version=2,0x40 $objdir/pack/test <obj-list &&
+ git multi-pack-index --object-dir=$objdir write &&
+ midx_read_expect 1 18 4 $objdir
+'
+
+compare_results_with_midx "one v2 pack"
+
+test_expect_success 'add more objects' '
+ for i in $(test_seq 6 10)
+ do
+ generate_objects $i
+ done &&
+ commit_and_list_objects
+'
+
+test_expect_success 'write midx with two packs' '
+ git pack-objects --index-version=1 $objdir/pack/test-2 <obj-list &&
+ git multi-pack-index --object-dir=$objdir write &&
+ midx_read_expect 2 34 4 $objdir
+'
+
+compare_results_with_midx "two packs"
+
+test_expect_success 'add more packs' '
+ for j in $(test_seq 11 20)
+ do
+ generate_objects $j &&
+ commit_and_list_objects &&
+ git pack-objects --index-version=2 $objdir/pack/test-pack <obj-list
+ done
+'
+
+compare_results_with_midx "mixed mode (two packs + extra)"
+
+test_expect_success 'write midx with twelve packs' '
+ git multi-pack-index --object-dir=$objdir write &&
+ midx_read_expect 12 74 4 $objdir
+'
+
+compare_results_with_midx "twelve packs"
+
+test_expect_success 'repack removes multi-pack-index' '
+ test_path_is_file $objdir/pack/multi-pack-index &&
+ git repack -adf &&
+ test_path_is_missing $objdir/pack/multi-pack-index
+'
+
+compare_results_with_midx "after repack"
+
+test_expect_success 'multi-pack-index and pack-bitmap' '
+ git -c repack.writeBitmaps=true repack -ad &&
+ git multi-pack-index write &&
+ git rev-list --test-bitmap HEAD
+'
+
+test_expect_success 'multi-pack-index and alternates' '
+ git init --bare alt.git &&
+ echo $(pwd)/alt.git/objects >.git/objects/info/alternates &&
+ echo content1 >file1 &&
+ altblob=$(GIT_DIR=alt.git git hash-object -w file1) &&
+ git cat-file blob $altblob &&
+ git rev-list --all
+'
+
+compare_results_with_midx "with alternate (local midx)"
+
+test_expect_success 'multi-pack-index in an alternate' '
+ mv .git/objects/pack/* alt.git/objects/pack &&
+ test_commit add_local_objects &&
+ git repack --local &&
+ git multi-pack-index write &&
+ midx_read_expect 1 3 4 $objdir &&
+ git reset --hard HEAD~1 &&
+ rm -f .git/objects/pack/*
+'
+
+compare_results_with_midx "with alternate (remote midx)"
+
+
+# usage: corrupt_data <file> <pos> [<data>]
+corrupt_data () {
+ file=$1
+ pos=$2
+ data="${3:-\0}"
+ printf "$data" | dd of="$file" bs=1 seek="$pos" conv=notrunc
+}
+
+# Force 64-bit offsets by manipulating the idx file.
+# This makes the IDX file _incorrect_ so be careful to clean up after!
+test_expect_success 'force some 64-bit offsets with pack-objects' '
+ mkdir objects64 &&
+ mkdir objects64/pack &&
+ for i in $(test_seq 1 11)
+ do
+ generate_objects 11
+ done &&
+ commit_and_list_objects &&
+ pack64=$(git pack-objects --index-version=2,0x40 objects64/pack/test-64 <obj-list) &&
+ idx64=objects64/pack/test-64-$pack64.idx &&
+ chmod u+w $idx64 &&
+ corrupt_data $idx64 2999 "\02" &&
+ midx64=$(git multi-pack-index --object-dir=objects64 write) &&
+ midx_read_expect 1 63 5 objects64 " large-offsets"
+'
+
+test_done
diff --git a/t/t5320-delta-islands.sh b/t/t5320-delta-islands.sh
new file mode 100755
index 0000000000..fea92a5777
--- /dev/null
+++ b/t/t5320-delta-islands.sh
@@ -0,0 +1,143 @@
+#!/bin/sh
+
+test_description='exercise delta islands'
+. ./test-lib.sh
+
+# returns true iff $1 is a delta based on $2
+is_delta_base () {
+ delta_base=$(echo "$1" | git cat-file --batch-check='%(deltabase)') &&
+ echo >&2 "$1 has base $delta_base" &&
+ test "$delta_base" = "$2"
+}
+
+# generate a commit on branch $1 with a single file, "file", whose
+# content is mostly based on the seed $2, but with a unique bit
+# of content $3 appended. This should allow us to see whether
+# blobs of different refs delta against each other.
+commit() {
+ blob=$({ test-tool genrandom "$2" 10240 && echo "$3"; } |
+ git hash-object -w --stdin) &&
+ tree=$(printf '100644 blob %s\tfile\n' "$blob" | git mktree) &&
+ commit=$(echo "$2-$3" | git commit-tree "$tree" ${4:+-p "$4"}) &&
+ git update-ref "refs/heads/$1" "$commit" &&
+ eval "$1"'=$(git rev-parse $1:file)' &&
+ eval "echo >&2 $1=\$$1"
+}
+
+test_expect_success 'setup commits' '
+ commit one seed 1 &&
+ commit two seed 12
+'
+
+# Note: This is heavily dependent on the "prefer larger objects as base"
+# heuristic.
+test_expect_success 'vanilla repack deltas one against two' '
+ git repack -adf &&
+ is_delta_base $one $two
+'
+
+test_expect_success 'island repack with no island definition is vanilla' '
+ git repack -adfi &&
+ is_delta_base $one $two
+'
+
+test_expect_success 'island repack with no matches is vanilla' '
+ git -c "pack.island=refs/foo" repack -adfi &&
+ is_delta_base $one $two
+'
+
+test_expect_success 'separate islands disallows delta' '
+ git -c "pack.island=refs/heads/(.*)" repack -adfi &&
+ ! is_delta_base $one $two &&
+ ! is_delta_base $two $one
+'
+
+test_expect_success 'same island allows delta' '
+ git -c "pack.island=refs/heads" repack -adfi &&
+ is_delta_base $one $two
+'
+
+test_expect_success 'coalesce same-named islands' '
+ git \
+ -c "pack.island=refs/(.*)/one" \
+ -c "pack.island=refs/(.*)/two" \
+ repack -adfi &&
+ is_delta_base $one $two
+'
+
+test_expect_success 'island restrictions drop reused deltas' '
+ git repack -adfi &&
+ is_delta_base $one $two &&
+ git -c "pack.island=refs/heads/(.*)" repack -adi &&
+ ! is_delta_base $one $two &&
+ ! is_delta_base $two $one
+'
+
+test_expect_success 'island regexes are left-anchored' '
+ git -c "pack.island=heads/(.*)" repack -adfi &&
+ is_delta_base $one $two
+'
+
+test_expect_success 'island regexes follow last-one-wins scheme' '
+ git \
+ -c "pack.island=refs/heads/(.*)" \
+ -c "pack.island=refs/heads/" \
+ repack -adfi &&
+ is_delta_base $one $two
+'
+
+test_expect_success 'setup shared history' '
+ commit root shared root &&
+ commit one shared 1 root &&
+ commit two shared 12-long root
+'
+
+# We know that $two will be preferred as a base from $one,
+# because we can transform it with a pure deletion.
+#
+# We also expect $root as a delta against $two by the "longest is base" rule.
+test_expect_success 'vanilla delta goes between branches' '
+ git repack -adf &&
+ is_delta_base $one $two &&
+ is_delta_base $root $two
+'
+
+# Here we should allow $one to base itself on $root; even though
+# they are in different islands, the objects in $root are in a superset
+# of islands compared to those in $one.
+#
+# Similarly, $two can delta against $root by our rules. And unlike $one,
+# in which we are just allowing it, the island rules actually put $root
+# as a possible base for $two, which it would not otherwise be (due to the size
+# sorting).
+test_expect_success 'deltas allowed against superset islands' '
+ git -c "pack.island=refs/heads/(.*)" repack -adfi &&
+ is_delta_base $one $root &&
+ is_delta_base $two $root
+'
+
+# We are going to test the packfile order here, so we again have to make some
+# assumptions. We assume that "$root", as part of our core "one", must come
+# before "$two". This should be guaranteed by the island code. However, for
+# this test to fail without islands, we are also assuming that it would not
+# otherwise do so. This is true by the current write order, which will put
+# commits (and their contents) before their parents.
+test_expect_success 'island core places core objects first' '
+ cat >expect <<-EOF &&
+ $root
+ $two
+ EOF
+ git -c "pack.island=refs/heads/(.*)" \
+ -c "pack.islandcore=one" \
+ repack -adfi &&
+ git verify-pack -v .git/objects/pack/*.pack |
+ cut -d" " -f1 |
+ egrep "$root|$two" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'unmatched island core is not fatal' '
+ git -c "pack.islandcore=one" repack -adfi
+'
+
+test_done
diff --git a/t/t5401-update-hooks.sh b/t/t5401-update-hooks.sh
index b5f886a0e2..956d69f5b1 100755
--- a/t/t5401-update-hooks.sh
+++ b/t/t5401-update-hooks.sh
@@ -104,17 +104,17 @@ test_expect_success 'post-update hook arguments' '
'
test_expect_success 'all hook stdin is /dev/null' '
- ! test -s victim.git/update.stdin &&
- ! test -s victim.git/post-update.stdin
+ test_must_be_empty victim.git/update.stdin &&
+ test_must_be_empty victim.git/post-update.stdin
'
test_expect_success 'all *-receive hook args are empty' '
- ! test -s victim.git/pre-receive.args &&
- ! test -s victim.git/post-receive.args
+ test_must_be_empty victim.git/pre-receive.args &&
+ test_must_be_empty victim.git/post-receive.args
'
test_expect_success 'send-pack produced no output' '
- ! test -s send.out
+ test_must_be_empty send.out
'
cat <<EOF >expect
diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh
index 8f945235e3..1b5a4a6d38 100755
--- a/t/t5500-fetch-pack.sh
+++ b/t/t5500-fetch-pack.sh
@@ -161,7 +161,7 @@ test_expect_success 'clone shallow object count' '
test_expect_success 'clone shallow object count (part 2)' '
sed -e "/^in-pack:/d" -e "/^packs:/d" -e "/^size-pack:/d" \
-e "/: 0$/d" count.shallow > count_output &&
- ! test -s count_output
+ test_must_be_empty count_output
'
test_expect_success 'fsck in shallow repo' '
diff --git a/t/t5509-fetch-push-namespaces.sh b/t/t5509-fetch-push-namespaces.sh
index 75c570adca..c88df78c0b 100755
--- a/t/t5509-fetch-push-namespaces.sh
+++ b/t/t5509-fetch-push-namespaces.sh
@@ -44,7 +44,7 @@ test_expect_success 'pushing into a repository using a ref namespace' '
test_cmp expected actual &&
# Try a namespace with no content
git ls-remote "ext::git --namespace=garbage %s ../pushee" >actual &&
- test_cmp /dev/null actual &&
+ test_must_be_empty actual &&
git ls-remote pushee-unnamespaced >actual &&
sed -e "s|refs/|refs/namespaces/namespace/refs/|" expected >expected.unnamespaced &&
test_cmp expected.unnamespaced actual
diff --git a/t/t5523-push-upstream.sh b/t/t5523-push-upstream.sh
index d6981ba304..c0df81a014 100755
--- a/t/t5523-push-upstream.sh
+++ b/t/t5523-push-upstream.sh
@@ -113,7 +113,7 @@ test_expect_success TTY 'quiet push' '
ensure_fresh_upstream &&
test_terminal git push --quiet --no-progress upstream master 2>&1 | tee output &&
- test_cmp /dev/null output
+ test_must_be_empty output
'
test_done
diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh
index 0f730d7781..6c2f9b2ba2 100755
--- a/t/t5526-fetch-submodules.sh
+++ b/t/t5526-fetch-submodules.sh
@@ -98,8 +98,8 @@ test_expect_success "fetch alone only fetches superproject" '
cd downstream &&
git fetch >../actual.out 2>../actual.err
) &&
- ! test -s actual.out &&
- ! test -s actual.err
+ test_must_be_empty actual.out &&
+ test_must_be_empty actual.err
'
test_expect_success "fetch --no-recurse-submodules only fetches superproject" '
@@ -107,8 +107,8 @@ test_expect_success "fetch --no-recurse-submodules only fetches superproject" '
cd downstream &&
git fetch --no-recurse-submodules >../actual.out 2>../actual.err
) &&
- ! test -s actual.out &&
- ! test -s actual.err
+ test_must_be_empty actual.out &&
+ test_must_be_empty actual.err
'
test_expect_success "using fetchRecurseSubmodules=true in .gitmodules recurses into submodules" '
@@ -127,8 +127,8 @@ test_expect_success "--no-recurse-submodules overrides .gitmodules config" '
cd downstream &&
git fetch --no-recurse-submodules >../actual.out 2>../actual.err
) &&
- ! test -s actual.out &&
- ! test -s actual.err
+ test_must_be_empty actual.out &&
+ test_must_be_empty actual.err
'
test_expect_success "using fetchRecurseSubmodules=false in .git/config overrides setting in .gitmodules" '
@@ -137,8 +137,8 @@ test_expect_success "using fetchRecurseSubmodules=false in .git/config overrides
git config submodule.submodule.fetchRecurseSubmodules false &&
git fetch >../actual.out 2>../actual.err
) &&
- ! test -s actual.out &&
- ! test -s actual.err
+ test_must_be_empty actual.out &&
+ test_must_be_empty actual.err
'
test_expect_success "--recurse-submodules overrides fetchRecurseSubmodules setting from .git/config" '
@@ -157,8 +157,8 @@ test_expect_success "--quiet propagates to submodules" '
cd downstream &&
git fetch --recurse-submodules --quiet >../actual.out 2>../actual.err
) &&
- ! test -s actual.out &&
- ! test -s actual.err
+ test_must_be_empty actual.out &&
+ test_must_be_empty actual.err
'
test_expect_success "--quiet propagates to parallel submodules" '
@@ -166,8 +166,8 @@ test_expect_success "--quiet propagates to parallel submodules" '
cd downstream &&
git fetch --recurse-submodules -j 2 --quiet >../actual.out 2>../actual.err
) &&
- ! test -s actual.out &&
- ! test -s actual.err
+ test_must_be_empty actual.out &&
+ test_must_be_empty actual.err
'
test_expect_success "--dry-run propagates to submodules" '
@@ -221,8 +221,8 @@ test_expect_success "--no-recurse-submodules overrides config setting" '
git config fetch.recurseSubmodules true &&
git fetch --no-recurse-submodules >../actual.out 2>../actual.err
) &&
- ! test -s actual.out &&
- ! test -s actual.err
+ test_must_be_empty actual.out &&
+ test_must_be_empty actual.err
'
test_expect_success "Recursion doesn't happen when no new commits are fetched in the superproject" '
@@ -235,8 +235,8 @@ test_expect_success "Recursion doesn't happen when no new commits are fetched in
git config --unset fetch.recurseSubmodules &&
git fetch >../actual.out 2>../actual.err
) &&
- ! test -s actual.out &&
- ! test -s actual.err
+ test_must_be_empty actual.out &&
+ test_must_be_empty actual.err
'
test_expect_success "Recursion stops when no new submodule commits are fetched" '
@@ -268,7 +268,7 @@ test_expect_success "Recursion doesn't happen when new superproject commits don'
cd downstream &&
git fetch >../actual.out 2>../actual.err
) &&
- ! test -s actual.out &&
+ test_must_be_empty actual.out &&
test_i18ncmp expect.err.file actual.err
'
@@ -357,8 +357,8 @@ test_expect_success "'--recurse-submodules=on-demand' doesn't recurse when no ne
git fetch --recurse-submodules=on-demand >../actual.out 2>../actual.err &&
git config --unset fetch.recurseSubmodules
) &&
- ! test -s actual.out &&
- ! test -s actual.err
+ test_must_be_empty actual.out &&
+ test_must_be_empty actual.err
'
test_expect_success "'--recurse-submodules=on-demand' recurses as deep as necessary (and ignores config)" '
@@ -402,7 +402,7 @@ test_expect_success "'--recurse-submodules=on-demand' stops when no new submodul
cd downstream &&
git fetch --recurse-submodules=on-demand >../actual.out 2>../actual.err
) &&
- ! test -s actual.out &&
+ test_must_be_empty actual.out &&
test_i18ncmp expect.err.file actual.err
'
@@ -477,7 +477,7 @@ test_expect_success "don't fetch submodule when newly recorded commits are alrea
cd downstream &&
git fetch >../actual.out 2>../actual.err
) &&
- ! test -s actual.out &&
+ test_must_be_empty actual.out &&
test_i18ncmp expect.err actual.err &&
(
cd submodule &&
@@ -495,7 +495,6 @@ test_expect_success "'fetch.recurseSubmodules=on-demand' works also without .git
git add submodule &&
git rm .gitmodules &&
git commit -m "new submodule without .gitmodules" &&
- printf "" >expect.out &&
head2=$(git rev-parse --short HEAD) &&
echo "From $pwd/." >expect.err.2 &&
echo " $head1..$head2 master -> origin/master" >>expect.err.2 &&
@@ -514,7 +513,7 @@ test_expect_success "'fetch.recurseSubmodules=on-demand' works also without .git
git config --unset fetch.recurseSubmodules &&
git reset --hard
) &&
- test_i18ncmp expect.out actual.out &&
+ test_must_be_empty actual.out &&
test_i18ncmp expect.err.2 actual.err &&
git checkout HEAD^ -- .gitmodules &&
git add .gitmodules &&
diff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh
index a0fc4005e0..5475afc052 100755
--- a/t/t5541-http-push-smart.sh
+++ b/t/t5541-http-push-smart.sh
@@ -210,7 +210,7 @@ test_expect_success TTY 'push --quiet silences status and progress' '
cd "$ROOT_PATH"/test_repo_clone &&
test_commit quiet &&
test_terminal git push --quiet >output 2>&1 &&
- test_cmp /dev/null output
+ test_must_be_empty output
'
test_expect_success TTY 'push --no-progress silences progress but not status' '
diff --git a/t/t5552-skipping-fetch-negotiator.sh b/t/t5552-skipping-fetch-negotiator.sh
index 5ad5bece55..30857b84a8 100755
--- a/t/t5552-skipping-fetch-negotiator.sh
+++ b/t/t5552-skipping-fetch-negotiator.sh
@@ -46,7 +46,7 @@ test_expect_success 'commits with no parents are sent regardless of skip distanc
test_commit -C server to_fetch &&
git init client &&
- for i in $(seq 7)
+ for i in $(test_seq 7)
do
test_commit -C client c$i
done &&
@@ -89,7 +89,7 @@ test_expect_success 'when two skips collide, favor the larger one' '
test_commit -C server to_fetch &&
git init client &&
- for i in $(seq 11)
+ for i in $(test_seq 11)
do
test_commit -C client c$i
done &&
@@ -168,14 +168,14 @@ test_expect_success 'do not send "have" with ancestors of commits that server AC
test_commit -C server to_fetch &&
git init client &&
- for i in $(seq 8)
+ for i in $(test_seq 8)
do
git -C client checkout --orphan b$i &&
test_commit -C client b$i.c0
done &&
- for j in $(seq 19)
+ for j in $(test_seq 19)
do
- for i in $(seq 8)
+ for i in $(test_seq 8)
do
git -C client checkout b$i &&
test_commit -C client b$i.c$j
@@ -205,7 +205,7 @@ test_expect_success 'do not send "have" with ancestors of commits that server AC
# fetch-pack should thus not send any more commits in the b1 branch, but
# should still send the others (in this test, just check b2).
- for i in $(seq 0 8)
+ for i in $(test_seq 0 8)
do
have_not_sent b1.c$i
done &&
diff --git a/t/t5562-http-backend-content-length.sh b/t/t5562-http-backend-content-length.sh
index 43570ce120..b24d8b05a4 100755
--- a/t/t5562-http-backend-content-length.sh
+++ b/t/t5562-http-backend-content-length.sh
@@ -153,4 +153,15 @@ test_expect_success 'CONTENT_LENGTH overflow ssite_t' '
grep "fatal:.*CONTENT_LENGTH" err
'
+test_expect_success 'empty CONTENT_LENGTH' '
+ env \
+ QUERY_STRING="service=git-receive-pack" \
+ PATH_TRANSLATED="$PWD"/.git/info/refs \
+ GIT_HTTP_EXPORT_ALL=TRUE \
+ REQUEST_METHOD=GET \
+ CONTENT_LENGTH="" \
+ git http-backend <empty_body >act.out 2>act.err &&
+ verify_http_result "200 OK"
+'
+
test_done
diff --git a/t/t5570-git-daemon.sh b/t/t5570-git-daemon.sh
index a571f22bfd..7466aad111 100755
--- a/t/t5570-git-daemon.sh
+++ b/t/t5570-git-daemon.sh
@@ -51,7 +51,7 @@ test_expect_success 'no-op fetch -v stderr is as expected' '
test_expect_success 'no-op fetch without "-v" is quiet' '
(cd clone && git fetch 2>../stderr) &&
- ! test -s stderr
+ test_must_be_empty stderr
'
test_expect_success 'remote detects correct HEAD' '
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index ddaa96ac4f..f1a49e94f5 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -624,10 +624,16 @@ test_expect_success 'clone on case-insensitive fs' '
git hash-object -w -t tree --stdin) &&
c=$(git commit-tree -m bogus $t) &&
git update-ref refs/heads/bogus $c &&
- git clone -b bogus . bogus
+ git clone -b bogus . bogus 2>warning
)
'
+test_expect_success !MINGW,!CYGWIN,CASE_INSENSITIVE_FS 'colliding file detection' '
+ grep X icasefs/warning &&
+ grep x icasefs/warning &&
+ test_i18ngrep "the following paths have collided" icasefs/warning
+'
+
partial_clone () {
SERVER="$1" &&
URL="$2" &&
diff --git a/t/t5703-upload-pack-ref-in-want.sh b/t/t5703-upload-pack-ref-in-want.sh
index a73c55a47e..d1ccc22331 100755
--- a/t/t5703-upload-pack-ref-in-want.sh
+++ b/t/t5703-upload-pack-ref-in-want.sh
@@ -176,7 +176,7 @@ test_expect_success 'setup repos for change-while-negotiating test' '
git clone "http://127.0.0.1:$LIB_HTTPD_PORT/smart/repo" "$LOCAL_PRISTINE" &&
cd "$LOCAL_PRISTINE" &&
git checkout -b side &&
- for i in $(seq 1 33); do test_commit s$i; done &&
+ for i in $(test_seq 1 33); do test_commit s$i; done &&
# Add novel commits to upstream
git checkout master &&
@@ -289,7 +289,7 @@ test_expect_success 'setup repos for fetching with ref-in-want tests' '
git clone "file://$REPO" "$LOCAL_PRISTINE" &&
cd "$LOCAL_PRISTINE" &&
git checkout -b side &&
- for i in $(seq 1 33); do test_commit s$i; done &&
+ for i in $(test_seq 1 33); do test_commit s$i; done &&
# Add novel commits to upstream
git checkout master &&
diff --git a/t/t6018-rev-list-glob.sh b/t/t6018-rev-list-glob.sh
index 02936c2f24..db8a7834d8 100755
--- a/t/t6018-rev-list-glob.sh
+++ b/t/t6018-rev-list-glob.sh
@@ -255,8 +255,8 @@ test_expect_success 'rev-list accumulates multiple --exclude' '
compare rev-list "--exclude=refs/remotes/* --exclude=refs/tags/* --all" --branches
'
-test_expect_failure 'rev-list should succeed with empty output on empty stdin' '
- git rev-list --stdin <expect >actual &&
+test_expect_success 'rev-list should succeed with empty output on empty stdin' '
+ git rev-list --stdin </dev/null >actual &&
test_must_be_empty actual
'
diff --git a/t/t6112-rev-list-filters-objects.sh b/t/t6112-rev-list-filters-objects.sh
index 0a37dd5f97..d4ff0b3bef 100755
--- a/t/t6112-rev-list-filters-objects.sh
+++ b/t/t6112-rev-list-filters-objects.sh
@@ -113,12 +113,11 @@ test_expect_success 'verify blob:limit=1k' '
'
test_expect_success 'verify blob:limit=1m' '
- cat </dev/null >expected &&
git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=1m \
| awk -f print_1.awk \
| sed "s/~//" \
| sort >observed &&
- test_cmp observed expected
+ test_must_be_empty observed
'
# Test sparse:path=<path> filter.
diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh
index 84dd1cb690..d639d94696 100755
--- a/t/t6120-describe.sh
+++ b/t/t6120-describe.sh
@@ -121,10 +121,9 @@ test_expect_success 'describe --contains defaults to HEAD without commit-ish' '
test_cmp expect actual
'
-: >err.expect
check_describe tags/A --all A^0
test_expect_success 'no warning was displayed for A' '
- test_cmp err.expect err.actual
+ test_must_be_empty err.actual
'
test_expect_success 'rename tag A to Q locally' '
diff --git a/t/t6130-pathspec-noglob.sh b/t/t6130-pathspec-noglob.sh
index 658353277e..37760233a5 100755
--- a/t/t6130-pathspec-noglob.sh
+++ b/t/t6130-pathspec-noglob.sh
@@ -97,9 +97,8 @@ test_expect_success 'no-glob option matches literally (bracket)' '
'
test_expect_success 'no-glob option disables :(literal)' '
- : >expect &&
git --literal-pathspecs log --format=%s -- ":(literal)foo" >actual &&
- test_cmp expect actual
+ test_must_be_empty actual
'
test_expect_success 'no-glob environment variable works' '
@@ -130,9 +129,8 @@ test_expect_success '**/ works with :(glob)' '
'
test_expect_success '**/ does not work with --noglob-pathspecs' '
- : >expect &&
git --noglob-pathspecs log --format=%s -- "**/bar" >actual &&
- test_cmp expect actual
+ test_must_be_empty actual
'
test_expect_success '**/ works with :(glob) and --noglob-pathspecs' '
@@ -154,9 +152,8 @@ test_expect_success '**/ works with --glob-pathspecs' '
'
test_expect_success '**/ does not work with :(literal) and --glob-pathspecs' '
- : >expect &&
git --glob-pathspecs log --format=%s -- ":(literal)**/bar" >actual &&
- test_cmp expect actual
+ test_must_be_empty actual
'
test_done
diff --git a/t/t6200-fmt-merge-msg.sh b/t/t6200-fmt-merge-msg.sh
index a54a52aaa4..93f23cfa82 100755
--- a/t/t6200-fmt-merge-msg.sh
+++ b/t/t6200-fmt-merge-msg.sh
@@ -366,8 +366,6 @@ test_expect_success 'merge-msg with nothing to merge' '
test_unconfig merge.log &&
test_config merge.summary yes &&
- >empty &&
-
(
cd remote &&
git checkout -b unrelated &&
@@ -376,7 +374,7 @@ test_expect_success 'merge-msg with nothing to merge' '
git fmt-merge-msg <.git/FETCH_HEAD >../actual
) &&
- test_cmp empty actual
+ test_must_be_empty actual
'
test_expect_success 'merge-msg tag' '
diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
index 024f8c06f7..97bfbee6e8 100755
--- a/t/t6300-for-each-ref.sh
+++ b/t/t6300-for-each-ref.sh
@@ -715,6 +715,29 @@ test_expect_success 'basic atom: head contents:trailers' '
test_cmp expect actual.clean
'
+test_expect_success 'trailer parsing not fooled by --- line' '
+ git commit --allow-empty -F - <<-\EOF &&
+ this is the subject
+
+ This is the body. The message has a "---" line which would confuse a
+ message+patch parser. But here we know we have only a commit message,
+ so we get it right.
+
+ trailer: wrong
+ ---
+ This is more body.
+
+ trailer: right
+ EOF
+
+ {
+ echo "trailer: right" &&
+ echo
+ } >expect &&
+ git for-each-ref --format="%(trailers)" refs/heads/master >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'Add symbolic ref for the following tests' '
git symbolic-ref refs/heads/sym refs/heads/master
'
diff --git a/t/t6600-test-reach.sh b/t/t6600-test-reach.sh
new file mode 100755
index 0000000000..d139a00d1d
--- /dev/null
+++ b/t/t6600-test-reach.sh
@@ -0,0 +1,242 @@
+#!/bin/sh
+
+test_description='basic commit reachability tests'
+
+. ./test-lib.sh
+
+# Construct a grid-like commit graph with points (x,y)
+# with 1 <= x <= 10, 1 <= y <= 10, where (x,y) has
+# parents (x-1, y) and (x, y-1), keeping in mind that
+# we drop a parent if a coordinate is nonpositive.
+#
+# (10,10)
+# / \
+# (10,9) (9,10)
+# / \ / \
+# (10,8) (9,9) (8,10)
+# / \ / \ / \
+# ( continued...)
+# \ / \ / \ /
+# (3,1) (2,2) (1,3)
+# \ / \ /
+# (2,1) (2,1)
+# \ /
+# (1,1)
+#
+# We use branch 'commit-x-y' to refer to (x,y).
+# This grid allows interesting reachability and
+# non-reachability queries: (x,y) can reach (x',y')
+# if and only if x' <= x and y' <= y.
+test_expect_success 'setup' '
+ for i in $(test_seq 1 10)
+ do
+ test_commit "1-$i" &&
+ git branch -f commit-1-$i
+ done &&
+ for j in $(test_seq 1 9)
+ do
+ git reset --hard commit-$j-1 &&
+ x=$(($j + 1)) &&
+ test_commit "$x-1" &&
+ git branch -f commit-$x-1 &&
+
+ for i in $(test_seq 2 10)
+ do
+ git merge commit-$j-$i -m "$x-$i" &&
+ git branch -f commit-$x-$i
+ done
+ done &&
+ git commit-graph write --reachable &&
+ mv .git/objects/info/commit-graph commit-graph-full &&
+ git show-ref -s commit-5-5 | git commit-graph write --stdin-commits &&
+ mv .git/objects/info/commit-graph commit-graph-half &&
+ git config core.commitGraph true
+'
+
+test_three_modes () {
+ test_when_finished rm -rf .git/objects/info/commit-graph &&
+ test-tool reach $1 <input >actual &&
+ test_cmp expect actual &&
+ cp commit-graph-full .git/objects/info/commit-graph &&
+ test-tool reach $1 <input >actual &&
+ test_cmp expect actual &&
+ cp commit-graph-half .git/objects/info/commit-graph &&
+ test-tool reach $1 <input >actual &&
+ test_cmp expect actual
+}
+
+test_expect_success 'ref_newer:miss' '
+ cat >input <<-\EOF &&
+ A:commit-5-7
+ B:commit-4-9
+ EOF
+ echo "ref_newer(A,B):0" >expect &&
+ test_three_modes ref_newer
+'
+
+test_expect_success 'ref_newer:hit' '
+ cat >input <<-\EOF &&
+ A:commit-5-7
+ B:commit-2-3
+ EOF
+ echo "ref_newer(A,B):1" >expect &&
+ test_three_modes ref_newer
+'
+
+test_expect_success 'in_merge_bases:hit' '
+ cat >input <<-\EOF &&
+ A:commit-5-7
+ B:commit-8-8
+ EOF
+ echo "in_merge_bases(A,B):1" >expect &&
+ test_three_modes in_merge_bases
+'
+
+test_expect_success 'in_merge_bases:miss' '
+ cat >input <<-\EOF &&
+ A:commit-6-8
+ B:commit-5-9
+ EOF
+ echo "in_merge_bases(A,B):0" >expect &&
+ test_three_modes in_merge_bases
+'
+
+test_expect_success 'is_descendant_of:hit' '
+ cat >input <<-\EOF &&
+ A:commit-5-7
+ X:commit-4-8
+ X:commit-6-6
+ X:commit-1-1
+ EOF
+ echo "is_descendant_of(A,X):1" >expect &&
+ test_three_modes is_descendant_of
+'
+
+test_expect_success 'is_descendant_of:miss' '
+ cat >input <<-\EOF &&
+ A:commit-6-8
+ X:commit-5-9
+ X:commit-4-10
+ X:commit-7-6
+ EOF
+ echo "is_descendant_of(A,X):0" >expect &&
+ test_three_modes is_descendant_of
+'
+
+test_expect_success 'get_merge_bases_many' '
+ cat >input <<-\EOF &&
+ A:commit-5-7
+ X:commit-4-8
+ X:commit-6-6
+ X:commit-8-3
+ EOF
+ {
+ echo "get_merge_bases_many(A,X):" &&
+ git rev-parse commit-5-6 \
+ commit-4-7 | sort
+ } >expect &&
+ test_three_modes get_merge_bases_many
+'
+
+test_expect_success 'reduce_heads' '
+ cat >input <<-\EOF &&
+ X:commit-1-10
+ X:commit-2-8
+ X:commit-3-6
+ X:commit-4-4
+ X:commit-1-7
+ X:commit-2-5
+ X:commit-3-3
+ X:commit-5-1
+ EOF
+ {
+ echo "reduce_heads(X):" &&
+ git rev-parse commit-5-1 \
+ commit-4-4 \
+ commit-3-6 \
+ commit-2-8 \
+ commit-1-10 | sort
+ } >expect &&
+ test_three_modes reduce_heads
+'
+
+test_expect_success 'can_all_from_reach:hit' '
+ cat >input <<-\EOF &&
+ X:commit-2-10
+ X:commit-3-9
+ X:commit-4-8
+ X:commit-5-7
+ X:commit-6-6
+ X:commit-7-5
+ X:commit-8-4
+ X:commit-9-3
+ Y:commit-1-9
+ Y:commit-2-8
+ Y:commit-3-7
+ Y:commit-4-6
+ Y:commit-5-5
+ Y:commit-6-4
+ Y:commit-7-3
+ Y:commit-8-1
+ EOF
+ echo "can_all_from_reach(X,Y):1" >expect &&
+ test_three_modes can_all_from_reach
+'
+
+test_expect_success 'can_all_from_reach:miss' '
+ cat >input <<-\EOF &&
+ X:commit-2-10
+ X:commit-3-9
+ X:commit-4-8
+ X:commit-5-7
+ X:commit-6-6
+ X:commit-7-5
+ X:commit-8-4
+ X:commit-9-3
+ Y:commit-1-9
+ Y:commit-2-8
+ Y:commit-3-7
+ Y:commit-4-6
+ Y:commit-5-5
+ Y:commit-6-4
+ Y:commit-8-5
+ EOF
+ echo "can_all_from_reach(X,Y):0" >expect &&
+ test_three_modes can_all_from_reach
+'
+
+test_expect_success 'commit_contains:hit' '
+ cat >input <<-\EOF &&
+ A:commit-7-7
+ X:commit-2-10
+ X:commit-3-9
+ X:commit-4-8
+ X:commit-5-7
+ X:commit-6-6
+ X:commit-7-5
+ X:commit-8-4
+ X:commit-9-3
+ EOF
+ echo "commit_contains(_,A,X,_):1" >expect &&
+ test_three_modes commit_contains &&
+ test_three_modes commit_contains --tag
+'
+
+test_expect_success 'commit_contains:miss' '
+ cat >input <<-\EOF &&
+ A:commit-6-5
+ X:commit-2-10
+ X:commit-3-9
+ X:commit-4-8
+ X:commit-5-7
+ X:commit-6-6
+ X:commit-7-5
+ X:commit-8-4
+ X:commit-9-3
+ EOF
+ echo "commit_contains(_,A,X,_):0" >expect &&
+ test_three_modes commit_contains &&
+ test_three_modes commit_contains --tag
+'
+
+test_done
diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh
index 9e59e5a5dd..36b50d0b4c 100755
--- a/t/t7001-mv.sh
+++ b/t/t7001-mv.sh
@@ -384,7 +384,7 @@ test_expect_success 'mv does not complain when no .gitmodules file is found' '
entry="$(git ls-files --stage sub | cut -f 1)" &&
mkdir mod &&
git mv sub mod/sub 2>actual.err &&
- ! test -s actual.err &&
+ test_must_be_empty actual.err &&
! test -e sub &&
[ "$entry" = "$(git ls-files --stage mod/sub | cut -f 1)" ] &&
(
@@ -408,7 +408,7 @@ test_expect_success 'mv will error out on a modified .gitmodules file unless sta
git diff-files --quiet -- sub &&
git add .gitmodules &&
git mv sub mod/sub 2>actual.err &&
- ! test -s actual.err &&
+ test_must_be_empty actual.err &&
! test -e sub &&
[ "$entry" = "$(git ls-files --stage mod/sub | cut -f 1)" ] &&
(
@@ -469,7 +469,7 @@ test_expect_success 'checking out a commit before submodule moved needs manual u
git update-index --refresh &&
git diff-files --quiet -- sub .gitmodules &&
git status -s sub2 >actual &&
- ! test -s actual
+ test_must_be_empty actual
'
test_expect_success 'mv -k does not accidentally destroy submodules' '
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index 465eb4ea3f..0b01862c23 100755
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -325,11 +325,10 @@ test_expect_success \
test_cmp expect actual
'
->expect
test_expect_success \
'listing tags using v.* should print nothing because none have v.' '
git tag -l "v.*" > actual &&
- test_cmp expect actual
+ test_must_be_empty actual
'
cat >expect <<EOF
@@ -1511,12 +1510,9 @@ test_expect_success 'inverse of the last test, with --no-contains' "
test_cmp expected actual
"
-cat > expected <<EOF
-EOF
-
test_expect_success 'checking that third commit has no tags' "
git tag -l --contains $hash3 v* >actual &&
- test_cmp expected actual
+ test_must_be_empty actual
"
cat > expected <<EOF
diff --git a/t/t7008-grep-binary.sh b/t/t7008-grep-binary.sh
index 615e7e0162..2d87c49b75 100755
--- a/t/t7008-grep-binary.sh
+++ b/t/t7008-grep-binary.sh
@@ -57,9 +57,8 @@ test_expect_success 'git grep -ah ina a' '
'
test_expect_success 'git grep -I ina a' '
- : >expect &&
test_must_fail git grep -I ina a >actual &&
- test_cmp expect actual
+ test_must_be_empty actual
'
test_expect_success 'git grep -c ina a' '
@@ -81,9 +80,8 @@ test_expect_success 'git grep -L bar a' '
'
test_expect_success 'git grep -q ina a' '
- : >expect &&
git grep -q ina a >actual &&
- test_cmp expect actual
+ test_must_be_empty actual
'
test_expect_success 'git grep -F ile a' '
diff --git a/t/t7064-wtstatus-pv2.sh b/t/t7064-wtstatus-pv2.sh
index b9a86d3347..11eccc231a 100755
--- a/t/t7064-wtstatus-pv2.sh
+++ b/t/t7064-wtstatus-pv2.sh
@@ -364,11 +364,8 @@ test_expect_success 'verify upstream fields in branch header' '
test_cmp expect actual &&
## Repeat the above but without --branch.
- cat >expect <<-EOF &&
- EOF
-
git status --porcelain=v2 --untracked-files=all >actual &&
- test_cmp expect actual &&
+ test_must_be_empty actual &&
## Test upstream-gone case. Fake this by pointing origin/master at
## a non-existing commit.
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index 94cb039a03..324933acfe 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -116,9 +116,8 @@ test_expect_success "checkout -m with dirty tree" '
git diff --name-status side >current.side &&
test_cmp expect.side current.side &&
- : >expect.index &&
git diff --cached >current.index &&
- test_cmp expect.index current.index
+ test_must_be_empty current.index
'
test_expect_success "checkout -m with dirty tree, renamed" '
@@ -139,7 +138,7 @@ test_expect_success "checkout -m with dirty tree, renamed" '
test_cmp expect uno &&
! test -f one &&
git diff --cached >current &&
- ! test -s current
+ test_must_be_empty current
'
@@ -163,7 +162,7 @@ test_expect_success 'checkout -m with merge conflict' '
fill d2 aT d7 aS >expect &&
test_cmp current expect &&
git diff --cached two >current &&
- ! test -s current
+ test_must_be_empty current
'
test_expect_success 'format of merge conflict from checkout -m' '
diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh
index 2b71e62ec2..c0ffc1022a 100755
--- a/t/t7400-submodule-basic.sh
+++ b/t/t7400-submodule-basic.sh
@@ -101,7 +101,6 @@ inspect() {
test_expect_success 'submodule add' '
echo "refs/heads/master" >expect &&
- >empty &&
(
cd addtest &&
@@ -123,7 +122,7 @@ test_expect_success 'submodule add' '
inspect addtest/submod ../.. &&
test_cmp expect heads &&
test_cmp expect head &&
- test_cmp empty untracked
+ test_must_be_empty untracked
'
test_expect_success 'setup parent and one repository' '
@@ -189,7 +188,6 @@ test_expect_success 'submodule add --branch' '
refs/heads/initial
refs/heads/master
EOF
- >empty &&
(
cd addtest &&
@@ -202,12 +200,11 @@ test_expect_success 'submodule add --branch' '
inspect addtest/submod-branch ../.. &&
test_cmp expect-heads heads &&
test_cmp expect-head head &&
- test_cmp empty untracked
+ test_must_be_empty untracked
'
test_expect_success 'submodule add with ./ in path' '
echo "refs/heads/master" >expect &&
- >empty &&
(
cd addtest &&
@@ -219,12 +216,11 @@ test_expect_success 'submodule add with ./ in path' '
inspect addtest/dotsubmod/frotz ../../.. &&
test_cmp expect heads &&
test_cmp expect head &&
- test_cmp empty untracked
+ test_must_be_empty untracked
'
test_expect_success 'submodule add with /././ in path' '
echo "refs/heads/master" >expect &&
- >empty &&
(
cd addtest &&
@@ -236,12 +232,11 @@ test_expect_success 'submodule add with /././ in path' '
inspect addtest/dotslashdotsubmod/frotz ../../.. &&
test_cmp expect heads &&
test_cmp expect head &&
- test_cmp empty untracked
+ test_must_be_empty untracked
'
test_expect_success 'submodule add with // in path' '
echo "refs/heads/master" >expect &&
- >empty &&
(
cd addtest &&
@@ -253,12 +248,11 @@ test_expect_success 'submodule add with // in path' '
inspect addtest/slashslashsubmod/frotz ../../.. &&
test_cmp expect heads &&
test_cmp expect head &&
- test_cmp empty untracked
+ test_must_be_empty untracked
'
test_expect_success 'submodule add with /.. in path' '
echo "refs/heads/master" >expect &&
- >empty &&
(
cd addtest &&
@@ -270,12 +264,11 @@ test_expect_success 'submodule add with /.. in path' '
inspect addtest/realsubmod ../.. &&
test_cmp expect heads &&
test_cmp expect head &&
- test_cmp empty untracked
+ test_must_be_empty untracked
'
test_expect_success 'submodule add with ./, /.. and // in path' '
echo "refs/heads/master" >expect &&
- >empty &&
(
cd addtest &&
@@ -287,7 +280,7 @@ test_expect_success 'submodule add with ./, /.. and // in path' '
inspect addtest/realsubmod2 ../.. &&
test_cmp expect heads &&
test_cmp expect head &&
- test_cmp empty untracked
+ test_must_be_empty untracked
'
test_expect_success !CYGWIN 'submodule add with \\ in path' '
@@ -306,7 +299,6 @@ test_expect_success !CYGWIN 'submodule add with \\ in path' '
test_expect_success 'submodule add in subdirectory' '
echo "refs/heads/master" >expect &&
- >empty &&
mkdir addtest/sub &&
(
@@ -319,7 +311,7 @@ test_expect_success 'submodule add in subdirectory' '
inspect addtest/realsubmod3 ../.. &&
test_cmp expect heads &&
test_cmp expect head &&
- test_cmp empty untracked
+ test_must_be_empty untracked
'
test_expect_success 'submodule add in subdirectory with relative path should fail' '
@@ -502,8 +494,6 @@ test_expect_success 'checkout superproject with subproject already present' '
'
test_expect_success 'apply submodule diff' '
- >empty &&
-
git branch second &&
(
cd init &&
@@ -518,7 +508,7 @@ test_expect_success 'apply submodule diff' '
git apply --index P.diff &&
git diff --cached master >staged &&
- test_cmp empty staged
+ test_must_be_empty staged
'
test_expect_success 'update --init' '
@@ -994,11 +984,6 @@ test_expect_success 'submodule deinit should remove the whole submodule section
rmdir init
'
-test_expect_success 'submodule deinit should unset core.worktree' '
- test_path_is_file .git/modules/example/config &&
- test_must_fail git config -f .git/modules/example/config core.worktree
-'
-
test_expect_success 'submodule deinit from subdirectory' '
git submodule update --init &&
git config submodule.example.foo bar &&
diff --git a/t/t7401-submodule-summary.sh b/t/t7401-submodule-summary.sh
index 1cd12b38c5..9bc841d085 100755
--- a/t/t7401-submodule-summary.sh
+++ b/t/t7401-submodule-summary.sh
@@ -300,7 +300,7 @@ test_expect_success 'should not fail in an empty repo' "
git init xyzzy &&
cd xyzzy &&
git submodule summary >output 2>&1 &&
- test_cmp output /dev/null
+ test_must_be_empty output
"
test_done
diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh
index 51646d8019..1a6773ee68 100755
--- a/t/t7501-commit.sh
+++ b/t/t7501-commit.sh
@@ -517,6 +517,22 @@ Myfooter: x" &&
test_cmp expected actual
'
+test_expect_success 'signoff not confused by ---' '
+ cat >expected <<-EOF &&
+ subject
+
+ body
+ ---
+ these dashes confuse the parser!
+
+ Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+ EOF
+ # should be a noop, since we already signed
+ git commit --allow-empty --signoff -F expected &&
+ git log -1 --pretty=format:%B >actual &&
+ test_cmp expected actual
+'
+
test_expect_success 'multiple -m' '
>negative &&
@@ -582,13 +598,11 @@ test_expect_success 'same tree (merge and amend merge)' '
git merge -s ours side -m "empty ok" &&
git diff HEAD^ HEAD >actual &&
- : >expected &&
- test_cmp expected actual &&
+ test_must_be_empty actual &&
git commit --amend -m "empty really ok" &&
git diff HEAD^ HEAD >actual &&
- : >expected &&
- test_cmp expected actual
+ test_must_be_empty actual
'
@@ -677,7 +691,7 @@ test_expect_success '--dry-run with conflicts fixed from a merge' '
git checkout -b branch-2 HEAD^1 &&
echo "commit-2-state" >test-file &&
git commit -m "commit 2" -i test-file &&
- ! $(git merge --no-commit commit-1) &&
+ test_must_fail git merge --no-commit commit-1 &&
echo "commit-2-state" >test-file &&
git add test-file &&
git commit --dry-run &&
diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
index 164719d1c9..c441861331 100755
--- a/t/t7513-interpret-trailers.sh
+++ b/t/t7513-interpret-trailers.sh
@@ -1417,4 +1417,46 @@ test_expect_success 'unfold' '
test_cmp expected actual
'
+test_expect_success 'handling of --- lines in input' '
+ echo "real-trailer: just right" >expected &&
+
+ git interpret-trailers --parse >actual <<-\EOF &&
+ subject
+
+ body
+
+ not-a-trailer: too soon
+ ------ this is just a line in the commit message with a bunch of
+ ------ dashes; it does not have any syntactic meaning.
+
+ real-trailer: just right
+ ---
+ below the dashed line may be a patch, etc.
+
+ not-a-trailer: too late
+ EOF
+
+ test_cmp expected actual
+'
+
+test_expect_success 'suppress --- handling' '
+ echo "real-trailer: just right" >expected &&
+
+ git interpret-trailers --parse --no-divider >actual <<-\EOF &&
+ subject
+
+ This commit message has a "---" in it, but because we tell
+ interpret-trailers not to respect that, it has no effect.
+
+ not-a-trailer: too soon
+ ---
+
+ This is still the commit message body.
+
+ real-trailer: just right
+ EOF
+
+ test_cmp expected actual
+'
+
test_done
diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh
index 6736d8d131..106148254d 100755
--- a/t/t7600-merge.sh
+++ b/t/t7600-merge.sh
@@ -38,7 +38,6 @@ printf '%s\n' '1 X' 2 3 4 5 6 7 8 9 >result.1
printf '%s\n' '1 X' 2 3 4 '5 X' 6 7 8 9 >result.1-5
printf '%s\n' '1 X' 2 3 4 '5 X' 6 7 8 '9 X' >result.1-5-9
printf '%s\n' 1 2 3 4 5 6 7 8 '9 Z' >result.9z
->empty
create_merge_msgs () {
echo "Merge tag 'c2'" >msg.1-5 &&
@@ -58,8 +57,6 @@ create_merge_msgs () {
echo &&
git log --no-merges ^HEAD c2 c3
} >squash.1-5-9 &&
- : >msg.nologff &&
- : >msg.nolognoff &&
{
echo "* tag 'c3':" &&
echo " commit 3"
@@ -519,7 +516,7 @@ test_expect_success 'tolerate unknown values for merge.ff' '
test_tick &&
git merge c1 2>message &&
verify_head "$c1" &&
- test_cmp empty message
+ test_must_be_empty message
'
test_expect_success 'combining --squash and --no-ff is refused' '
@@ -551,13 +548,13 @@ test_expect_success 'merge log message' '
git reset --hard c0 &&
git merge --no-log c2 &&
git show -s --pretty=format:%b HEAD >msg.act &&
- test_cmp msg.nologff msg.act &&
+ test_must_be_empty msg.act &&
git reset --hard c0 &&
test_config branch.master.mergeoptions "--no-ff" &&
git merge --no-log c2 &&
git show -s --pretty=format:%b HEAD >msg.act &&
- test_cmp msg.nolognoff msg.act &&
+ test_must_be_empty msg.act &&
git merge --log c3 &&
git show -s --pretty=format:%b HEAD >msg.act &&
diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh
index b18503de81..a9fb971615 100755
--- a/t/t7610-mergetool.sh
+++ b/t/t7610-mergetool.sh
@@ -328,9 +328,8 @@ test_expect_success 'mergetool produces no errors when keepBackup is used' '
git checkout -b test$test_count move-to-c &&
test_config mergetool.keepBackup true &&
test_must_fail git merge move-to-b &&
- : >expect &&
echo d | git mergetool a/a/file.txt 2>actual &&
- test_cmp expect actual &&
+ test_must_be_empty actual &&
! test -d a
'
diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh
index d826e24b45..be5c1bd553 100755
--- a/t/t7810-grep.sh
+++ b/t/t7810-grep.sh
@@ -217,9 +217,8 @@ do
'
test_expect_success "grep -w $L (w)" '
- : >expected &&
test_must_fail git grep -n -w -e "^w" $H >actual &&
- test_cmp expected actual
+ test_must_be_empty actual
'
test_expect_success "grep -w $L (x)" '
@@ -239,26 +238,24 @@ do
'
test_expect_success "grep -w $L (y-2)" '
- : >expected &&
if git grep -n -w -e "^y y" $H >actual
then
echo should not have matched
cat actual
false
else
- test_cmp expected actual
+ test_must_be_empty actual
fi
'
test_expect_success "grep -w $L (z)" '
- : >expected &&
if git grep -n -w -e "^z" $H >actual
then
echo should not have matched
cat actual
false
else
- test_cmp expected actual
+ test_must_be_empty actual
fi
'
@@ -498,7 +495,7 @@ test_expect_success 'grep -L -C' '
test_expect_success 'grep --files-without-match --quiet' '
git grep --files-without-match --quiet nonexistent_string >actual &&
- test_cmp /dev/null actual
+ test_must_be_empty actual
'
cat >expected <<EOF
@@ -619,11 +616,10 @@ z:zzz
EOF
test_expect_success 'grep -q, silently report matches' '
- >empty &&
git grep -q mmap >actual &&
- test_cmp empty actual &&
+ test_must_be_empty actual &&
test_must_fail git grep -q qfwfq >actual &&
- test_cmp empty actual
+ test_must_be_empty actual
'
test_expect_success 'grep -C1 hunk mark between files' '
@@ -716,8 +712,7 @@ test_expect_success 'log grep (9)' '
test_expect_success 'log grep (9)' '
git log -g --grep-reflog="commit: third" --author="non-existent" --pretty=tformat:%s >actual &&
- : >expect &&
- test_cmp expect actual
+ test_must_be_empty actual
'
test_expect_success 'log --grep-reflog can only be used under -g' '
@@ -807,15 +802,13 @@ test_expect_success 'log --all-match --grep --grep --author takes intersection'
'
test_expect_success 'log --author does not search in timestamp' '
- : >expect &&
git log --author="$GIT_AUTHOR_DATE" >actual &&
- test_cmp expect actual
+ test_must_be_empty actual
'
test_expect_success 'log --committer does not search in timestamp' '
- : >expect &&
git log --committer="$GIT_COMMITTER_DATE" >actual &&
- test_cmp expect actual
+ test_must_be_empty actual
'
test_expect_success 'grep with CE_VALID file' '
@@ -956,7 +949,7 @@ test_expect_success 'grep from a subdirectory to search wider area (2)' '
(
cd s &&
test_expect_code 1 git grep xxyyzz .. >out &&
- ! test -s out
+ test_must_be_empty out
)
'
@@ -1065,13 +1058,12 @@ test_expect_success 'inside git repository but with --no-index' '
echo ".gitignore:.*o*" &&
cat is/expect.unignored
} >is/expect.full &&
- : >is/expect.empty &&
echo file2:world >is/expect.sub &&
(
cd is/git &&
git init &&
test_must_fail git grep o >../actual.full &&
- test_cmp ../expect.empty ../actual.full &&
+ test_must_be_empty ../actual.full &&
git grep --untracked o >../actual.unignored &&
test_cmp ../expect.unignored ../actual.unignored &&
@@ -1084,7 +1076,7 @@ test_expect_success 'inside git repository but with --no-index' '
cd sub &&
test_must_fail git grep o >../../actual.sub &&
- test_cmp ../../expect.empty ../../actual.sub &&
+ test_must_be_empty ../../actual.sub &&
git grep --no-index o >../../actual.sub &&
test_cmp ../../expect.sub ../../actual.sub &&
@@ -1250,10 +1242,9 @@ test_expect_success !PCRE 'grep -P pattern errors without PCRE' '
'
test_expect_success 'grep pattern with grep.extendedRegexp=true' '
- >empty &&
test_must_fail git -c grep.extendedregexp=true \
grep "\p{Ps}.*?\p{Pe}" hello.c >actual &&
- test_cmp empty actual
+ test_must_be_empty actual
'
test_expect_success PCRE 'grep -P pattern with grep.extendedRegexp=true' '
diff --git a/t/t7811-grep-open.sh b/t/t7811-grep-open.sh
index e1951a5cbb..d1ebfd88c7 100755
--- a/t/t7811-grep-open.sh
+++ b/t/t7811-grep-open.sh
@@ -51,14 +51,13 @@ test_expect_success SIMPLEPAGER 'git grep -O' '
grep.h
EOF
echo grep.h >expect.notless &&
- >empty &&
PATH=.:$PATH git grep -O GREP_PATTERN >out &&
{
test_cmp expect.less pager-args ||
test_cmp expect.notless pager-args
} &&
- test_cmp empty out
+ test_must_be_empty out
'
test_expect_success 'git grep -O --cached' '
@@ -72,7 +71,6 @@ test_expect_success 'git grep -O --no-index' '
grep.h
untracked
EOF
- >empty &&
(
GIT_PAGER='\''printf "%s\n" >pager-args'\'' &&
@@ -80,7 +78,7 @@ test_expect_success 'git grep -O --no-index' '
git grep --no-index -O GREP_PATTERN >out
) &&
test_cmp expect pager-args &&
- test_cmp empty out
+ test_must_be_empty out
'
test_expect_success 'setup: fake "less"' '
@@ -96,15 +94,14 @@ test_expect_success 'git grep -O jumps to line in less' '
+/*GREP_PATTERN
grep.h
EOF
- >empty &&
GIT_PAGER=./less git grep -O GREP_PATTERN >out &&
test_cmp expect actual &&
- test_cmp empty out &&
+ test_must_be_empty out &&
git grep -O./less GREP_PATTERN >out2 &&
test_cmp expect actual &&
- test_cmp empty out2
+ test_must_be_empty out2
'
test_expect_success 'modified file' '
@@ -122,7 +119,7 @@ test_expect_success 'modified file' '
test_when_finished "git checkout HEAD unrelated" &&
GIT_PAGER=./less git grep -F -O "enum grep_pat_token" >out &&
test_cmp expect actual &&
- test_cmp empty out
+ test_must_be_empty out
'
test_expect_success 'copes with color settings' '
@@ -138,7 +135,6 @@ test_expect_success 'copes with color settings' '
test_expect_success 'run from subdir' '
rm -f actual &&
echo grep.c >expect &&
- >empty &&
(
cd subdir &&
@@ -156,8 +152,8 @@ test_expect_success 'run from subdir' '
;;
esac &&
test_cmp expect args &&
- test_cmp empty out &&
- test_cmp empty out2
+ test_must_be_empty out &&
+ test_must_be_empty out2
'
test_done
diff --git a/t/t8010-cat-file-filters.sh b/t/t8010-cat-file-filters.sh
index 0f86c19174..31de4b64dc 100755
--- a/t/t8010-cat-file-filters.sh
+++ b/t/t8010-cat-file-filters.sh
@@ -47,7 +47,7 @@ test_expect_success 'cat-file --textconv --path=<path> works' '
test_expect_success '--path=<path> complains without --textconv/--filters' '
sha1=$(git rev-parse -q --verify HEAD:world.txt) &&
test_must_fail git cat-file --path=hello.txt blob $sha1 >actual 2>err &&
- test ! -s actual &&
+ test_must_be_empty actual &&
grep "path.*needs.*filters" err
'
diff --git a/t/t9011-svn-da.sh b/t/t9011-svn-da.sh
index b38d16f9db..ab1ef28fd9 100755
--- a/t/t9011-svn-da.sh
+++ b/t/t9011-svn-da.sh
@@ -18,7 +18,7 @@ test_expect_success 'reject empty delta' '
test_expect_success 'delta can empty file' '
printf "SVNQ" | q_to_nul >clear.delta &&
test-svn-fe -d preimage clear.delta 4 >actual &&
- test_cmp empty actual
+ test_must_be_empty actual
'
test_expect_success 'reject svndiff2' '
@@ -29,7 +29,7 @@ test_expect_success 'reject svndiff2' '
test_expect_success 'one-window empty delta' '
printf "SVNQ%s" "QQQQQ" | q_to_nul >clear.onewindow &&
test-svn-fe -d preimage clear.onewindow 9 >actual &&
- test_cmp empty actual
+ test_must_be_empty actual
'
test_expect_success 'reject incomplete window header' '
@@ -50,7 +50,7 @@ test_expect_success 'two-window empty delta' '
printf "SVNQ%s%s" "QQQQQ" "QQQQQ" | q_to_nul >clear.twowindow &&
test-svn-fe -d preimage clear.twowindow 14 >actual &&
test_must_fail test-svn-fe -d preimage clear.twowindow 13 &&
- test_cmp empty actual
+ test_must_be_empty actual
'
test_expect_success 'noisy zeroes' '
@@ -60,7 +60,7 @@ test_expect_success 'noisy zeroes' '
q_to_nul >clear.noisy &&
len=$(wc -c <clear.noisy) &&
test-svn-fe -d preimage clear.noisy $len &&
- test_cmp empty actual
+ test_must_be_empty actual
'
test_expect_success 'reject variable-length int in magic' '
@@ -83,7 +83,7 @@ test_expect_success 'reject truncated integer' '
test_expect_success 'nonempty (but unused) preimage view' '
printf "SVNQ%b" "Q\003QQQ" | q_to_nul >clear.readpreimage &&
test-svn-fe -d preimage clear.readpreimage 9 >actual &&
- test_cmp empty actual
+ test_must_be_empty actual
'
test_expect_success 'preimage view: right endpoint cannot backtrack' '
@@ -99,7 +99,7 @@ test_expect_success 'preimage view: left endpoint can advance' '
q_to_nul >clear.shrinkbacktrack &&
test-svn-fe -d preimage clear.preshrink 14 >actual &&
test_must_fail test-svn-fe -d preimage clear.shrinkbacktrack 14 &&
- test_cmp empty actual
+ test_must_be_empty actual
'
test_expect_success 'preimage view: offsets compared by value' '
@@ -109,7 +109,7 @@ test_expect_success 'preimage view: offsets compared by value' '
q_to_nul >clear.noisyadvance &&
test_must_fail test-svn-fe -d preimage clear.noisybacktrack 15 &&
test-svn-fe -d preimage clear.noisyadvance 15 &&
- test_cmp empty actual
+ test_must_be_empty actual
'
test_expect_success 'preimage view: reject truncated preimage' '
diff --git a/t/t9131-git-svn-empty-symlink.sh b/t/t9131-git-svn-empty-symlink.sh
index f762038f0e..3bf4255aa3 100755
--- a/t/t9131-git-svn-empty-symlink.sh
+++ b/t/t9131-git-svn-empty-symlink.sh
@@ -85,7 +85,7 @@ EOF
test_expect_success 'clone using git svn' 'git svn clone -r1 "$svnrepo" x'
test_expect_success 'enable broken symlink workaround' \
'(cd x && git config svn.brokenSymlinkWorkaround true)'
-test_expect_success '"bar" is an empty file' 'test -f x/bar && ! test -s x/bar'
+test_expect_success '"bar" is an empty file' 'test_must_be_empty x/bar'
test_expect_success 'get "bar" => symlink fix from svn' \
'(cd x && git svn rebase)'
test_expect_success SYMLINKS '"bar" becomes a symlink' 'test -h x/bar'
@@ -94,14 +94,14 @@ test_expect_success SYMLINKS '"bar" becomes a symlink' 'test -h x/bar'
test_expect_success 'clone using git svn' 'git svn clone -r1 "$svnrepo" y'
test_expect_success 'disable broken symlink workaround' \
'(cd y && git config svn.brokenSymlinkWorkaround false)'
-test_expect_success '"bar" is an empty file' 'test -f y/bar && ! test -s y/bar'
+test_expect_success '"bar" is an empty file' 'test_must_be_empty y/bar'
test_expect_success 'get "bar" => symlink fix from svn' \
'(cd y && git svn rebase)'
test_expect_success '"bar" does not become a symlink' '! test -L y/bar'
# svn.brokenSymlinkWorkaround is unset
test_expect_success 'clone using git svn' 'git svn clone -r1 "$svnrepo" z'
-test_expect_success '"bar" is an empty file' 'test -f z/bar && ! test -s z/bar'
+test_expect_success '"bar" is an empty file' 'test_must_be_empty z/bar'
test_expect_success 'get "bar" => symlink fix from svn' \
'(cd z && git svn rebase)'
test_expect_success '"bar" does not become a symlink' '! test -L z/bar'
diff --git a/t/t9135-git-svn-moved-branch-empty-file.sh b/t/t9135-git-svn-moved-branch-empty-file.sh
index 93db45db7d..2f80b216fe 100755
--- a/t/t9135-git-svn-moved-branch-empty-file.sh
+++ b/t/t9135-git-svn-moved-branch-empty-file.sh
@@ -13,8 +13,7 @@ test_expect_success 'test that b1 exists and is empty' '
(
cd x &&
git reset --hard origin/branch-c &&
- test -f b1 &&
- ! test -s b1
+ test_must_be_empty b1
)
'
diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh
index cd61288aa1..c5946cb0b8 100755
--- a/t/t9200-git-cvsexportcommit.sh
+++ b/t/t9200-git-cvsexportcommit.sh
@@ -43,11 +43,11 @@ check_entries () {
sed -ne '/^\//p' "$1/CVS/Entries" | sort | cut -d/ -f2,3,5 >actual
if test -z "$2"
then
- >expected
+ test_must_be_empty actual
else
printf '%s\n' "$2" | tr '|' '\012' >expected
+ test_cmp expected actual
fi
- test_cmp expected actual
}
test_expect_success \
diff --git a/t/t9802-git-p4-filetype.sh b/t/t9802-git-p4-filetype.sh
index 1fc9b33aeb..9978352d78 100755
--- a/t/t9802-git-p4-filetype.sh
+++ b/t/t9802-git-p4-filetype.sh
@@ -310,7 +310,7 @@ test_expect_success SYMLINKS 'empty symlink target' '
# p4 to sync here will make it generate errors.
cd "$cli" &&
p4 print -q //depot/empty-symlink#2 >out &&
- test ! -s out
+ test_must_be_empty out
) &&
test_when_finished cleanup_git &&
diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh
index ab890d3d4b..81a5179e28 100755
--- a/t/t9903-bash-prompt.sh
+++ b/t/t9903-bash-prompt.sh
@@ -513,10 +513,9 @@ test_expect_success 'prompt - format string starting with dash' '
test_expect_success 'prompt - pc mode' '
printf "BEFORE: (\${__git_ps1_branch_name}):AFTER\\nmaster" >expected &&
- printf "" >expected_output &&
(
__git_ps1 "BEFORE:" ":AFTER" >"$actual" &&
- test_cmp expected_output "$actual" &&
+ test_must_be_empty "$actual" &&
printf "%s\\n%s" "$PS1" "${__git_ps1_branch_name}" >"$actual"
) &&
test_cmp expected "$actual"
@@ -712,13 +711,12 @@ test_expect_success 'prompt - hide if pwd ignored - env var set, config disabled
'
test_expect_success 'prompt - hide if pwd ignored - env var set, config unset' '
- printf "" >expected &&
(
cd ignored_dir &&
GIT_PS1_HIDE_IF_PWD_IGNORED=y &&
__git_ps1 >"$actual"
) &&
- test_cmp expected "$actual"
+ test_must_be_empty "$actual"
'
test_expect_success 'prompt - hide if pwd ignored - env var set, config unset, pc mode' '
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
index 4207af4077..d82fac9d79 100644
--- a/t/test-lib-functions.sh
+++ b/t/test-lib-functions.sh
@@ -42,6 +42,8 @@ test_decode_color () {
function name(n) {
if (n == 0) return "RESET";
if (n == 1) return "BOLD";
+ if (n == 2) return "FAINT";
+ if (n == 3) return "ITALIC";
if (n == 7) return "REVERSE";
if (n == 30) return "BLACK";
if (n == 31) return "RED";
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 8bb0f4348e..3f95bfda60 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -867,7 +867,7 @@ then
# handle only executables, unless they are shell libraries that
# need to be in the exec-path.
test -x "$1" ||
- test "# " = "$(head -c 2 <"$1")" ||
+ test "# " = "$(test_copy_bytes 2 <"$1")" ||
return;
base=$(basename "$1")
@@ -882,7 +882,7 @@ then
# do not override scripts
if test -x "$symlink_target" &&
test ! -d "$symlink_target" &&
- test "#!" != "$(head -c 2 < "$symlink_target")"
+ test "#!" != "$(test_copy_bytes 2 <"$symlink_target")"
then
symlink_target=../valgrind.sh
fi
@@ -1083,6 +1083,12 @@ else
test_set_prereq C_LOCALE_OUTPUT
fi
+if test -z "$GIT_TEST_CHECK_CACHE_TREE"
+then
+ GIT_TEST_CHECK_CACHE_TREE=true
+ export GIT_TEST_CHECK_CACHE_TREE
+fi
+
test_lazy_prereq PIPE '
# test whether the filesystem supports FIFOs
test_have_prereq !MINGW,!CYGWIN &&