Merge branch 'mm/test-grep-lint' into next

The test suite has been updated to use the 'test_grep' helper instead
of bare 'grep' for test assertions, allowing file contents to be
printed on failure for easier debugging. A new 'greplint' linter has
been introduced to detect and prevent new bare 'grep' assertions from
being added to the test suite.

* mm/test-grep-lint:
  t: add greplint to detect bare grep assertions
  t: convert grep assertions to test_grep
  t: fix Lexer line count for $() inside double-quoted strings
  t: extract chainlint's parser into shared module
  t: fix grep assertions missing file arguments
  t/README: document test_grep helper
next
Junio C Hamano 2026-07-10 08:27:01 -07:00
commit 1916c07bf5
385 changed files with 3810 additions and 3328 deletions

2
t/.gitattributes vendored
View File

@ -1,5 +1,7 @@
t[0-9][0-9][0-9][0-9]/* -whitespace
/chainlint/*.expect eol=lf -whitespace
/greplint/*.expect eol=lf -whitespace
/greplint/*.test eol=lf -whitespace
/t0110/url-* binary
/t3206/* eol=lf
/t3900/*.txt eol=lf

View File

@ -27,9 +27,11 @@ TEST_LINT ?= test-lint
ifdef TEST_OUTPUT_DIRECTORY
TEST_RESULTS_DIRECTORY = $(TEST_OUTPUT_DIRECTORY)/test-results
CHAINLINTTMP = $(TEST_OUTPUT_DIRECTORY)/chainlinttmp
GREPLINTTMP = $(TEST_OUTPUT_DIRECTORY)/greplinttmp
else
TEST_RESULTS_DIRECTORY = test-results
CHAINLINTTMP = chainlinttmp
GREPLINTTMP = greplinttmp
endif

# Shell quote;
@ -38,6 +40,7 @@ TEST_SHELL_PATH_SQ = $(subst ','\'',$(TEST_SHELL_PATH))
PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
TEST_RESULTS_DIRECTORY_SQ = $(subst ','\'',$(TEST_RESULTS_DIRECTORY))
CHAINLINTTMP_SQ = $(subst ','\'',$(CHAINLINTTMP))
GREPLINTTMP_SQ = $(subst ','\'',$(GREPLINTTMP))

T = $(sort $(wildcard t[0-9][0-9][0-9][0-9]-*.sh))
THELPERS = $(sort $(filter-out $(T),$(wildcard *.sh)))
@ -45,6 +48,7 @@ TLIBS = $(sort $(wildcard lib-*.sh)) annotate-tests.sh
TPERF = $(sort $(wildcard perf/p[0-9][0-9][0-9][0-9]-*.sh))
TINTEROP = $(sort $(wildcard interop/i[0-9][0-9][0-9][0-9]-*.sh))
CHAINLINTTESTS = $(sort $(patsubst chainlint/%.test,%,$(wildcard chainlint/*.test)))
GREPLINTTESTS = $(sort $(patsubst greplint/%.test,%,$(wildcard greplint/*.test)))
CHAINLINT = '$(PERL_PATH_SQ)' chainlint.pl
UNIT_TEST_SOURCES = $(wildcard unit-tests/t-*.c)
UNIT_TEST_PROGRAMS = $(patsubst unit-tests/%.c,unit-tests/bin/%$(X),$(UNIT_TEST_SOURCES))
@ -63,8 +67,8 @@ test: pre-clean check-meson $(TEST_LINT)
$(CHAINLINTSUPPRESS) $(MAKE) aggregate-results-and-cleanup

ifneq ($(PERL_PATH),)
test: check-chainlint
prove: check-chainlint
test: check-chainlint check-greplint
prove: check-chainlint check-greplint
endif

failed:
@ -102,7 +106,7 @@ unit-tests-test-tool:
pre-clean:
$(RM) -r '$(TEST_RESULTS_DIRECTORY_SQ)'

clean-except-prove-cache: clean-chainlint
clean-except-prove-cache: clean-chainlint clean-greplint
$(RM) -r 'trash directory'.*
$(RM) -r valgrind/bin

@ -120,6 +124,17 @@ check-chainlint:
{ $(CHAINLINT) --emit-all '$(CHAINLINTTMP_SQ)'/tests >'$(CHAINLINTTMP_SQ)'/actual || true; } && \
diff -u '$(CHAINLINTTMP_SQ)'/expect '$(CHAINLINTTMP_SQ)'/actual

clean-greplint:
$(RM) -r '$(GREPLINTTMP_SQ)'

check-greplint:
@mkdir -p '$(GREPLINTTMP_SQ)' && \
'$(PERL_PATH_SQ)' greplint-cat.pl '$(GREPLINTTMP_SQ)' $(GREPLINTTESTS) && \
{ '$(PERL_PATH_SQ)' greplint.pl \
$(patsubst %,greplint/%.test,$(GREPLINTTESTS)) \
>'$(GREPLINTTMP_SQ)'/actual 2>&1 || true; } && \
diff -u '$(GREPLINTTMP_SQ)'/expect '$(GREPLINTTMP_SQ)'/actual

check-meson:
@# awk acts up when trying to match single quotes, so we use \047 instead.
@mkdir -p mesontmp && \
@ -139,7 +154,7 @@ check-meson:
test-lint: test-lint-duplicates test-lint-executable \
test-lint-filenames
ifneq ($(PERL_PATH),)
test-lint: test-lint-shell-syntax
test-lint: test-lint-shell-syntax test-greplint
else
GIT_TEST_CHAIN_LINT = 0
endif
@ -160,6 +175,9 @@ test-lint-executable:
test-lint-shell-syntax:
@'$(PERL_PATH_SQ)' check-non-portable-shell.pl $(T) $(THELPERS) $(TPERF)

test-greplint:
@'$(PERL_PATH_SQ)' greplint.pl $(T) $(THELPERS) $(TPERF)

test-lint-filenames:
@# We do *not* pass a glob to ls-files but use grep instead, to catch
@# non-ASCII characters (which are quoted within double-quotes)
@ -185,7 +203,8 @@ perf:
$(MAKE) -C perf/ all

.PHONY: pre-clean $(T) aggregate-results clean valgrind perf \
check-chainlint clean-chainlint test-chainlint $(UNIT_TESTS)
check-chainlint clean-chainlint test-chainlint \
check-greplint clean-greplint test-greplint $(UNIT_TESTS)

.PHONY: libgit-sys-test libgit-rs-test
libgit-sys-test:

View File

@ -1039,6 +1039,40 @@ see test-lib-functions.sh for the full list and their options.

Check whether a file has the length it is expected to.

- test_grep [!] [<grep-options>] <pattern> <file>

Check whether <file> contains a line matching <pattern>, or
with '!' that no line matches. Use this instead of bare
'grep <pattern> <file>' in test assertions. On failure,
test_grep prints the contents of <file> for easier debugging,
whereas a bare 'grep' would fail silently.

For negation, pass '!' as the first argument:

test_grep ! "^diff --git" actual

Do not negate by writing '! test_grep', as that suppresses the
diagnostic output.

test_grep should only be used as a test assertion. When grep
is used as a data filter (e.g. 'grep -v "^index" actual >filtered')
or inside a command substitution (e.g. '$(grep -c ...)'), plain
'grep' is the right choice because the exit code is not the
assertion itself.

test_grep requires <file> to exist and will BUG otherwise, so
use it only where the file is guaranteed to exist at that point.
When a file's presence is conditional (a backend-specific file,
or a path that only exists on some platforms, such as an NTFS
8.3 short name), guard the assertion on that condition (a
prerequisite, or a 'test -e' on the path) and use test_grep
inside the guard:

if test_have_prereq REFFILES
then
test_grep ! "$refname" .git/packed-refs
fi

- test_path_is_file <path>
test_path_is_dir <path>
test_path_is_missing <path>

View File

@ -23,458 +23,9 @@ my $jobs = -1;
my $show_stats;
my $emit_all;

# Lexer tokenizes POSIX shell scripts. It is roughly modeled after section 2.3
# "Token Recognition" of POSIX chapter 2 "Shell Command Language". Although
# similar to lexical analyzers for other languages, this one differs in a few
# substantial ways due to quirks of the shell command language.
#
# For instance, in many languages, newline is just whitespace like space or
# TAB, but in shell a newline is a command separator, thus a distinct lexical
# token. A newline is significant and returned as a distinct token even at the
# end of a shell comment.
#
# In other languages, `1+2` would typically be scanned as three tokens
# (`1`, `+`, and `2`), but in shell it is a single token. However, the similar
# `1 + 2`, which embeds whitespace, is scanned as three token in shell, as well.
# In shell, several characters with special meaning lose that meaning when not
# surrounded by whitespace. For instance, the negation operator `!` is special
# when standing alone surrounded by whitespace; whereas in `foo!uucp` it is
# just a plain character in the longer token "foo!uucp". In many other
# languages, `"string"/foo:'string'` might be scanned as five tokens ("string",
# `/`, `foo`, `:`, and 'string'), but in shell, it is just a single token.
#
# The lexical analyzer for the shell command language is also somewhat unusual
# in that it recursively invokes the parser to handle the body of `$(...)`
# expressions which can contain arbitrary shell code. Such expressions may be
# encountered both inside and outside of double-quoted strings.
#
# The lexical analyzer is responsible for consuming shell here-doc bodies which
# extend from the line following a `<<TAG` operator until a line consisting
# solely of `TAG`. Here-doc consumption begins when a newline is encountered.
# It is legal for multiple here-doc `<<TAG` operators to be present on a single
# line, in which case their bodies must be present one following the next, and
# are consumed in the (left-to-right) order the `<<TAG` operators appear on the
# line. A special complication is that the bodies of all here-docs must be
# consumed when the newline is encountered even if the parse context depth has
# changed. For instance, in `cat <<A && x=$(cat <<B &&\n`, bodies of here-docs
# "A" and "B" must be consumed even though "A" was introduced outside the
# recursive parse context in which "B" was introduced and in which the newline
# is encountered.
package Lexer;

sub new {
my ($class, $parser, $s) = @_;
bless {
parser => $parser,
buff => $s,
lineno => 1,
heretags => []
} => $class;
}

sub scan_heredoc_tag {
my $self = shift @_;
${$self->{buff}} =~ /\G(-?)/gc;
my $indented = $1;
my $token = $self->scan_token();
return "<<$indented" unless $token;
my $tag = $token->[0];
$tag =~ s/['"\\]//g;
$$token[0] = $indented ? "\t$tag" : "$tag";
push(@{$self->{heretags}}, $token);
return "<<$indented$tag";
}

sub scan_op {
my ($self, $c) = @_;
my $b = $self->{buff};
return $c unless $$b =~ /\G(.)/sgc;
my $cc = $c . $1;
return scan_heredoc_tag($self) if $cc eq '<<';
return $cc if $cc =~ /^(?:&&|\|\||>>|;;|<&|>&|<>|>\|)$/;
pos($$b)--;
return $c;
}

sub scan_sqstring {
my $self = shift @_;
${$self->{buff}} =~ /\G([^']*'|.*\z)/sgc;
my $s = $1;
$self->{lineno} += () = $s =~ /\n/sg;
return "'" . $s;
}

sub scan_dqstring {
my $self = shift @_;
my $b = $self->{buff};
my $s = '"';
while (1) {
# slurp up non-special characters
$s .= $1 if $$b =~ /\G([^"\$\\]+)/gc;
# handle special characters
last unless $$b =~ /\G(.)/sgc;
my $c = $1;
$s .= '"', last if $c eq '"';
$s .= '$' . $self->scan_dollar(), next if $c eq '$';
if ($c eq '\\') {
$s .= '\\', last unless $$b =~ /\G(.)/sgc;
$c = $1;
$self->{lineno}++, next if $c eq "\n"; # line splice
# backslash escapes only $, `, ", \ in dq-string
$s .= '\\' unless $c =~ /^[\$`"\\]$/;
$s .= $c;
next;
}
die("internal error scanning dq-string '$c'\n");
}
$self->{lineno} += () = $s =~ /\n/sg;
return $s;
}

sub scan_balanced {
my ($self, $c1, $c2) = @_;
my $b = $self->{buff};
my $depth = 1;
my $s = $c1;
while ($$b =~ /\G([^\Q$c1$c2\E]*(?:[\Q$c1$c2\E]|\z))/gc) {
$s .= $1;
$depth++, next if $s =~ /\Q$c1\E$/;
$depth--;
last if $depth == 0;
}
$self->{lineno} += () = $s =~ /\n/sg;
return $s;
}

sub scan_subst {
my $self = shift @_;
my @tokens = $self->{parser}->parse(qr/^\)$/);
$self->{parser}->next_token(); # closing ")"
return @tokens;
}

sub scan_dollar {
my $self = shift @_;
my $b = $self->{buff};
return $self->scan_balanced('(', ')') if $$b =~ /\G\((?=\()/gc; # $((...))
return '(' . join(' ', map {$_->[0]} $self->scan_subst()) . ')' if $$b =~ /\G\(/gc; # $(...)
return $self->scan_balanced('{', '}') if $$b =~ /\G\{/gc; # ${...}
return $1 if $$b =~ /\G(\w+)/gc; # $var
return $1 if $$b =~ /\G([@*#?$!0-9-])/gc; # $*, $1, $$, etc.
return '';
}

sub swallow_heredocs {
my $self = shift @_;
my $b = $self->{buff};
my $tags = $self->{heretags};
while (my $tag = shift @$tags) {
my $start = pos($$b);
my $indent = $$tag[0] =~ s/^\t// ? '\\s*' : '';
$$b =~ /(?:\G|\n)$indent\Q$$tag[0]\E(?:\n|\z)/gc;
if (pos($$b) > $start) {
my $body = substr($$b, $start, pos($$b) - $start);
$self->{parser}->{heredocs}->{$$tag[0]} = {
content => substr($body, 0, length($body) - length($&)),
start_line => $self->{lineno},
};
$self->{lineno} += () = $body =~ /\n/sg;
next;
}
push(@{$self->{parser}->{problems}}, ['HEREDOC', $tag]);
$$b =~ /(?:\G|\n).*\z/gc; # consume rest of input
my $body = substr($$b, $start, pos($$b) - $start);
$self->{lineno} += () = $body =~ /\n/sg;
last;
}
}

sub scan_token {
my $self = shift @_;
my $b = $self->{buff};
my $token = '';
my ($start, $startln);
RESTART:
$startln = $self->{lineno};
$$b =~ /\G[ \t]+/gc; # skip whitespace (but not newline)
$start = pos($$b) || 0;
$self->{lineno}++, return ["\n", $start, pos($$b), $startln, $startln] if $$b =~ /\G#[^\n]*(?:\n|\z)/gc; # comment
while (1) {
# slurp up non-special characters
$token .= $1 if $$b =~ /\G([^\\;&|<>(){}'"\$\s]+)/gc;
# handle special characters
last unless $$b =~ /\G(.)/sgc;
my $c = $1;
pos($$b)--, last if $c =~ /^[ \t]$/; # whitespace ends token
pos($$b)--, last if length($token) && $c =~ /^[;&|<>(){}\n]$/;
$token .= $self->scan_sqstring(), next if $c eq "'";
$token .= $self->scan_dqstring(), next if $c eq '"';
$token .= $c . $self->scan_dollar(), next if $c eq '$';
$self->{lineno}++, $self->swallow_heredocs(), $token = $c, last if $c eq "\n";
$token = $self->scan_op($c), last if $c =~ /^[;&|<>]$/;
$token = $c, last if $c =~ /^[(){}]$/;
if ($c eq '\\') {
$token .= '\\', last unless $$b =~ /\G(.)/sgc;
$c = $1;
$self->{lineno}++, next if $c eq "\n" && length($token); # line splice
$self->{lineno}++, goto RESTART if $c eq "\n"; # line splice
$token .= '\\' . $c;
next;
}
die("internal error scanning character '$c'\n");
}
return length($token) ? [$token, $start, pos($$b), $startln, $self->{lineno}] : undef;
}

# ShellParser parses POSIX shell scripts (with minor extensions for Bash). It
# is a recursive descent parser very roughly modeled after section 2.10 "Shell
# Grammar" of POSIX chapter 2 "Shell Command Language".
package ShellParser;

sub new {
my ($class, $s) = @_;
my $self = bless {
buff => [],
stop => [],
output => [],
heredocs => {},
insubshell => 0,
} => $class;
$self->{lexer} = Lexer->new($self, $s);
return $self;
}

sub next_token {
my $self = shift @_;
return pop(@{$self->{buff}}) if @{$self->{buff}};
return $self->{lexer}->scan_token();
}

sub untoken {
my $self = shift @_;
push(@{$self->{buff}}, @_);
}

sub peek {
my $self = shift @_;
my $token = $self->next_token();
return undef unless defined($token);
$self->untoken($token);
return $token;
}

sub stop_at {
my ($self, $token) = @_;
return 1 unless defined($token);
my $stop = ${$self->{stop}}[-1] if @{$self->{stop}};
return defined($stop) && $token->[0] =~ $stop;
}

sub expect {
my ($self, $expect) = @_;
my $token = $self->next_token();
return $token if defined($token) && $token->[0] eq $expect;
push(@{$self->{output}}, "?!ERR?! expected '$expect' but found '" . (defined($token) ? $token->[0] : "<end-of-input>") . "'\n");
$self->untoken($token) if defined($token);
return ();
}

sub optional_newlines {
my $self = shift @_;
my @tokens;
while (my $token = $self->peek()) {
last unless $token->[0] eq "\n";
push(@tokens, $self->next_token());
}
return @tokens;
}

sub parse_group {
my $self = shift @_;
return ($self->parse(qr/^}$/),
$self->expect('}'));
}

sub parse_subshell {
my $self = shift @_;
$self->{insubshell}++;
my @tokens = ($self->parse(qr/^\)$/),
$self->expect(')'));
$self->{insubshell}--;
return @tokens;
}

sub parse_case_pattern {
my $self = shift @_;
my @tokens;
while (defined(my $token = $self->next_token())) {
push(@tokens, $token);
last if $token->[0] eq ')';
}
return @tokens;
}

sub parse_case {
my $self = shift @_;
my @tokens;
push(@tokens,
$self->next_token(), # subject
$self->optional_newlines(),
$self->expect('in'),
$self->optional_newlines());
while (1) {
my $token = $self->peek();
last unless defined($token) && $token->[0] ne 'esac';
push(@tokens,
$self->parse_case_pattern(),
$self->optional_newlines(),
$self->parse(qr/^(?:;;|esac)$/)); # item body
$token = $self->peek();
last unless defined($token) && $token->[0] ne 'esac';
push(@tokens,
$self->expect(';;'),
$self->optional_newlines());
}
push(@tokens, $self->expect('esac'));
return @tokens;
}

sub parse_for {
my $self = shift @_;
my @tokens;
push(@tokens,
$self->next_token(), # variable
$self->optional_newlines());
my $token = $self->peek();
if (defined($token) && $token->[0] eq 'in') {
push(@tokens,
$self->expect('in'),
$self->optional_newlines());
}
push(@tokens,
$self->parse(qr/^do$/), # items
$self->expect('do'),
$self->optional_newlines(),
$self->parse_loop_body(),
$self->expect('done'));
return @tokens;
}

sub parse_if {
my $self = shift @_;
my @tokens;
while (1) {
push(@tokens,
$self->parse(qr/^then$/), # if/elif condition
$self->expect('then'),
$self->optional_newlines(),
$self->parse(qr/^(?:elif|else|fi)$/)); # if/elif body
my $token = $self->peek();
last unless defined($token) && $token->[0] eq 'elif';
push(@tokens, $self->expect('elif'));
}
my $token = $self->peek();
if (defined($token) && $token->[0] eq 'else') {
push(@tokens,
$self->expect('else'),
$self->optional_newlines(),
$self->parse(qr/^fi$/)); # else body
}
push(@tokens, $self->expect('fi'));
return @tokens;
}

sub parse_loop_body {
my $self = shift @_;
return $self->parse(qr/^done$/);
}

sub parse_loop {
my $self = shift @_;
return ($self->parse(qr/^do$/), # condition
$self->expect('do'),
$self->optional_newlines(),
$self->parse_loop_body(),
$self->expect('done'));
}

sub parse_func {
my $self = shift @_;
return ($self->expect('('),
$self->expect(')'),
$self->optional_newlines(),
$self->parse_cmd()); # body
}

sub parse_bash_array_assignment {
my $self = shift @_;
my @tokens = $self->expect('(');
while (defined(my $token = $self->next_token())) {
push(@tokens, $token);
last if $token->[0] eq ')';
}
return @tokens;
}

my %compound = (
'{' => \&parse_group,
'(' => \&parse_subshell,
'case' => \&parse_case,
'for' => \&parse_for,
'if' => \&parse_if,
'until' => \&parse_loop,
'while' => \&parse_loop);

sub parse_cmd {
my $self = shift @_;
my $cmd = $self->next_token();
return () unless defined($cmd);
return $cmd if $cmd->[0] eq "\n";

my $token;
my @tokens = $cmd;
if ($cmd->[0] eq '!') {
push(@tokens, $self->parse_cmd());
return @tokens;
} elsif (my $f = $compound{$cmd->[0]}) {
push(@tokens, $self->$f());
} elsif (defined($token = $self->peek()) && $token->[0] eq '(') {
if ($cmd->[0] !~ /\w=$/) {
push(@tokens, $self->parse_func());
return @tokens;
}
my @array = $self->parse_bash_array_assignment();
$tokens[-1]->[0] .= join(' ', map {$_->[0]} @array);
$tokens[-1]->[2] = $array[$#array][2] if @array;
}

while (defined(my $token = $self->next_token())) {
$self->untoken($token), last if $self->stop_at($token);
push(@tokens, $token);
last if $token->[0] =~ /^(?:[;&\n|]|&&|\|\|)$/;
}
push(@tokens, $self->next_token()) if $tokens[-1]->[0] ne "\n" && defined($token = $self->peek()) && $token->[0] eq "\n";
return @tokens;
}

sub accumulate {
my ($self, $tokens, $cmd) = @_;
push(@$tokens, @$cmd);
}

sub parse {
my ($self, $stop) = @_;
push(@{$self->{stop}}, $stop);
goto DONE if $self->stop_at($self->peek());
my @tokens;
while (my @cmd = $self->parse_cmd()) {
$self->accumulate(\@tokens, \@cmd);
last if $self->stop_at($self->peek());
}
DONE:
pop(@{$self->{stop}});
return @tokens;
}
use File::Basename;
do(dirname($0) . "/lib-shell-parser.pl")
or die "$0: failed to load lib-shell-parser.pl: $@$!\n";

# TestParser is a subclass of ShellParser which, beyond parsing shell script
# code, is also imbued with semantic knowledge of test construction, and checks
@ -482,9 +33,10 @@ DONE:
# the tests themselves or in behaviors being exercised by the tests. As such,
# TestParser is only called upon to parse test bodies, not the top-level
# scripts in which the tests are defined.

package TestParser;

use base 'ShellParser';
our @ISA = ('ShellParser');

sub new {
my $class = shift @_;
@ -578,51 +130,10 @@ DONE:
$self->SUPER::accumulate($tokens, $cmd);
}

# ScriptParser is a subclass of ShellParser which identifies individual test
# definitions within test scripts, and passes each test body through TestParser
# to identify possible problems. ShellParser detects test definitions not only
# at the top-level of test scripts but also within compound commands such as
# loops and function definitions.
package ScriptParser;
# ChainlintParser extends ScriptParser with &&-chain checking
package ChainlintParser;

use base 'ShellParser';

sub new {
my $class = shift @_;
my $self = $class->SUPER::new(@_);
$self->{ntests} = 0;
$self->{nerrs} = 0;
return $self;
}

# extract the raw content of a token, which may be a single string or a
# composition of multiple strings and non-string character runs; for instance,
# `"test body"` unwraps to `test body`; `word"a b"42'c d'` to `worda b42c d`
sub unwrap {
my $token = (@_ ? shift @_ : $_)->[0];
# simple case: 'sqstring' or "dqstring"
return $token if $token =~ s/^'([^']*)'$/$1/;
return $token if $token =~ s/^"([^"]*)"$/$1/;

# composite case
my ($s, $q, $escaped);
while (1) {
# slurp up non-special characters
$s .= $1 if $token =~ /\G([^\\'"]*)/gc;
# handle special characters
last unless $token =~ /\G(.)/sgc;
my $c = $1;
$q = undef, next if defined($q) && $c eq $q;
$q = $c, next if !defined($q) && $c =~ /^['"]$/;
if ($c eq '\\') {
last unless $token =~ /\G(.)/sgc;
$c = $1;
$s .= '\\' if $c eq "\n"; # preserve line splice
}
$s .= $c;
}
return $s
}
our @ISA = ('ScriptParser');

sub format_problem {
local $_ = shift;
@ -635,10 +146,10 @@ sub format_problem {

sub check_test {
my $self = shift @_;
my $title = unwrap(shift @_);
my $title = ScriptParser::unwrap(shift @_);
my $body = shift @_;
my $lineno = $body->[3];
$body = unwrap($body);
$body = ScriptParser::unwrap($body);
if ($body eq '-') {
my $herebody = shift @_;
$body = $herebody->{content};
@ -673,24 +184,8 @@ sub check_test {
push(@{$self->{output}}, "$c->{blue}# chainlint: $title$c->{reset}\n$checked");
}

sub parse_cmd {
my $self = shift @_;
my @tokens = $self->SUPER::parse_cmd();
return @tokens unless @tokens && $tokens[0]->[0] =~ /^test_expect_(?:success|failure)$/;
my $n = $#tokens;
$n-- while $n >= 0 && $tokens[$n]->[0] =~ /^(?:[;&\n|]|&&|\|\|)$/;
my $herebody;
if ($n >= 2 && $tokens[$n-1]->[0] eq '-' && $tokens[$n]->[0] =~ /^<<-?(.+)$/) {
$herebody = $self->{heredocs}->{$1};
$n--;
}
$self->check_test($tokens[1], $tokens[2], $herebody) if $n == 2; # title body
$self->check_test($tokens[2], $tokens[3], $herebody) if $n > 2; # prereq title body
return @tokens;
}

# main contains high-level functionality for processing command-line switches,
# feeding input test scripts to ScriptParser, and reporting results.
# feeding input test scripts to ChainlintParser, and reporting results.
package main;

my $getnow = sub { return time(); };
@ -803,7 +298,7 @@ sub check_script {
}
my $s = do { local $/; <$fh> };
close($fh);
my $parser = ScriptParser->new(\$s);
my $parser = ChainlintParser->new(\$s);
1 while $parser->parse_cmd();
if (@{$parser->{output}}) {
my $c = fd_colors(1);

View File

@ -523,7 +523,7 @@ test_expect_success 'Verify descending sort' '

test_expect_success 'Give help even with invalid sort atoms' '
${git_for_each_ref} --sort=bogus -h >actual 2>&1 &&
grep "^usage: ${git_for_each_ref}" actual
test_grep "^usage: ${git_for_each_ref}" actual
'

cat >expected <<\EOF
@ -622,7 +622,7 @@ test_expect_success 'Quoting style: tcl' '
for i in "--perl --shell" "-s --python" "--python --tcl" "--tcl --perl"; do
test_expect_success "more than one quoting style: $i" "
test_must_fail ${git_for_each_ref} $i 2>err &&
grep '^error: more than one quoting style' err
test_grep '^error: more than one quoting style' err
"
done

@ -1892,7 +1892,7 @@ test_expect_success "${git_for_each_ref} --stdin: fails if extra args" '
>in &&
test_must_fail ${git_for_each_ref} --format="%(refname)" \
--stdin refs/heads/extra <in 2>err &&
grep "unknown arguments supplied with --stdin" err
test_grep "unknown arguments supplied with --stdin" err
'

test_expect_success "${git_for_each_ref} --stdin: matches" '
@ -1955,11 +1955,11 @@ test_expect_success "${git_for_each_ref} with nested tags" '

test_expect_success 'is-base atom with non-commits' '
${git_for_each_ref} --format="%(is-base:HEAD) %(refname)" >out 2>err &&
grep "(HEAD) refs/heads/main" out &&
test_grep "(HEAD) refs/heads/main" out &&

test_line_count = 2 err &&
grep "error: object .* is a commit, not a blob" err &&
grep "error: bad tag pointer to" err
test_grep "error: object .* is a commit, not a blob" err &&
test_grep "error: bad tag pointer to" err
'

GRADE_FORMAT="%(signature:grade)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"

27
t/greplint-cat.pl Normal file
View File

@ -0,0 +1,27 @@
#!/usr/bin/env perl

use strict;
use warnings;

# Assemble expected output for check-greplint target.
# Usage: greplint-cat.pl <outdir> <test-name> ...
#
# For each <test-name>, reads greplint/<test-name>.expect and
# prepends "greplint/<test-name>.test:" to every non-empty line,
# matching the output format of greplint.pl. Writes combined
# expected output to <outdir>/expect.

my $outdir = shift;
open(my $expect, '>', "$outdir/expect")
or die "unable to open $outdir/expect: $!";

for my $name (@ARGV) {
open(my $fh, '<', "greplint/$name.expect")
or die "unable to open greplint/$name.expect: $!";
while (<$fh>) {
print $expect "greplint/$name.test:$_";
}
close $fh;
}

close $expect;

258
t/greplint.pl Normal file
View File

@ -0,0 +1,258 @@
#!/usr/bin/env perl

# Detect bare 'grep' used as a test assertion where 'test_grep'
# should be used, and '! test_grep' where 'test_grep !' should
# be used.
#
# The shared shell parser tokenizes test bodies so that 'grep'
# inside heredocs, command substitutions like $(grep ...), and
# quoted strings is collapsed into a single token and never seen
# by our check. A line-oriented approach would need to track
# heredoc delimiters, nested $() depth, and cross-line pipe
# state to avoid false positives on patterns like:
#
# write_script foo.sh <<-\EOF
# grep pattern file # data, not an assertion
# EOF
#
# The Lexer already handles these.

use warnings;
use strict;
use File::Basename;
do(dirname($0) . "/lib-shell-parser.pl")
or die "$0: failed to load lib-shell-parser.pl: $@$!\n";

my $exit_code = 0;

# GrepLintParser inherits ScriptParser's ability to find
# test_expect_success/failure blocks and call check_test()
# on each body. We override check_test() to walk the token
# stream looking for bare grep assertions.
package GrepLintParser;

our @ISA = ('ScriptParser');

# After these tokens, the next token is a command word.
# For example, in 'echo foo && grep bar file', the 'grep'
# after '&&' is at command position and should be flagged.
my %cmd_start = map { $_ => 1 } qw(&& || ; ;; do then else elif), "\n", '{', '(';

# Tokens indicating grep's output is piped or redirected.
my %filter_op = map { $_ => 1 } qw(| > >> <);

# A token is at "command word" position if the shell would
# interpret it as a program name rather than an argument.
# Only 'grep' at command position is an assertion we should
# flag; 'grep' as an argument ('test_must_fail grep') or
# value ('for cmd in grep sed') is not.
sub is_command_word {
my ($tokens, $pos) = @_;
return 1 if $pos == 0;
for (my $j = $pos - 1; $j >= 0; $j--) {
my $t = $tokens->[$j]->[0];
# After a separator or pipe, a new command starts.
return 1 if $cmd_start{$t} || $t eq '|';
# After '}' or ')', what follows is a separator or
# redirect on the compound command, not a new command.
return 0 if $t eq '}' || $t eq ')';
# '!' is a prefix that does not consume command
# position; keep scanning to find what precedes it.
next if $t eq '!';
# Any other word means we are past the command word.
return 0;
}
return 1;
}

# lint_ok() reports whether a bare grep carries a trailing
# '# lint-ok' comment telling this linter to skip it.
#
# In practice this is needed for just one case: a grep acting
# as a data filter whose output is consumed by a redirect or
# pipe on an enclosing compound command (such as a subshell or
# brace group) rather than by grep's own pipeline, e.g.
#
# ( grep ... && # lint-ok
# sed ... ) >out
#
# { grep ... || : # lint-ok
# } >out
#
# is_filter() only scans grep's own pipeline: it stops at the
# separator before the compound command closes and never sees
# the outer redirect, so it would flag such a grep as an
# assertion. A grep that really is an assertion is better
# written as test_grep (or a guarded test_grep when the file's
# presence is conditional) than annotated with lint-ok.
sub lint_ok {
my ($raw_lines, $ln) = @_;
if ($ln < 1 || $ln > @$raw_lines) {
warn "lint_ok: line number $ln out of range (1.." .
scalar(@$raw_lines) . ")\n";
return 0;
}
return $raw_lines->[$ln - 1] =~ /lint-ok/;
}

# Grep is a filter (not an assertion) if it receives piped
# input or sends its output to a pipe or redirect. Check
# both directions from grep's position in the token stream.
sub is_filter {
my ($tokens, $pos) = @_;
# Backward: is grep receiving piped input?
# Newlines don't break pipes ('cmd |\n grep' is one
# pipeline), so skip past them.
for (my $j = $pos - 1; $j >= 0; $j--) {
my $t = $tokens->[$j]->[0];
return 1 if $t eq '|';
next if $t eq "\n";
last if $cmd_start{$t} || $t eq '}' || $t eq ')';
}
# Forward: is grep piping or redirecting output?
# Unlike the backward scan, we do not skip newlines here:
# a bare newline is a command boundary, and redirects or
# pipes must appear on the same line as grep (or after a
# line continuation, which the Lexer consumes).
for (my $j = $pos + 1; $j < @$tokens; $j++) {
my $t = $tokens->[$j]->[0];
return 0 if $cmd_start{$t};
return 1 if $filter_op{$t};
}
return 0;
}

# Map a body-relative line number to a file line number.
# For double-quoted bodies, backslash-continuation lines
# (\<newline>) are consumed by the Lexer without appearing
# in the body text, so the inner parser sees fewer lines
# than the source file has. We walk the source lines to
# count continuations and adjust accordingly.
sub body_to_file_line {
my ($body_lineno, $body_token, $raw_lines, $body_start) = @_;
my $body_text = $body_token->[0];
my $body_end_line = $body_token->[4];
unless ($body_start && $body_start >= 1) {
warn "body_start is not a positive integer\n";
return $body_lineno;
}
my $file_lineno = $body_lineno + $body_start - 1;
# Only double-quoted bodies have line splices.
return $file_lineno unless $body_text =~ /^"/;
my $adj = 0;
my $lines_seen = 0;
unless ($body_end_line && $body_end_line >= $body_start) {
warn "body_end_line is not set for double-quoted body\n";
return $file_lineno;
}
my $end = $body_end_line;
if ($end > @$raw_lines) {
warn "body_end_line ($end) exceeds file length (" .
scalar(@$raw_lines) . ")\n";
return $file_lineno;
}
my $src_ln = $body_start;
while ($src_ln <= $end && $lines_seen < $body_lineno) {
my $line = $raw_lines->[$src_ln - 1];
# Odd trailing backslashes = continuation (\<nl>).
# Even = escaped backslashes (\\), not a continuation.
if ($line =~ /(\\*)$/ && length($1) % 2 == 1) {
$adj++;
} else {
$lines_seen++;
}
$src_ln++;
}
if ($lines_seen < $body_lineno) {
warn "body_lineno ($body_lineno) not found within body range " .
"($body_start..$end)\n";
}
return $file_lineno + $adj;
}

# ScriptParser calls this for each test body found in the script.
sub check_test {
my $self = shift @_;
my $title = ScriptParser::unwrap(shift @_);
my $body_token = shift @_;
my $body_start = $body_token->[3];
my $body = ScriptParser::unwrap($body_token);
# Handle heredoc-style test bodies:
# test_expect_success 'title' - <<\EOF
# grep pattern file
# EOF
# The '-' signals that the body follows as a heredoc.
if ($body eq '-') {
my $herebody = shift @_;
if ($herebody) {
$body = $herebody->{content};
$body_start = $herebody->{start_line};
}
}
return unless $body;

my $raw_lines = $self->{raw_lines};

# The outer parser gives us the body as an opaque string.
# Parse it to get individual tokens with command boundaries.
my $parser = ShellParser->new(\$body);
my @tokens = $parser->parse();

my $file = $self->{file};

for (my $i = 0; $i < @tokens; $i++) {
my $text = $tokens[$i]->[0];
next unless is_command_word(\@tokens, $i);

my $token_lineno = $tokens[$i]->[3];
unless (defined($token_lineno) && $token_lineno >= 1) {
warn "token has no line number\n";
next;
}
my $file_lineno = body_to_file_line(
$token_lineno,
$body_token, $raw_lines, $body_start);

# '!' negates the exit code without consuming command
# position. '! test_grep' is an anti-pattern because
# test_grep only prints diagnostics on grep failure,
# and '!' inverts after that decision is already made.
if ($text eq '!') {
if ($i + 1 < @tokens &&
$tokens[$i + 1]->[0] eq 'test_grep' &&
!lint_ok($raw_lines, $file_lineno)) {
print "$file:$file_lineno: error: ",
'use "test_grep !" instead of ',
'"! test_grep"', "\n";
$exit_code = 1;
}
next;
}

# Bare grep as a command (not a filter) is a test
# assertion that should use test_grep for better
# failure diagnostics.
if ($text eq 'grep' &&
!is_filter(\@tokens, $i) &&
!lint_ok($raw_lines, $file_lineno)) {
print "$file:$file_lineno: error: ",
"bare grep outside pipeline ",
"(use test_grep)\n";
$exit_code = 1;
}
}
}

package main;

for my $file (@ARGV) {
open(my $fh, '<:unix:crlf', $file) or die "$0: $file: $!\n";
my @raw_lines = <$fh>;
close $fh;
my $s = join('', @raw_lines);
my $parser = GrepLintParser->new(\$s);
$parser->{file} = $file;
$parser->{raw_lines} = \@raw_lines;
$parser->parse();
}
exit $exit_code;

View File

@ -0,0 +1 @@
3: error: bare grep outside pipeline (use test_grep)

View File

@ -0,0 +1,4 @@
test_expect_success 'grep after && is flagged' '
cmd &&
grep pattern file
'

View File

@ -0,0 +1 @@
3: error: bare grep outside pipeline (use test_grep)

View File

@ -0,0 +1,4 @@
test_expect_success 'grep after semicolon is flagged' '
echo hello;
grep pattern file
'

View File

@ -0,0 +1,3 @@
4: error: bare grep outside pipeline (use test_grep)
8: error: bare grep outside pipeline (use test_grep)
15: error: bare grep outside pipeline (use test_grep)

View File

@ -0,0 +1,17 @@
test_expect_success 'grep after then/do/else is flagged' '
if true
then
grep pattern file
fi &&
while true
do
grep pattern file &&
break
done &&
if true
then
echo yes
else
grep pattern file
fi
'

View File

@ -0,0 +1 @@
2: error: bare grep outside pipeline (use test_grep)

View File

@ -0,0 +1,3 @@
test_expect_success 'grep -c is flagged (not special-cased)' '
grep -c pattern file
'

View File

@ -0,0 +1 @@
2: error: bare grep outside pipeline (use test_grep)

View File

@ -0,0 +1,3 @@
test_expect_success 'grep -e is flagged' '
grep -e pattern file
'

View File

@ -0,0 +1 @@
2: error: bare grep outside pipeline (use test_grep)

View File

@ -0,0 +1,3 @@
test_expect_success 'grep -E is flagged' '
grep -E "pat+ern" file
'

View File

View File

@ -0,0 +1,4 @@
test_expect_success 'grep with lint-ok annotation is not flagged' '
grep pattern file && # lint-ok
echo done
'

View File

@ -0,0 +1 @@
2: error: bare grep outside pipeline (use test_grep)

View File

@ -0,0 +1,3 @@
test_expect_success 'negated grep is flagged' '
! grep pattern file
'

View File

@ -0,0 +1 @@
2: error: bare grep outside pipeline (use test_grep)

View File

@ -0,0 +1,3 @@
test_expect_success 'grep -f is flagged' '
grep -f patterns.txt file
'

View File

@ -0,0 +1 @@
2: error: bare grep outside pipeline (use test_grep)

View File

@ -0,0 +1,3 @@
test_expect_success 'bare grep is flagged' '
grep pattern file
'

View File

@ -0,0 +1 @@
3: error: bare grep outside pipeline (use test_grep)

View File

@ -0,0 +1,5 @@
test_expect_success 'grep in subshell is flagged' '
(
grep pattern file
)
'

View File

@ -0,0 +1 @@
10: error: bare grep outside pipeline (use test_grep)

View File

@ -0,0 +1,11 @@
# Double-quoted test bodies with backslash-continuation lines:
# the splice adjustment in check_test compensates for \<newline>
# lines that the lexer consumes without emitting into the body
# text, so the reported line number matches the source.
test_expect_success 'dqstring continuation offset' "
x=\$(echo \
hello) &&
y=\$(echo \
world) &&
grep pattern file
"

View File

@ -0,0 +1,3 @@
test_expect_success 'grep in command substitution is not flagged' '
x=$(grep pattern file)
'

View File

View File

@ -0,0 +1,3 @@
test_expect_success 'grep receiving pipe input is not flagged' '
cmd | grep pattern
'

View File

View File

@ -0,0 +1,3 @@
test_expect_success 'grep piping to another command is not flagged' '
grep pattern file | wc -l
'

View File

View File

@ -0,0 +1,3 @@
test_expect_success 'grep with output redirect is not flagged' '
grep pattern file >output
'

View File

View File

@ -0,0 +1,3 @@
test_expect_success 'grep reading from stdin redirect is not flagged' '
grep pattern <input
'

View File

View File

@ -0,0 +1,3 @@
test_expect_success 'grep as argument to another command is not flagged' '
test_must_fail grep pattern file
'

View File

View File

@ -0,0 +1,6 @@
test_expect_success 'grep as value in for-loop is not flagged' '
for cmd in grep sed awk
do
echo $cmd
done
'

View File

@ -0,0 +1 @@
2: error: use "test_grep !" instead of "! test_grep"

View File

@ -0,0 +1,3 @@
test_expect_success 'wrong negation of test_grep is flagged' '
! test_grep pattern file
'

View File

@ -173,7 +173,7 @@ rev_list_tests_head () {

test_expect_success "bitmap --objects handles non-commit objects ($state, $branch)" '
git rev-list --objects --use-bitmap-index $branch tagged-blob >actual &&
grep $blob actual
test_grep $blob actual
'
}

@ -242,16 +242,16 @@ basic_bitmap_tests () {
GIT_PROGRESS_DELAY=0 \
git pack-objects --all --stdout --progress \
</dev/null >/dev/null 2>stderr &&
grep "Enumerating objects: $count, done" stderr &&
grep "pack-reused $count" stderr &&
test_grep "Enumerating objects: $count, done" stderr &&
test_grep "pack-reused $count" stderr &&

# now the same but with one non-reused object
git commit --allow-empty -m "an extra commit object" &&
GIT_PROGRESS_DELAY=0 \
git pack-objects --all --stdout --progress \
</dev/null >/dev/null 2>stderr &&
grep "Enumerating objects: $((count+1)), done" stderr &&
grep "pack-reused $count" stderr
test_grep "Enumerating objects: $((count+1)), done" stderr &&
test_grep "pack-reused $count" stderr
'
}

@ -283,7 +283,7 @@ test_rev_exists () {
then
test_path_is_file $midx-$(midx_checksum $objdir).rev
fi &&
grep "\"category\":\"load_midx_revindex\",\"key\":\"source\",\"value\":\"$kind\"" event.trace
test_grep "\"category\":\"load_midx_revindex\",\"key\":\"source\",\"value\":\"$kind\"" event.trace
'
}


View File

@ -63,9 +63,9 @@ test_expect_success "connect with $BUNDLE_URI_PROTOCOL:// using protocol v2: no
>actual 2>err &&

# Server responded using protocol v2
grep "< version 2" log &&
test_grep "< version 2" log &&

! grep bundle-uri log
test_grep ! bundle-uri log
'

test_expect_success "connect with $BUNDLE_URI_PROTOCOL:// using protocol v2: have bundle-uri" '
@ -78,10 +78,10 @@ test_expect_success "connect with $BUNDLE_URI_PROTOCOL:// using protocol v2: hav
>actual 2>err &&

# Server responded using protocol v2
grep "< version 2" log &&
test_grep "< version 2" log &&

# Server advertised bundle-uri capability
grep "< bundle-uri" log
test_grep "< bundle-uri" log
'

test_expect_success "clone with $BUNDLE_URI_PROTOCOL:// using protocol v2: request bundle-uris" '
@ -95,13 +95,13 @@ test_expect_success "clone with $BUNDLE_URI_PROTOCOL:// using protocol v2: reque
>actual 2>err &&

# Server responded using protocol v2
grep "< version 2" log &&
test_grep "< version 2" log &&

# Server advertised bundle-uri capability
grep "< bundle-uri" log &&
test_grep "< bundle-uri" log &&

# Client did not issue bundle-uri command
! grep "> command=bundle-uri" log &&
test_grep ! "> command=bundle-uri" log &&

GIT_TRACE_PACKET="$PWD/log" \
git \
@ -111,13 +111,13 @@ test_expect_success "clone with $BUNDLE_URI_PROTOCOL:// using protocol v2: reque
>actual 2>err &&

# Server responded using protocol v2
grep "< version 2" log &&
test_grep "< version 2" log &&

# Server advertised bundle-uri capability
grep "< bundle-uri" log &&
test_grep "< bundle-uri" log &&

# Client issued bundle-uri command
grep "> command=bundle-uri" log &&
test_grep "> command=bundle-uri" log &&

GIT_TRACE_PACKET="$PWD/log3" \
git \
@ -128,13 +128,13 @@ test_expect_success "clone with $BUNDLE_URI_PROTOCOL:// using protocol v2: reque
>actual 2>err &&

# Server responded using protocol v2
grep "< version 2" log3 &&
test_grep "< version 2" log3 &&

# Server advertised bundle-uri capability
grep "< bundle-uri" log3 &&
test_grep "< bundle-uri" log3 &&

# Client did not issue bundle-uri command (--bundle-uri override)
! grep "> command=bundle-uri" log3
test_grep ! "> command=bundle-uri" log3
'

# The remaining tests will all assume transfer.bundleURI=true

View File

@ -275,7 +275,7 @@ test_http_push_nonff () {
'

test_expect_success 'non-fast-forward push show ref status' '
grep "^ ! \[rejected\][ ]*$BRANCH -> $BRANCH (non-fast-forward)$" output
test_grep "^ ! \[rejected\][ ]*$BRANCH -> $BRANCH (non-fast-forward)$" output
'

test_expect_success 'non-fast-forward push shows help message' '

534
t/lib-shell-parser.pl Normal file
View File

@ -0,0 +1,534 @@
# Copyright (c) 2021-2022 Eric Sunshine <sunshine@sunshineco.com>
#
# Shared shell script parser for test lint tools. Provides Lexer,
# ShellParser, and ScriptParser. Subclass ScriptParser and override
# check_test() to implement lint checks.

use strict;
use warnings;

# Lexer tokenizes POSIX shell scripts. It is roughly modeled after section 2.3
# "Token Recognition" of POSIX chapter 2 "Shell Command Language". Although
# similar to lexical analyzers for other languages, this one differs in a few
# substantial ways due to quirks of the shell command language.
#
# For instance, in many languages, newline is just whitespace like space or
# TAB, but in shell a newline is a command separator, thus a distinct lexical
# token. A newline is significant and returned as a distinct token even at the
# end of a shell comment.
#
# In other languages, `1+2` would typically be scanned as three tokens
# (`1`, `+`, and `2`), but in shell it is a single token. However, the similar
# `1 + 2`, which embeds whitepaces, is scanned as three token in shell, as well.
# In shell, several characters with special meaning lose that meaning when not
# surrounded by whitespace. For instance, the negation operator `!` is special
# when standing alone surrounded by whitespace; whereas in `foo!uucp` it is
# just a plain character in the longer token "foo!uucp". In many other
# languages, `"string"/foo:'string'` might be scanned as five tokens ("string",
# `/`, `foo`, `:`, and 'string'), but in shell, it is just a single token.
#
# The lexical analyzer for the shell command language is also somewhat unusual
# in that it recursively invokes the parser to handle the body of `$(...)`
# expressions which can contain arbitrary shell code. Such expressions may be
# encountered both inside and outside of double-quoted strings.
#
# The lexical analyzer is responsible for consuming shell here-doc bodies which
# extend from the line following a `<<TAG` operator until a line consisting
# solely of `TAG`. Here-doc consumption begins when a newline is encountered.
# It is legal for multiple here-doc `<<TAG` operators to be present on a single
# line, in which case their bodies must be present one following the next, and
# are consumed in the (left-to-right) order the `<<TAG` operators appear on the
# line. A special complication is that the bodies of all here-docs must be
# consumed when the newline is encountered even if the parse context depth has
# changed. For instance, in `cat <<A && x=$(cat <<B &&\n`, bodies of here-docs
# "A" and "B" must be consumed even though "A" was introduced outside the
# recursive parse context in which "B" was introduced and in which the newline
# is encountered.
package Lexer;

sub new {
my ($class, $parser, $s) = @_;
bless {
parser => $parser,
buff => $s,
lineno => 1,
heretags => []
} => $class;
}

sub scan_heredoc_tag {
my $self = shift @_;
${$self->{buff}} =~ /\G(-?)/gc;
my $indented = $1;
my $token = $self->scan_token();
return "<<$indented" unless $token;
my $tag = $token->[0];
$tag =~ s/['"\\]//g;
$$token[0] = $indented ? "\t$tag" : "$tag";
push(@{$self->{heretags}}, $token);
return "<<$indented$tag";
}

sub scan_op {
my ($self, $c) = @_;
my $b = $self->{buff};
return $c unless $$b =~ /\G(.)/sgc;
my $cc = $c . $1;
return scan_heredoc_tag($self) if $cc eq '<<';
return $cc if $cc =~ /^(?:&&|\|\||>>|;;|<&|>&|<>|>\|)$/;
pos($$b)--;
return $c;
}

sub scan_sqstring {
my $self = shift @_;
${$self->{buff}} =~ /\G([^']*'|.*\z)/sgc;
my $s = $1;
$self->{lineno} += () = $s =~ /\n/sg;
return "'" . $s;
}

sub scan_dqstring {
my $self = shift @_;
my $b = $self->{buff};
my $s = '"';
while (1) {
# Slurp non-special characters; count newlines here because
# newlines inside $() are already counted by the recursive parse.
if ($$b =~ /\G([^"\$\\]+)/gc) {
$s .= $1;
$self->{lineno} += $1 =~ tr/\n//;
}
# handle special characters
last unless $$b =~ /\G(.)/sgc;
my $c = $1;
$s .= '"', last if $c eq '"';
$s .= '$' . $self->scan_dollar(), next if $c eq '$';
if ($c eq '\\') {
$s .= '\\', last unless $$b =~ /\G(.)/sgc;
$c = $1;
$self->{lineno}++, next if $c eq "\n"; # line splice
# backslash escapes only $, `, ", \ in dq-string
$s .= '\\' unless $c =~ /^[\$`"\\]$/;
$s .= $c;
next;
}
die("internal error scanning dq-string '$c'\n");
}
return $s;
}

sub scan_balanced {
my ($self, $c1, $c2) = @_;
my $b = $self->{buff};
my $depth = 1;
my $s = $c1;
while ($$b =~ /\G([^\Q$c1$c2\E]*(?:[\Q$c1$c2\E]|\z))/gc) {
$s .= $1;
$depth++, next if $s =~ /\Q$c1\E$/;
$depth--;
last if $depth == 0;
}
$self->{lineno} += () = $s =~ /\n/sg;
return $s;
}

sub scan_subst {
my $self = shift @_;
my @tokens = $self->{parser}->parse(qr/^\)$/);
$self->{parser}->next_token(); # closing ")"
return @tokens;
}

sub scan_dollar {
my $self = shift @_;
my $b = $self->{buff};
return $self->scan_balanced('(', ')') if $$b =~ /\G\((?=\()/gc; # $((...))
return '(' . join(' ', map {$_->[0]} $self->scan_subst()) . ')' if $$b =~ /\G\(/gc; # $(...)
return $self->scan_balanced('{', '}') if $$b =~ /\G\{/gc; # ${...}
return $1 if $$b =~ /\G(\w+)/gc; # $var
return $1 if $$b =~ /\G([@*#?$!0-9-])/gc; # $*, $1, $$, etc.
return '';
}

sub swallow_heredocs {
my $self = shift @_;
my $b = $self->{buff};
my $tags = $self->{heretags};
while (my $tag = shift @$tags) {
my $start = pos($$b);
my $indent = $$tag[0] =~ s/^\t// ? '\\s*' : '';
$$b =~ /(?:\G|\n)$indent\Q$$tag[0]\E(?:\n|\z)/gc;
if (pos($$b) > $start) {
my $body = substr($$b, $start, pos($$b) - $start);
$self->{parser}->{heredocs}->{$$tag[0]} = {
content => substr($body, 0, length($body) - length($&)),
start_line => $self->{lineno},
};
$self->{lineno} += () = $body =~ /\n/sg;
next;
}
push(@{$self->{parser}->{problems}}, ['HEREDOC', $tag]);
$$b =~ /(?:\G|\n).*\z/gc; # consume rest of input
my $body = substr($$b, $start, pos($$b) - $start);
$self->{lineno} += () = $body =~ /\n/sg;
last;
}
}

sub scan_token {
my $self = shift @_;
my $b = $self->{buff};
my $token = '';
my ($start, $startln);
RESTART:
$startln = $self->{lineno};
$$b =~ /\G[ \t]+/gc; # skip whitespace (but not newline)
$start = pos($$b) || 0;
$self->{lineno}++, return ["\n", $start, pos($$b), $startln, $startln] if $$b =~ /\G#[^\n]*(?:\n|\z)/gc; # comment
while (1) {
# slurp up non-special characters
$token .= $1 if $$b =~ /\G([^\\;&|<>(){}'"\$\s]+)/gc;
# handle special characters
last unless $$b =~ /\G(.)/sgc;
my $c = $1;
pos($$b)--, last if $c =~ /^[ \t]$/; # whitespace ends token
pos($$b)--, last if length($token) && $c =~ /^[;&|<>(){}\n]$/;
$token .= $self->scan_sqstring(), next if $c eq "'";
$token .= $self->scan_dqstring(), next if $c eq '"';
$token .= $c . $self->scan_dollar(), next if $c eq '$';
$self->{lineno}++, $self->swallow_heredocs(), $token = $c, last if $c eq "\n";
$token = $self->scan_op($c), last if $c =~ /^[;&|<>]$/;
$token = $c, last if $c =~ /^[(){}]$/;
if ($c eq '\\') {
$token .= '\\', last unless $$b =~ /\G(.)/sgc;
$c = $1;
$self->{lineno}++, next if $c eq "\n" && length($token); # line splice
$self->{lineno}++, goto RESTART if $c eq "\n"; # line splice
$token .= '\\' . $c;
next;
}
die("internal error scanning character '$c'\n");
}
return length($token) ? [$token, $start, pos($$b), $startln, $self->{lineno}] : undef;
}

# ShellParser parses POSIX shell scripts (with minor extensions for Bash). It
# is a recursive descent parser very roughly modeled after section 2.10 "Shell
# Grammar" of POSIX chapter 2 "Shell Command Language".

package ShellParser;

sub new {
my ($class, $s) = @_;
my $self = bless {
buff => [],
stop => [],
output => [],
heredocs => {},
insubshell => 0,
} => $class;
$self->{lexer} = Lexer->new($self, $s);
return $self;
}

sub next_token {
my $self = shift @_;
return pop(@{$self->{buff}}) if @{$self->{buff}};
return $self->{lexer}->scan_token();
}

sub untoken {
my $self = shift @_;
push(@{$self->{buff}}, @_);
}

sub peek {
my $self = shift @_;
my $token = $self->next_token();
return undef unless defined($token);
$self->untoken($token);
return $token;
}

sub stop_at {
my ($self, $token) = @_;
return 1 unless defined($token);
my $stop = ${$self->{stop}}[-1] if @{$self->{stop}};
return defined($stop) && $token->[0] =~ $stop;
}

sub expect {
my ($self, $expect) = @_;
my $token = $self->next_token();
return $token if defined($token) && $token->[0] eq $expect;
push(@{$self->{output}}, "?!ERR?! expected '$expect' but found '" . (defined($token) ? $token->[0] : "<end-of-input>") . "'\n");
$self->untoken($token) if defined($token);
return ();
}

sub optional_newlines {
my $self = shift @_;
my @tokens;
while (my $token = $self->peek()) {
last unless $token->[0] eq "\n";
push(@tokens, $self->next_token());
}
return @tokens;
}

sub parse_group {
my $self = shift @_;
return ($self->parse(qr/^}$/),
$self->expect('}'));
}

sub parse_subshell {
my $self = shift @_;
$self->{insubshell}++;
my @tokens = ($self->parse(qr/^\)$/),
$self->expect(')'));
$self->{insubshell}--;
return @tokens;
}

sub parse_case_pattern {
my $self = shift @_;
my @tokens;
while (defined(my $token = $self->next_token())) {
push(@tokens, $token);
last if $token->[0] eq ')';
}
return @tokens;
}

sub parse_case {
my $self = shift @_;
my @tokens;
push(@tokens,
$self->next_token(), # subject
$self->optional_newlines(),
$self->expect('in'),
$self->optional_newlines());
while (1) {
my $token = $self->peek();
last unless defined($token) && $token->[0] ne 'esac';
push(@tokens,
$self->parse_case_pattern(),
$self->optional_newlines(),
$self->parse(qr/^(?:;;|esac)$/)); # item body
$token = $self->peek();
last unless defined($token) && $token->[0] ne 'esac';
push(@tokens,
$self->expect(';;'),
$self->optional_newlines());
}
push(@tokens, $self->expect('esac'));
return @tokens;
}

sub parse_for {
my $self = shift @_;
my @tokens;
push(@tokens,
$self->next_token(), # variable
$self->optional_newlines());
my $token = $self->peek();
if (defined($token) && $token->[0] eq 'in') {
push(@tokens,
$self->expect('in'),
$self->optional_newlines());
}
push(@tokens,
$self->parse(qr/^do$/), # items
$self->expect('do'),
$self->optional_newlines(),
$self->parse_loop_body(),
$self->expect('done'));
return @tokens;
}

sub parse_if {
my $self = shift @_;
my @tokens;
while (1) {
push(@tokens,
$self->parse(qr/^then$/), # if/elif condition
$self->expect('then'),
$self->optional_newlines(),
$self->parse(qr/^(?:elif|else|fi)$/)); # if/elif body
my $token = $self->peek();
last unless defined($token) && $token->[0] eq 'elif';
push(@tokens, $self->expect('elif'));
}
my $token = $self->peek();
if (defined($token) && $token->[0] eq 'else') {
push(@tokens,
$self->expect('else'),
$self->optional_newlines(),
$self->parse(qr/^fi$/)); # else body
}
push(@tokens, $self->expect('fi'));
return @tokens;
}

sub parse_loop_body {
my $self = shift @_;
return $self->parse(qr/^done$/);
}

sub parse_loop {
my $self = shift @_;
return ($self->parse(qr/^do$/), # condition
$self->expect('do'),
$self->optional_newlines(),
$self->parse_loop_body(),
$self->expect('done'));
}

sub parse_func {
my $self = shift @_;
return ($self->expect('('),
$self->expect(')'),
$self->optional_newlines(),
$self->parse_cmd()); # body
}

sub parse_bash_array_assignment {
my $self = shift @_;
my @tokens = $self->expect('(');
while (defined(my $token = $self->next_token())) {
push(@tokens, $token);
last if $token->[0] eq ')';
}
return @tokens;
}

my %compound = (
'{' => \&parse_group,
'(' => \&parse_subshell,
'case' => \&parse_case,
'for' => \&parse_for,
'if' => \&parse_if,
'until' => \&parse_loop,
'while' => \&parse_loop);

sub parse_cmd {
my $self = shift @_;
my $cmd = $self->next_token();
return () unless defined($cmd);
return $cmd if $cmd->[0] eq "\n";

my $token;
my @tokens = $cmd;
if ($cmd->[0] eq '!') {
push(@tokens, $self->parse_cmd());
return @tokens;
} elsif (my $f = $compound{$cmd->[0]}) {
push(@tokens, $self->$f());
} elsif (defined($token = $self->peek()) && $token->[0] eq '(') {
if ($cmd->[0] !~ /\w=$/) {
push(@tokens, $self->parse_func());
return @tokens;
}
my @array = $self->parse_bash_array_assignment();
$tokens[-1]->[0] .= join(' ', map {$_->[0]} @array);
$tokens[-1]->[2] = $array[$#array][2] if @array;
}

while (defined(my $token = $self->next_token())) {
$self->untoken($token), last if $self->stop_at($token);
push(@tokens, $token);
last if $token->[0] =~ /^(?:[;&\n|]|&&|\|\|)$/;
}
push(@tokens, $self->next_token()) if $tokens[-1]->[0] ne "\n" && defined($token = $self->peek()) && $token->[0] eq "\n";
return @tokens;
}

sub accumulate {
my ($self, $tokens, $cmd) = @_;
push(@$tokens, @$cmd);
}

sub parse {
my ($self, $stop) = @_;
push(@{$self->{stop}}, $stop);
goto DONE if $self->stop_at($self->peek());
my @tokens;
while (my @cmd = $self->parse_cmd()) {
$self->accumulate(\@tokens, \@cmd);
last if $self->stop_at($self->peek());
}
DONE:
pop(@{$self->{stop}});
return @tokens;
}

# ScriptParser is a subclass of ShellParser which identifies individual test
# definitions within test scripts and passes each test body to check_test().
# ScriptParser detects test definitions not only at the top-level of test
# scripts but also within compound commands such as loops and function
# definitions.

package ScriptParser;

our @ISA = ('ShellParser');

sub new {
my $class = shift @_;
my $self = $class->SUPER::new(@_);
$self->{ntests} = 0;
$self->{nerrs} = 0;
return $self;
}

# extract the raw content of a token, which may be a single string or a
# composition of multiple strings and non-string character runs; for instance,
# `"test body"` unwraps to `test body`; `word"a b"42'c d'` to `worda b42c d`
sub unwrap {
my $token = (@_ ? shift @_ : $_)->[0];
# simple case: 'sqstring' or "dqstring"
return $token if $token =~ s/^'([^']*)'$/$1/;
return $token if $token =~ s/^"([^"]*)"$/$1/;

# composite case
my ($s, $q, $escaped);
while (1) {
# slurp up non-special characters
$s .= $1 if $token =~ /\G([^\\'"]*)/gc;
# handle special characters
last unless $token =~ /\G(.)/sgc;
my $c = $1;
$q = undef, next if defined($q) && $c eq $q;
$q = $c, next if !defined($q) && $c =~ /^['"]$/;
if ($c eq '\\') {
last unless $token =~ /\G(.)/sgc;
$c = $1;
$s .= '\\' if $c eq "\n"; # preserve line splice
}
$s .= $c;
}
return $s
}

sub check_test {
# no-op; subclass and override to implement lint checks
}

sub parse_cmd {
my $self = shift @_;
my @tokens = $self->SUPER::parse_cmd();
return @tokens unless @tokens && $tokens[0]->[0] =~ /^test_expect_(?:success|failure)$/;
my $n = $#tokens;
$n-- while $n >= 0 && $tokens[$n]->[0] =~ /^(?:[;&\n|]|&&|\|\|)$/;
my $herebody;
if ($n >= 2 && $tokens[$n-1]->[0] eq '-' && $tokens[$n]->[0] =~ /^<<-?(.+)$/) {
$herebody = $self->{heredocs}->{$1};
$n--;
}
$self->check_test($tokens[1], $tokens[2], $herebody) if $n == 2; # title body
$self->check_test($tokens[2], $tokens[3], $herebody) if $n > 2; # prereq title body
return @tokens;
}

1;

View File

@ -195,7 +195,7 @@ test_expect_success 'delete ref while another dangling packed ref' '
test_expect_success 'pack ref directly below refs/' '
git update-ref refs/top HEAD &&
git ${pack_refs} --all --prune &&
grep refs/top .git/packed-refs &&
test_grep refs/top .git/packed-refs &&
test_path_is_missing .git/refs/top
'


View File

@ -19,7 +19,7 @@ test_expect_success '--exists with missing reference' '

test_expect_success '--exists does not use DWIM' '
test_expect_code 2 ${git_show_ref_exists} $GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME 2>err &&
grep "reference does not exist" err
test_grep "reference does not exist" err
'

test_expect_success '--exists with HEAD' '

View File

@ -743,7 +743,7 @@ test_expect_success 'subtest: lazy prereqs do not turn off tracing' '
test_done
EOF

grep "echo trace" lazy-prereq-and-tracing/err
test_grep "echo trace" lazy-prereq-and-tracing/err
'

test_expect_success 'subtest: tests clean up after themselves' '
@ -815,7 +815,7 @@ test_expect_success 'subtest: test_atexit is run' '

test_expect_success 'test_oid provides sane info by default' '
test_oid zero >actual &&
grep "^00*\$" actual &&
test_grep "^00*\$" actual &&
rawsz="$(test_oid rawsz)" &&
hexsz="$(test_oid hexsz)" &&
# +1 accounts for the trailing newline
@ -827,7 +827,7 @@ test_expect_success 'test_oid can look up data for SHA-1' '
test_when_finished "test_detect_hash" &&
test_set_hash sha1 &&
test_oid zero >actual &&
grep "^00*\$" actual &&
test_grep "^00*\$" actual &&
rawsz="$(test_oid rawsz)" &&
hexsz="$(test_oid hexsz)" &&
test $(wc -c <actual) -eq 41 &&
@ -839,7 +839,7 @@ test_expect_success 'test_oid can look up data for SHA-256' '
test_when_finished "test_detect_hash" &&
test_set_hash sha256 &&
test_oid zero >actual &&
grep "^00*\$" actual &&
test_grep "^00*\$" actual &&
rawsz="$(test_oid rawsz)" &&
hexsz="$(test_oid hexsz)" &&
test $(wc -c <actual) -eq 65 &&
@ -884,11 +884,11 @@ test_expect_success 'test_bool_env' '
# test script, hence the redirection of fd 7, and aborts
# with "exit 1", hence the subshell.
! ( test_bool_env envvar true ) 7>err &&
grep "error: test_bool_env requires bool values" err &&
test_grep "error: test_bool_env requires bool values" err &&

envvar=true &&
! ( test_bool_env envvar invalid ) 7>err &&
grep "error: test_bool_env requires bool values" err
test_grep "error: test_bool_env requires bool values" err
)
'

@ -1242,12 +1242,12 @@ test_expect_success 'test_must_fail on a failing git command with env' '

test_expect_success 'test_must_fail rejects a non-git command' '
! test_must_fail grep ^$ notafile 2>err &&
grep -F "test_must_fail: only '"'"'git'"'"' is allowed" err
test_grep -F "test_must_fail: only '"'"'git'"'"' is allowed" err
'

test_expect_success 'test_must_fail rejects a non-git command with env' '
! test_must_fail env var1=a var2=b grep ^$ notafile 2>err &&
grep -F "test_must_fail: only '"'"'git'"'"' is allowed" err
test_grep -F "test_must_fail: only '"'"'git'"'"' is allowed" err
'

test_done

View File

@ -278,9 +278,9 @@ test_expect_success POSIXPERM 'init creates a new deep directory (umask vs. shar
git init --bare --shared=0660 newdir/a/b/c &&
test_path_is_dir newdir/a/b/c/refs &&
ls -ld newdir/a newdir/a/b > lsab.out &&
! grep -v "^drwxrw[sx]r-x" lsab.out &&
test_grep ! -v "^drwxrw[sx]r-x" lsab.out &&
ls -ld newdir/a/b/c > lsc.out &&
! grep -v "^drwxrw[sx]---" lsc.out
test_grep ! -v "^drwxrw[sx]---" lsc.out
)
'

@ -619,7 +619,7 @@ test_expect_success DEFAULT_REPO_FORMAT 'extensions.refStorage is not allowed wi
git init refstorage &&
git -C refstorage config extensions.refStorage files &&
test_must_fail git -C refstorage rev-parse 2>err &&
grep "repo version is 0, but v1-only extension found" err
test_grep "repo version is 0, but v1-only extension found" err
'

test_expect_success DEFAULT_REPO_FORMAT 'extensions.refStorage with files backend' '
@ -637,7 +637,7 @@ test_expect_success DEFAULT_REPO_FORMAT 'extensions.refStorage with unknown back
git -C refstorage config core.repositoryformatversion 1 &&
git -C refstorage config extensions.refStorage garbage &&
test_must_fail git -C refstorage rev-parse 2>err &&
grep "invalid value for ${SQ}extensions.refstorage${SQ}: ${SQ}garbage${SQ}" err
test_grep "invalid value for ${SQ}extensions.refstorage${SQ}: ${SQ}garbage${SQ}" err
'

test_expect_success 'init with GIT_DEFAULT_REF_FORMAT=garbage' '
@ -848,8 +848,8 @@ test_expect_success MINGW 'redirect std handles' '
GIT_REDIRECT_STDOUT=output.txt \
GIT_REDIRECT_STDERR="2>&1" \
git rev-parse --git-dir --verify refs/invalid &&
grep "^\\.git\$" output.txt &&
grep "Needed a single revision" output.txt
test_grep "^\\.git\$" output.txt &&
test_grep "Needed a single revision" output.txt
'

test_expect_success '--initial-branch' '
@ -862,14 +862,14 @@ test_expect_success '--initial-branch' '
git init --initial-branch=ignore initial-branch-option 2>err &&
test_grep "ignored --initial-branch" err &&
git -C initial-branch-option symbolic-ref HEAD >actual &&
grep hello actual
test_grep hello actual
'

test_expect_success 'overridden default initial branch name (config)' '
test_config_global init.defaultBranch nmb &&
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= git init initial-branch-config &&
git -C initial-branch-config symbolic-ref HEAD >actual &&
grep nmb actual
test_grep nmb actual
'

test_expect_success 'advice on unconfigured init.defaultBranch' '
@ -907,7 +907,7 @@ test_expect_success 'overridden default main branch name (env)' '
test_config_global init.defaultBranch nmb &&
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=env git init main-branch-env &&
git -C main-branch-env symbolic-ref HEAD >actual &&
grep env actual
test_grep env actual
'

test_expect_success 'invalid default branch name' '

View File

@ -790,8 +790,8 @@ test_expect_success 'existing file and directory' '
>one &&
mkdir top-level-dir &&
git check-ignore one top-level-dir >actual &&
grep one actual &&
grep top-level-dir actual
test_grep one actual &&
test_grep top-level-dir actual
'

test_expect_success 'existing directory and file' '
@ -800,8 +800,8 @@ test_expect_success 'existing directory and file' '
>one &&
mkdir top-level-dir &&
git check-ignore top-level-dir one >actual &&
grep one actual &&
grep top-level-dir actual
test_grep one actual &&
test_grep top-level-dir actual
'

test_expect_success 'exact prefix matching (with root)' '

View File

@ -35,7 +35,7 @@ test_expect_success PIPE 'setup: .git as a FIFO (named pipe) is rejected' '
cd parent/fifo-trap &&
mkfifo .git &&
test_must_fail git rev-parse --git-dir 2>stderr &&
grep "not a regular file" stderr
test_grep "not a regular file" stderr
)
'

@ -47,7 +47,7 @@ test_expect_success SYMLINKS,PIPE 'setup: .git as a symlink to a FIFO is rejecte
mkfifo target-fifo &&
ln -s target-fifo .git &&
test_must_fail git rev-parse --git-dir 2>stderr &&
grep "not a regular file" stderr
test_grep "not a regular file" stderr
)
'

@ -58,7 +58,7 @@ test_expect_success 'setup: .git with garbage content is rejected' '
cd parent/garbage-trap &&
echo "garbage" >.git &&
test_must_fail git rev-parse --git-dir 2>stderr &&
grep "invalid gitfile format" stderr
test_grep "invalid gitfile format" stderr
)
'


View File

@ -131,8 +131,8 @@ test_expect_success 'git help succeeds without git.html' '

test_expect_success 'git help --user-interfaces' '
git help --user-interfaces >help.output &&
grep "^ attributes " help.output &&
grep "^ mailmap " help.output
test_grep "^ attributes " help.output &&
test_grep "^ mailmap " help.output
'

test_expect_success 'git help -c' '

View File

@ -16,7 +16,7 @@ fi
test_expect_success 'test-sha1 detects shattered pdf' '
test_must_fail test-tool sha1 <"$TEST_DATA/shattered-1.pdf" 2>err &&
test_grep collision err &&
grep 38762cf7f55934b34d179ae6a4c80cadccbb7f0a err
test_grep 38762cf7f55934b34d179ae6a4c80cadccbb7f0a err
'

test_done

View File

@ -88,7 +88,7 @@ test_expect_success 'test-tool env-helper reads config thanks to trace2' '
test_must_fail \
env HOME="$(pwd)/home" \
git config -l 2>err &&
grep "exceeded maximum include depth" err &&
test_grep "exceeded maximum include depth" err &&

# This validates that the assumption that we attempt to
# read the configuration and fail very early in the start-up
@ -100,7 +100,7 @@ test_expect_success 'test-tool env-helper reads config thanks to trace2' '
test-tool -C no-such-directory \
env-helper --type=bool --default=0 \
--exit-code GIT_TEST_ENV_HELPER 2>err &&
grep "exceeded maximum include depth" err
test_grep "exceeded maximum include depth" err
'

test_done

View File

@ -731,7 +731,7 @@ test_expect_success 'process filter should restart after unexpected write failur
rm -f debug.log &&
git checkout --quiet --no-progress . 2>git-stderr.log &&

grep "smudge write error" git-stderr.log &&
test_grep "smudge write error" git-stderr.log &&
test_grep "error: external filter" git-stderr.log &&

cat >expected.log <<-EOF &&
@ -853,7 +853,7 @@ test_expect_success 'invalid process filter must fail (and not hang!)' '

cp "$TEST_ROOT/test.o" test.r &&
test_must_fail git add . 2>git-stderr.log &&
grep "expected git-filter-server" git-stderr.log
test_grep "expected git-filter-server" git-stderr.log
)
'

@ -970,7 +970,7 @@ test_expect_success 'missing file in delayed checkout' '

rm -rf repo-cloned &&
test_must_fail git clone repo repo-cloned 2>git-stderr.log &&
grep "error: .missing-delay\.a. was not filtered properly" git-stderr.log
test_grep "error: .missing-delay\.a. was not filtered properly" git-stderr.log
'

test_expect_success 'invalid file in delayed checkout' '
@ -991,7 +991,7 @@ test_expect_success 'invalid file in delayed checkout' '

rm -rf repo-cloned &&
test_must_fail git clone repo repo-cloned 2>git-stderr.log &&
grep "error: external filter .* signaled that .unfiltered. is now available although it has not been delayed earlier" git-stderr.log
test_grep "error: external filter .* signaled that .unfiltered. is now available although it has not been delayed earlier" git-stderr.log
'

for mode in 'case' 'utf-8'
@ -1032,7 +1032,7 @@ do

git clone $mode-collision $mode-collision-cloned &&
# Make sure z was really delayed
grep "IN: smudge $dir/z .* \\[DELAYED\\]" $mode-collision-cloned/delayed.log &&
test_grep "IN: smudge $dir/z .* \\[DELAYED\\]" $mode-collision-cloned/delayed.log &&

# Should not create $dir/z at $symlink/z
test_path_is_missing $mode-collision/target-dir/z
@ -1070,7 +1070,7 @@ test_expect_success SYMLINKS,CASE_INSENSITIVE_FS \
git commit -m super &&

git checkout --recurse-submodules . &&
grep "IN: smudge A/B/y .* \\[DELAYED\\]" delayed.log &&
test_grep "IN: smudge A/B/y .* \\[DELAYED\\]" delayed.log &&
test_path_is_missing target-dir/y
)
'
@ -1161,9 +1161,9 @@ test_expect_success 'delayed checkout correctly reports the number of updated en

rm *.a &&
git checkout . 2>err &&
grep "IN: smudge test-delay10.a .* \\[DELAYED\\]" delayed.log &&
grep "IN: smudge test-delay11.a .* \\[DELAYED\\]" delayed.log &&
grep "Updated 2 paths from the index" err
test_grep "IN: smudge test-delay10.a .* \\[DELAYED\\]" delayed.log &&
test_grep "IN: smudge test-delay11.a .* \\[DELAYED\\]" delayed.log &&
test_grep "Updated 2 paths from the index" err
)
'


View File

@ -20,10 +20,10 @@ test_expect_success 'core.unsetenvvars works' '
HOBBES=Calvin &&
export HOBBES &&
git commit --allow-empty -m with 2>err &&
grep Calvin err &&
test_grep Calvin err &&
git -c core.unsetenvvars=FINDUS,HOBBES,CALVIN \
commit --allow-empty -m without 2>err &&
! grep Calvin err
test_grep ! Calvin err
'

test_done

View File

@ -407,12 +407,12 @@ test_expect_success 'strip comments with changed comment string' '

test_expect_success 'newline as commentchar is forbidden' '
test_must_fail git -c core.commentChar="$LF" stripspace -s 2>err &&
grep "core.commentchar cannot contain newline" err
test_grep "core.commentchar cannot contain newline" err
'

test_expect_success 'empty commentchar is forbidden' '
test_must_fail git -c core.commentchar= stripspace -s 2>err &&
grep "core.commentchar must have at least one character" err
test_grep "core.commentchar must have at least one character" err
'

test_expect_success '-c with single line' '

View File

@ -29,7 +29,7 @@ test_expect_success 'PID info not shown by default' '
test_must_fail git add . 2>err &&
# Should not crash, just show normal error without PID
test_grep "Unable to create" err &&
! test_grep "is held by process" err
test_grep ! "is held by process" err
)
'


View File

@ -324,13 +324,13 @@ test_expect_success 'non ambiguous option (after two options it abbreviates)' '

test_expect_success 'Alias options do not contribute to abbreviation' '
test-tool parse-options --alias-source 123 >output &&
grep "^string: 123" output &&
test_grep "^string: 123" output &&
test-tool parse-options --alias-target 123 >output &&
grep "^string: 123" output &&
test_grep "^string: 123" output &&
test_must_fail test-tool parse-options --alias &&
GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS=false \
test-tool parse-options --alias 123 >output &&
grep "^string: 123" output
test_grep "^string: 123" output
'

cat >typo.err <<\EOF
@ -582,16 +582,16 @@ test_expect_success 'KEEP_UNKNOWN_OPT works' '

test_expect_success 'NO_INTERNAL_HELP works for -h' '
test_expect_code 129 test-tool parse-options-flags --no-internal-help cmd -h 2>err &&
grep "^error: unknown switch \`h$SQ" err &&
grep "^usage: " err
test_grep "^error: unknown switch \`h$SQ" err &&
test_grep "^usage: " err
'

for help_opt in help help-all
do
test_expect_success "NO_INTERNAL_HELP works for --$help_opt" "
test_expect_code 129 test-tool parse-options-flags --no-internal-help cmd --$help_opt 2>err &&
grep '^error: unknown option \`'$help_opt\' err &&
grep '^usage: ' err
test_grep '^error: unknown option \`'$help_opt\' err &&
test_grep '^usage: ' err
"
done

@ -608,38 +608,38 @@ test_expect_success 'KEEP_UNKNOWN_OPT | NO_INTERNAL_HELP works' '

test_expect_success 'subcommand - no subcommand shows error and usage' '
test_expect_code 129 test-tool parse-subcommand cmd 2>err &&
grep "^error: need a subcommand" err &&
grep ^usage: err
test_grep "^error: need a subcommand" err &&
test_grep ^usage: err
'

test_expect_success 'subcommand - subcommand after -- shows error and usage' '
test_expect_code 129 test-tool parse-subcommand cmd -- subcmd-one 2>err &&
grep "^error: need a subcommand" err &&
grep ^usage: err
test_grep "^error: need a subcommand" err &&
test_grep ^usage: err
'

test_expect_success 'subcommand - subcommand after --end-of-options shows error and usage' '
test_expect_code 129 test-tool parse-subcommand cmd --end-of-options subcmd-one 2>err &&
grep "^error: need a subcommand" err &&
grep ^usage: err
test_grep "^error: need a subcommand" err &&
test_grep ^usage: err
'

test_expect_success 'subcommand - unknown subcommand shows error and usage' '
test_expect_code 129 test-tool parse-subcommand cmd nope 2>err &&
grep "^error: unknown subcommand: \`nope$SQ" err &&
grep ^usage: err
test_grep "^error: unknown subcommand: \`nope$SQ" err &&
test_grep ^usage: err
'

test_expect_success 'subcommand - subcommands cannot be abbreviated' '
test_expect_code 129 test-tool parse-subcommand cmd subcmd-o 2>err &&
grep "^error: unknown subcommand: \`subcmd-o$SQ$" err &&
grep ^usage: err
test_grep "^error: unknown subcommand: \`subcmd-o$SQ$" err &&
test_grep ^usage: err
'

test_expect_success 'subcommand - no negated subcommands' '
test_expect_code 129 test-tool parse-subcommand cmd no-subcmd-one 2>err &&
grep "^error: unknown subcommand: \`no-subcmd-one$SQ" err &&
grep ^usage: err
test_grep "^error: unknown subcommand: \`no-subcmd-one$SQ" err &&
test_grep ^usage: err
'

test_expect_success 'subcommand - simple' '
@ -709,8 +709,8 @@ test_expect_success 'subcommand - SUBCOMMAND_OPTIONAL + subcommand not given + u

test_expect_success 'subcommand - SUBCOMMAND_OPTIONAL + subcommand not given + unknown option' '
test_expect_code 129 test-tool parse-subcommand --subcommand-optional cmd --subcommand-opt 2>err &&
grep "^error: unknown option" err &&
grep ^usage: err
test_grep "^error: unknown option" err &&
test_grep ^usage: err
'

test_expect_success 'subcommand - SUBCOMMAND_OPTIONAL | KEEP_UNKNOWN_OPT + subcommand not given + unknown option' '
@ -778,28 +778,28 @@ test_expect_success 'subcommand - completion helper' '

test_expect_success 'subcommands are incompatible with STOP_AT_NON_OPTION' '
test_must_fail test-tool parse-subcommand --stop-at-non-option cmd subcmd-one 2>err &&
grep ^BUG err
test_grep ^BUG err
'

test_expect_success 'subcommands are incompatible with KEEP_UNKNOWN_OPT unless in combination with SUBCOMMAND_OPTIONAL' '
test_must_fail test-tool parse-subcommand --keep-unknown-opt cmd subcmd-two 2>err &&
grep ^BUG err
test_grep ^BUG err
'

test_expect_success 'subcommands are incompatible with KEEP_DASHDASH unless in combination with SUBCOMMAND_OPTIONAL' '
test_must_fail test-tool parse-subcommand --keep-dashdash cmd subcmd-two 2>err &&
grep ^BUG err
test_grep ^BUG err
'

test_expect_success 'negative unsigned' '
test_must_fail test-tool parse-options --unsigned -1 >out 2>err &&
grep "non-negative integer" err &&
test_grep "non-negative integer" err &&
test_must_be_empty out
'

test_expect_success 'unsigned with units but no numbers' '
test_must_fail test-tool parse-options --unsigned m >out 2>err &&
grep "non-negative integer" err &&
test_grep "non-negative integer" err &&
test_must_be_empty out
'


View File

@ -13,7 +13,7 @@ test_expect_success 'setup ' '

test_expect_success 'tag --contains <existent_tag>' '
git tag --contains "v1.0" >actual 2>actual.err &&
grep "v1.0" actual &&
test_grep "v1.0" actual &&
test_line_count = 0 actual.err
'


View File

@ -33,19 +33,19 @@ test_expect_success 'servers cannot share the same path' '
test_expect_success 'big response' '
test-tool simple-ipc send --token=big >actual &&
test_line_count -ge 10000 actual &&
grep -q "big: [0]*9999\$" actual
test_grep -q "big: [0]*9999\$" actual
'

test_expect_success 'chunk response' '
test-tool simple-ipc send --token=chunk >actual &&
test_line_count -ge 10000 actual &&
grep -q "big: [0]*9999\$" actual
test_grep -q "big: [0]*9999\$" actual
'

test_expect_success 'slow response' '
test-tool simple-ipc send --token=slow >actual &&
test_line_count -ge 100 actual &&
grep -q "big: [0]*99\$" actual
test_grep -q "big: [0]*99\$" actual
'

# Send an IPC with n=100,000 bytes of ballast. This should be large enough
@ -54,7 +54,7 @@ test_expect_success 'slow response' '
#
test_expect_success 'sendbytes' '
test-tool simple-ipc sendbytes --bytecount=100000 --byte=A >actual &&
grep "sent:A00100000 rcvd:A00100000" actual
test_grep "sent:A00100000 rcvd:A00100000" actual
'

# Start a series of <threads> client threads that each make <batchsize>
@ -93,7 +93,7 @@ test_expect_success 'stress test threads' '
--batchsize=13 \
>actual &&
test_line_count = 92 actual &&
grep "good 91" actual &&
test_grep "good 91" actual &&
grep "sent:A" <actual >actual_a &&
cat >expect_a <<-EOF &&
sent:A00000019 rcvd:A00000019

View File

@ -97,7 +97,7 @@ test_expect_success POSIXPERM 'run_command reports EACCES' '
chmod -x hello.sh &&
test_must_fail test-tool run-command run-command ./hello.sh 2>err &&

grep "fatal: cannot exec.*hello.sh" err
test_grep "fatal: cannot exec.*hello.sh" err
'

test_expect_success POSIXPERM,SANITY 'unreadable directory in PATH' '

View File

@ -127,7 +127,7 @@ test_expect_success SYMLINKS 'dir-iterator should not follow symlinks by default
test_expect_success SYMLINKS 'dir-iterator does not resolve top-level symlinks' '
test_must_fail test-tool dir-iterator ./dir5 >out &&

grep "ENOTDIR" out
test_grep "ENOTDIR" out
'

test_done

View File

@ -21,23 +21,23 @@ test_expect_success 'run based on configured value' '

git for-each-repo --config=run.key commit --allow-empty -m "ran" &&
git -C one log -1 --pretty=format:%s >message &&
grep ran message &&
test_grep ran message &&
git -C two log -1 --pretty=format:%s >message &&
! grep ran message &&
test_grep ! ran message &&
git -C three log -1 --pretty=format:%s >message &&
grep ran message &&
test_grep ran message &&
git -C ~/four log -1 --pretty=format:%s >message &&
grep ran message &&
test_grep ran message &&

git for-each-repo --config=run.key -- commit --allow-empty -m "ran again" &&
git -C one log -1 --pretty=format:%s >message &&
grep again message &&
test_grep again message &&
git -C two log -1 --pretty=format:%s >message &&
! grep again message &&
test_grep ! again message &&
git -C three log -1 --pretty=format:%s >message &&
grep again message &&
test_grep again message &&
git -C ~/four log -1 --pretty=format:%s >message &&
grep again message &&
test_grep again message &&

git -C three for-each-repo --config=run.key -- \
commit --allow-empty -m "ran from worktree" &&

View File

@ -10,7 +10,7 @@ Verify wrappers and compatibility functions.

test_expect_success 'mktemp to nonexistent directory prints filename' '
test_must_fail test-tool mktemp doesnotexist/testXXXXXX 2>err &&
grep "doesnotexist/test" err
test_grep "doesnotexist/test" err
'

test_expect_success POSIXPERM,SANITY 'mktemp to unwritable directory prints filename' '
@ -18,7 +18,7 @@ test_expect_success POSIXPERM,SANITY 'mktemp to unwritable directory prints file
test_when_finished "chmod +w cannotwrite" &&
chmod -w cannotwrite &&
test_must_fail test-tool mktemp cannotwrite/testXXXXXX 2>err &&
grep "cannotwrite/test" err
test_grep "cannotwrite/test" err
'

test_expect_success 'git_mkstemps_mode does not fail if fd 0 is not open' '
@ -33,7 +33,7 @@ test_expect_success 'check for a bug in the regex routines' '
test_expect_success 'incomplete sideband messages are reassembled' '
test-tool pkt-line send-split-sideband >split-sideband &&
test-tool pkt-line receive-sideband <split-sideband 2>err &&
grep "Hello, world" err
test_grep "Hello, world" err
'

test_expect_success 'eof on sideband message is reported' '

View File

@ -52,17 +52,17 @@ test_expect_success 'add more packfiles' '

# HEAD^{tree} is in 2 packfiles
test-tool find-pack HEAD^{tree} >head_tree_packs &&
grep "$head_commit_pack" head_tree_packs &&
grep mypackname1 head_tree_packs &&
! grep mypackname2 head_tree_packs &&
test_grep "$head_commit_pack" head_tree_packs &&
test_grep mypackname1 head_tree_packs &&
test_grep ! mypackname2 head_tree_packs &&
test-tool find-pack --check-count 2 HEAD^{tree} &&
! test-tool find-pack --check-count 1 HEAD^{tree} &&

# HEAD:five.t is also in 2 packfiles
test-tool find-pack HEAD:five.t >five_packs &&
grep "$head_commit_pack" five_packs &&
! grep mypackname1 five_packs &&
grep mypackname2 five_packs &&
test_grep "$head_commit_pack" five_packs &&
test_grep ! mypackname1 five_packs &&
test_grep mypackname2 five_packs &&
test-tool find-pack -c 2 HEAD:five.t &&
! test-tool find-pack --check-count=0 HEAD:five.t
'

View File

@ -40,15 +40,15 @@ test_expect_success 'sanity check "System Info" section' '

# The beginning should match "git version --build-options" verbatim,
# but rather than checking bit-for-bit equality, just test some basics.
grep "git version " system &&
grep "shell-path: ." system &&
test_grep "git version " system &&
test_grep "shell-path: ." system &&

# After the version, there should be some more info.
# This is bound to differ from environment to environment,
# so we just do some rather high-level checks.
grep "uname: ." system &&
grep "compiler info: ." system &&
grep "zlib." system
test_grep "uname: ." system &&
test_grep "compiler info: ." system &&
test_grep "zlib." system
'

test_expect_success 'dies if file with same name as report already exists' '
@ -112,7 +112,7 @@ test_expect_success UNZIP '--diagnose creates diagnostics zip archive' '
git bugreport --diagnose -o report -s test >out &&

zip_path=report/git-diagnostics-test.zip &&
grep "Available space" out &&
test_grep "Available space" out &&
test_path_is_file "$zip_path" &&

# Check zipped archive content
@ -120,10 +120,10 @@ test_expect_success UNZIP '--diagnose creates diagnostics zip archive' '
test_file_not_empty out &&

"$GIT_UNZIP" -p "$zip_path" packs-local.txt >out &&
grep ".git/objects" out &&
test_grep ".git/objects" out &&

"$GIT_UNZIP" -p "$zip_path" objects-local.txt >out &&
grep "^Total: [0-9][0-9]*" out &&
test_grep "^Total: [0-9][0-9]*" out &&

# Should not include .git directory contents by default
! "$GIT_UNZIP" -l "$zip_path" | grep ".git/"
@ -136,7 +136,7 @@ test_expect_success UNZIP '--diagnose=stats excludes .git dir contents' '

# Includes pack quantity/size info
"$GIT_UNZIP" -p "$zip_path" packs-local.txt >out &&
grep ".git/objects" out &&
test_grep ".git/objects" out &&

# Does not include .git directory contents
! "$GIT_UNZIP" -l "$zip_path" | grep ".git/"

View File

@ -8,7 +8,7 @@ test_expect_success UNZIP 'creates diagnostics zip archive' '
test_when_finished rm -rf report &&

git diagnose -o report -s test >out &&
grep "Available space" out &&
test_grep "Available space" out &&

zip_path=report/git-diagnostics-test.zip &&
test_path_is_file "$zip_path" &&
@ -18,10 +18,10 @@ test_expect_success UNZIP 'creates diagnostics zip archive' '
test_file_not_empty out &&

"$GIT_UNZIP" -p "$zip_path" packs-local.txt >out &&
grep ".git/objects" out &&
test_grep ".git/objects" out &&

"$GIT_UNZIP" -p "$zip_path" objects-local.txt >out &&
grep "^Total: [0-9][0-9]*" out &&
test_grep "^Total: [0-9][0-9]*" out &&

# Should not include .git directory contents by default
! "$GIT_UNZIP" -l "$zip_path" | grep ".git/"
@ -34,7 +34,7 @@ test_expect_success UNZIP 'counts loose objects' '
git diagnose -o test-count -s 1 >out &&
zip_path=test-count/git-diagnostics-1.zip &&
"$GIT_UNZIP" -p "$zip_path" objects-local.txt >out &&
grep "^Total: [1-9][0-9]* loose objects" out
test_grep "^Total: [1-9][0-9]* loose objects" out
'

test_expect_success UNZIP '--mode=stats excludes .git dir contents' '
@ -45,7 +45,7 @@ test_expect_success UNZIP '--mode=stats excludes .git dir contents' '
# Includes pack quantity/size info
zip_path=report/git-diagnostics-test.zip &&
"$GIT_UNZIP" -p "$zip_path" packs-local.txt >out &&
grep ".git/objects" out &&
test_grep ".git/objects" out &&

# Does not include .git directory contents
! "$GIT_UNZIP" -l "$zip_path" | grep ".git/"
@ -59,7 +59,7 @@ test_expect_success UNZIP '--mode=all includes .git dir contents' '
# Includes pack quantity/size info
zip_path=report/git-diagnostics-test.zip &&
"$GIT_UNZIP" -p "$zip_path" packs-local.txt >out &&
grep ".git/objects" out &&
test_grep ".git/objects" out &&

# Includes .git directory contents
"$GIT_UNZIP" -l "$zip_path" | grep ".git/" &&

View File

@ -50,7 +50,7 @@ test_expect_success 'merge @{-1}~1' '
git checkout main &&
git merge @{-1}~1 &&
git cat-file commit HEAD >actual &&
grep "Merge branch '\''other'\''" actual
test_grep "Merge branch '\''other'\''" actual
'

test_expect_success 'merge @{-100} before checking out that many branches yet' '

View File

@ -16,7 +16,7 @@ test_expect_success 'sanity: $TEXTDOMAIN is git' '
'

test_expect_success 'xgettext sanity: Perl _() strings are not extracted' '
! grep "A Perl string xgettext will not get" "$GIT_PO_PATH"/is.po
test_grep ! "A Perl string xgettext will not get" "$GIT_PO_PATH"/is.po
'

test_expect_success 'xgettext sanity: Comment extraction with --add-comments' '
@ -26,8 +26,8 @@ test_expect_success 'xgettext sanity: Comment extraction with --add-comments' '
'

test_expect_success 'xgettext sanity: Comment extraction with --add-comments stops at statements' '
! grep "This is a phony" "$GIT_PO_PATH"/is.po &&
! grep "the above comment" "$GIT_PO_PATH"/is.po
test_grep ! "This is a phony" "$GIT_PO_PATH"/is.po &&
test_grep ! "the above comment" "$GIT_PO_PATH"/is.po
'

test_expect_success GETTEXT 'sanity: $TEXTDOMAINDIR exists without NO_GETTEXT=YesPlease' '
@ -44,10 +44,10 @@ test_expect_success GETTEXT 'sanity: Icelandic locale was compiled' '
test_expect_success GETTEXT_LOCALE 'sanity: gettext("") metadata is OK' '
# Return value may be non-zero
LANGUAGE=is LC_ALL="$is_IS_locale" gettext "" >zero-expect &&
grep "Project-Id-Version: Git" zero-expect &&
grep "Git Mailing List <git@vger.kernel.org>" zero-expect &&
grep "Content-Type: text/plain; charset=UTF-8" zero-expect &&
grep "Content-Transfer-Encoding: 8bit" zero-expect
test_grep "Project-Id-Version: Git" zero-expect &&
test_grep "Git Mailing List <git@vger.kernel.org>" zero-expect &&
test_grep "Content-Type: text/plain; charset=UTF-8" zero-expect &&
test_grep "Content-Transfer-Encoding: 8bit" zero-expect
'

test_expect_success GETTEXT_LOCALE 'sanity: gettext(unknown) is passed through' '

View File

@ -12,7 +12,7 @@ test_expect_success 'git show a ISO-8859-1 commit under C locale' '
test_commit "iso-c-commit" iso-under-c &&
git show >out 2>err &&
test_must_be_empty err &&
grep -q "iso-c-commit" out
test_grep -q "iso-c-commit" out
'

test_expect_success GETTEXT_LOCALE 'git show a ISO-8859-1 commit under a UTF-8 locale' '
@ -20,7 +20,7 @@ test_expect_success GETTEXT_LOCALE 'git show a ISO-8859-1 commit under a UTF-8 l
test_commit "iso-utf8-commit" iso-under-utf8 &&
LANGUAGE=is LC_ALL="$is_IS_locale" git show >out 2>err &&
test_must_be_empty err &&
grep -q "iso-utf8-commit" out
test_grep -q "iso-utf8-commit" out
'

test_done

View File

@ -66,22 +66,22 @@ test_expect_success GETTEXT_LOCALE 'gettext: Fetching a UTF-8 msgid -> UTF-8' '
# eyes.
test_expect_success GETTEXT_ISO_LOCALE 'gettext: Fetching a UTF-8 msgid -> ISO-8859-1' '
LANGUAGE=is LC_ALL="$is_IS_iso_locale" gettext "TEST: single and “double” quotes" >actual &&
grep "einfaldar" actual &&
grep "$(echo tvöfaldar | iconv -f UTF-8 -t ISO8859-1)" actual
test_grep "einfaldar" actual &&
test_grep "$(echo tvöfaldar | iconv -f UTF-8 -t ISO8859-1)" actual
'

test_expect_success GETTEXT_LOCALE 'gettext.c: git init UTF-8 -> UTF-8' '
printf "Bjó til tóma Git lind" >expect &&
LANGUAGE=is LC_ALL="$is_IS_locale" git init repo >actual &&
test_when_finished "rm -rf repo" &&
grep "^$(cat expect) " actual
test_grep "^$(cat expect) " actual
'

test_expect_success GETTEXT_ISO_LOCALE 'gettext.c: git init UTF-8 -> ISO-8859-1' '
printf "Bjó til tóma Git lind" >expect &&
LANGUAGE=is LC_ALL="$is_IS_iso_locale" git init repo >actual &&
test_when_finished "rm -rf repo" &&
grep "^$(iconv -f UTF-8 -t ISO8859-1 <expect) " actual
test_grep "^$(iconv -f UTF-8 -t ISO8859-1 <expect) " actual
'

test_done

View File

@ -333,12 +333,12 @@ test_expect_success 'unsafe URLs are redacted by default' '

GIT_TRACE2="$(pwd)/trace.normal" \
git clone https://user:pwd@example.com/ clone &&
! grep user:pwd trace.normal &&
test_grep ! user:pwd trace.normal &&

GIT_TRACE2_REDACT=0 GIT_TRACE2="$(pwd)/unredacted.normal" \
git clone https://user:pwd@example.com/ clone2 &&
grep "start .* clone https://user:pwd@example.com" unredacted.normal &&
grep "remote.origin.url=https://user:pwd@example.com" unredacted.normal
test_grep "start .* clone https://user:pwd@example.com" unredacted.normal &&
test_grep "remote.origin.url=https://user:pwd@example.com" unredacted.normal
'

test_done

View File

@ -283,13 +283,13 @@ test_expect_success 'unsafe URLs are redacted by default' '

GIT_TRACE2_PERF="$(pwd)/trace.perf" \
git clone https://user:pwd@example.com/ clone &&
! grep user:pwd trace.perf &&
test_grep ! user:pwd trace.perf &&

GIT_TRACE2_REDACT=0 GIT_TRACE2_PERF="$(pwd)/unredacted.perf" \
git clone https://user:pwd@example.com/ clone2 &&
perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <unredacted.perf >actual &&
grep "d0|main|start|.* clone https://user:pwd@example.com" actual &&
grep "d0|main|def_param|.*|remote.origin.url:https://user:pwd@example.com" actual
test_grep "d0|main|start|.* clone https://user:pwd@example.com" actual &&
test_grep "d0|main|def_param|.*|remote.origin.url:https://user:pwd@example.com" actual
'

# Confirm that the requested command produces a "cmd_name" and a
@ -358,13 +358,13 @@ test_expect_success LIBCURL \

perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <prop.perf >actual &&

grep "d0|main|cmd_name|.*|_run_dashed_" actual &&
grep "d0|main|def_param|.*|cfg.prop.foo:red" actual &&
grep "d0|main|def_param|.*|ENV_PROP_FOO:blue" actual &&
test_grep "d0|main|cmd_name|.*|_run_dashed_" actual &&
test_grep "d0|main|def_param|.*|cfg.prop.foo:red" actual &&
test_grep "d0|main|def_param|.*|ENV_PROP_FOO:blue" actual &&

grep "d1|main|cmd_name|.*|remote-curl" actual &&
grep "d1|main|def_param|.*|cfg.prop.foo:red" actual &&
grep "d1|main|def_param|.*|ENV_PROP_FOO:blue" actual
test_grep "d1|main|cmd_name|.*|remote-curl" actual &&
test_grep "d1|main|def_param|.*|cfg.prop.foo:red" actual &&
test_grep "d1|main|def_param|.*|ENV_PROP_FOO:blue" actual
'

# Similarly, `git-http-fetch` is not built from git.c so do a
@ -389,13 +389,13 @@ test_expect_success LIBCURL \

perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <prop.perf >actual &&

grep "d0|main|cmd_name|.*|_run_dashed_" actual &&
grep "d0|main|def_param|.*|cfg.prop.foo:red" actual &&
grep "d0|main|def_param|.*|ENV_PROP_FOO:blue" actual &&
test_grep "d0|main|cmd_name|.*|_run_dashed_" actual &&
test_grep "d0|main|def_param|.*|cfg.prop.foo:red" actual &&
test_grep "d0|main|def_param|.*|ENV_PROP_FOO:blue" actual &&

grep "d1|main|cmd_name|.*|http-fetch" actual &&
grep "d1|main|def_param|.*|cfg.prop.foo:red" actual &&
grep "d1|main|def_param|.*|ENV_PROP_FOO:blue" actual
test_grep "d1|main|cmd_name|.*|http-fetch" actual &&
test_grep "d1|main|def_param|.*|cfg.prop.foo:red" actual &&
test_grep "d1|main|def_param|.*|ENV_PROP_FOO:blue" actual
'

# Historically, alias expansion explicitly emitted the def_param
@ -421,22 +421,22 @@ test_expect_success 'expect def_params during git alias expansion' '
perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <prop.perf >actual &&

# "git xxx" is first mapped to "git-xxx" and the child will fail.
grep "d0|main|cmd_name|.*|_run_dashed_ (_run_dashed_)" actual &&
test_grep "d0|main|cmd_name|.*|_run_dashed_ (_run_dashed_)" actual &&

# We unpeel that and substitute "version" into "xxx" (giving
# "git version") and update the cmd_name event.
grep "d0|main|cmd_name|.*|_run_git_alias_ (_run_dashed_/_run_git_alias_)" actual &&
test_grep "d0|main|cmd_name|.*|_run_git_alias_ (_run_dashed_/_run_git_alias_)" actual &&

# These def_param events could be associated with either of the
# above cmd_name events. It does not matter.
grep "d0|main|def_param|.*|cfg.prop.foo:red" actual &&
grep "d0|main|def_param|.*|ENV_PROP_FOO:blue" actual &&
test_grep "d0|main|def_param|.*|cfg.prop.foo:red" actual &&
test_grep "d0|main|def_param|.*|ENV_PROP_FOO:blue" actual &&

# The "git version" child sees a different cmd_name hierarchy.
# Also test the def_param (only for completeness).
grep "d1|main|cmd_name|.*|version (_run_dashed_/_run_git_alias_/version)" actual &&
grep "d1|main|def_param|.*|cfg.prop.foo:red" actual &&
grep "d1|main|def_param|.*|ENV_PROP_FOO:blue" actual
test_grep "d1|main|cmd_name|.*|version (_run_dashed_/_run_git_alias_/version)" actual &&
test_grep "d1|main|def_param|.*|cfg.prop.foo:red" actual &&
test_grep "d1|main|def_param|.*|ENV_PROP_FOO:blue" actual
'

test_expect_success 'expect def_params during shell alias expansion' '
@ -456,25 +456,25 @@ test_expect_success 'expect def_params during shell alias expansion' '
perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <prop.perf >actual &&

# "git xxx" is first mapped to "git-xxx" and the child will fail.
grep "d0|main|cmd_name|.*|_run_dashed_ (_run_dashed_)" actual &&
test_grep "d0|main|cmd_name|.*|_run_dashed_ (_run_dashed_)" actual &&

# We unpeel that and substitute "git version" for "git xxx" (as a
# shell command. Another cmd_name event is emitted as we unpeel.
grep "d0|main|cmd_name|.*|_run_shell_alias_ (_run_dashed_/_run_shell_alias_)" actual &&
test_grep "d0|main|cmd_name|.*|_run_shell_alias_ (_run_dashed_/_run_shell_alias_)" actual &&

# These def_param events could be associated with either of the
# above cmd_name events. It does not matter.
grep "d0|main|def_param|.*|cfg.prop.foo:red" actual &&
grep "d0|main|def_param|.*|ENV_PROP_FOO:blue" actual &&
test_grep "d0|main|def_param|.*|cfg.prop.foo:red" actual &&
test_grep "d0|main|def_param|.*|ENV_PROP_FOO:blue" actual &&

# We get the following only because we used a git command for the
# shell command. In general, it could have been a shell script and
# we would see nothing.
#
# The child knows the cmd_name hierarchy so it includes it.
grep "d1|main|cmd_name|.*|version (_run_dashed_/_run_shell_alias_/version)" actual &&
grep "d1|main|def_param|.*|cfg.prop.foo:red" actual &&
grep "d1|main|def_param|.*|ENV_PROP_FOO:blue" actual
test_grep "d1|main|cmd_name|.*|version (_run_dashed_/_run_shell_alias_/version)" actual &&
test_grep "d1|main|def_param|.*|cfg.prop.foo:red" actual &&
test_grep "d1|main|def_param|.*|ENV_PROP_FOO:blue" actual
'

test_expect_success 'expect def_params during nested git alias expansion' '
@ -496,33 +496,33 @@ test_expect_success 'expect def_params during nested git alias expansion' '

# "git xxx" is first mapped to "git-xxx" and try to spawn "git-xxx"
# and the child will fail.
grep "d0|main|cmd_name|.*|_run_dashed_ (_run_dashed_)" actual &&
grep "d0|main|child_start|.*|.* class:dashed argv:\[git-xxx\]" actual &&
test_grep "d0|main|cmd_name|.*|_run_dashed_ (_run_dashed_)" actual &&
test_grep "d0|main|child_start|.*|.* class:dashed argv:\[git-xxx\]" actual &&

# We unpeel that and substitute "yyy" into "xxx" (giving "git yyy")
# and spawn "git-yyy" and the child will fail.
grep "d0|main|alias|.*|alias:xxx argv:\[yyy\]" actual &&
grep "d0|main|cmd_name|.*|_run_dashed_ (_run_dashed_/_run_dashed_)" actual &&
grep "d0|main|child_start|.*|.* class:dashed argv:\[git-yyy\]" actual &&
test_grep "d0|main|alias|.*|alias:xxx argv:\[yyy\]" actual &&
test_grep "d0|main|cmd_name|.*|_run_dashed_ (_run_dashed_/_run_dashed_)" actual &&
test_grep "d0|main|child_start|.*|.* class:dashed argv:\[git-yyy\]" actual &&

# We unpeel that and substitute "version" into "xxx" (giving
# "git version") and update the cmd_name event.
grep "d0|main|alias|.*|alias:yyy argv:\[version\]" actual &&
grep "d0|main|cmd_name|.*|_run_git_alias_ (_run_dashed_/_run_dashed_/_run_git_alias_)" actual &&
test_grep "d0|main|alias|.*|alias:yyy argv:\[version\]" actual &&
test_grep "d0|main|cmd_name|.*|_run_git_alias_ (_run_dashed_/_run_dashed_/_run_git_alias_)" actual &&

# These def_param events could be associated with any of the
# above cmd_name events. It does not matter.
grep "d0|main|def_param|.*|cfg.prop.foo:red" actual >actual.matches &&
grep "d0|main|def_param|.*|ENV_PROP_FOO:blue" actual &&
test_grep "d0|main|def_param|.*|ENV_PROP_FOO:blue" actual &&

# However, we do not want them repeated each time we unpeel.
test_line_count = 1 actual.matches &&

# The "git version" child sees a different cmd_name hierarchy.
# Also test the def_param (only for completeness).
grep "d1|main|cmd_name|.*|version (_run_dashed_/_run_dashed_/_run_git_alias_/version)" actual &&
grep "d1|main|def_param|.*|cfg.prop.foo:red" actual &&
grep "d1|main|def_param|.*|ENV_PROP_FOO:blue" actual
test_grep "d1|main|cmd_name|.*|version (_run_dashed_/_run_dashed_/_run_git_alias_/version)" actual &&
test_grep "d1|main|def_param|.*|cfg.prop.foo:red" actual &&
test_grep "d1|main|def_param|.*|ENV_PROP_FOO:blue" actual
'

test_done

View File

@ -332,7 +332,7 @@ test_expect_success 'unsafe URLs are redacted by default in cmd_start events' '

GIT_TRACE2_EVENT="$(pwd)/trace.event" \
test-tool trace2 300redact_start git clone https://user:pwd@example.com/ clone2 &&
! grep user:pwd trace.event
test_grep ! user:pwd trace.event
'

test_expect_success 'unsafe URLs are redacted by default in child_start events' '
@ -341,7 +341,7 @@ test_expect_success 'unsafe URLs are redacted by default in child_start events'

GIT_TRACE2_EVENT="$(pwd)/trace.event" \
test-tool trace2 301redact_child_start git clone https://user:pwd@example.com/ clone2 &&
! grep user:pwd trace.event
test_grep ! user:pwd trace.event
'

test_expect_success 'unsafe URLs are redacted by default in exec events' '
@ -350,7 +350,7 @@ test_expect_success 'unsafe URLs are redacted by default in exec events' '

GIT_TRACE2_EVENT="$(pwd)/trace.event" \
test-tool trace2 302redact_exec git clone https://user:pwd@example.com/ clone2 &&
! grep user:pwd trace.event
test_grep ! user:pwd trace.event
'

test_expect_success 'unsafe URLs are redacted by default in def_param events' '
@ -359,7 +359,7 @@ test_expect_success 'unsafe URLs are redacted by default in def_param events' '

GIT_TRACE2_EVENT="$(pwd)/trace.event" \
test-tool trace2 303redact_def_param url https://user:pwd@example.com/ &&
! grep user:pwd trace.event
test_grep ! user:pwd trace.event
'

test_done

View File

@ -1014,7 +1014,7 @@ test_expect_success 'credential config with partial URLs' '
do
git -c credential.$partial.helper=yep \
credential fill <stdin >stdout &&
grep yep stdout ||
test_grep yep stdout ||
return 1
done &&

@ -1030,7 +1030,7 @@ test_expect_success 'credential config with partial URLs' '
do
git -c credential.$partial.helper=yep \
credential fill <stdin >stdout &&
! grep yep stdout ||
test_grep ! yep stdout ||
return 1
done &&


View File

@ -206,7 +206,7 @@ test_expect_success 'fetching of missing objects' '
git -C repo cat-file -p "$HASH" 2>err &&

# Ensure that no spurious FETCH_HEAD messages are written
! grep FETCH_HEAD err &&
test_grep ! FETCH_HEAD err &&

# Ensure that the .promisor file is written, and check that its
# associated packfile contains the object
@ -214,7 +214,7 @@ test_expect_success 'fetching of missing objects' '
test_line_count = 1 promisorlist &&
IDX=$(sed "s/promisor$/idx/" promisorlist) &&
git verify-pack --verbose "$IDX" >out &&
grep "$HASH" out
test_grep "$HASH" out
'

test_expect_success 'fetching of a promised object that promisor remote no longer has' '
@ -228,7 +228,7 @@ test_expect_success 'fetching of a promised object that promisor remote no longe

rm -rf unreliable-server/.git/objects/* &&
test_must_fail git -C unreliable-client checkout HEAD 2>err &&
grep "could not fetch.*from promisor remote" err
test_grep "could not fetch.*from promisor remote" err
'

test_expect_success 'fetching of missing objects works with ref-in-want enabled' '
@ -240,7 +240,7 @@ test_expect_success 'fetching of missing objects works with ref-in-want enabled'
rm -rf repo/.git/objects/* &&
rm -f trace &&
GIT_TRACE_PACKET="$(pwd)/trace" git -C repo cat-file -p "$HASH" &&
grep "fetch< fetch=.*ref-in-want" trace
test_grep "fetch< fetch=.*ref-in-want" trace
'

test_expect_success 'fetching from another promisor remote' '
@ -263,7 +263,7 @@ test_expect_success 'fetching from another promisor remote' '
test_line_count = 1 promisorlist &&
IDX=$(sed "s/promisor$/idx/" promisorlist) &&
git verify-pack --verbose "$IDX" >out &&
grep "$HASH2" out
test_grep "$HASH2" out
'

test_expect_success 'fetching with --filter configures a promisor remote' '
@ -286,7 +286,7 @@ test_expect_success 'fetching with --filter configures a promisor remote' '
test_line_count = 1 promisorlist &&
IDX=$(sed "s/promisor$/idx/" promisorlist) &&
git verify-pack --verbose "$IDX" >out &&
grep "$HASH3" out
test_grep "$HASH3" out
'

test_expect_success 'fetching of missing blobs works' '
@ -327,8 +327,8 @@ test_expect_success 'fetching of missing trees does not fetch blobs' '

# Ensure that the tree, but not the blob, is fetched
git -C repo rev-list --objects --missing=print $(cat treehash) >objects &&
grep "^$(cat treehash)" objects &&
grep "^[?]$(cat blobhash)" objects
test_grep "^$(cat treehash)" objects &&
test_grep "^[?]$(cat blobhash)" objects
'

test_expect_success 'rev-list stops traversal at missing and promised commit' '
@ -343,8 +343,8 @@ test_expect_success 'rev-list stops traversal at missing and promised commit' '
git -C repo config core.repositoryformatversion 1 &&
git -C repo config extensions.partialclone "arbitrary string" &&
git -C repo rev-list --exclude-promisor-objects --objects bar >out &&
grep $(git -C repo rev-parse bar) out &&
! grep $FOO out
test_grep $(git -C repo rev-parse bar) out &&
test_grep ! $FOO out
'

test_expect_success 'missing tree objects with --missing=allow-promisor and --exclude-promisor-objects' '
@ -413,10 +413,10 @@ test_expect_success 'rev-list stops traversal at missing and promised tree' '
git -C repo config core.repositoryformatversion 1 &&
git -C repo config extensions.partialclone "arbitrary string" &&
git -C repo rev-list --exclude-promisor-objects --objects HEAD >out &&
grep $(git -C repo rev-parse foo) out &&
! grep $TREE out &&
grep $(git -C repo rev-parse HEAD) out &&
! grep $TREE2 out
test_grep $(git -C repo rev-parse foo) out &&
test_grep ! $TREE out &&
test_grep $(git -C repo rev-parse HEAD) out &&
test_grep ! $TREE2 out
'

test_expect_success 'rev-list stops traversal at missing and promised blob' '
@ -432,8 +432,8 @@ test_expect_success 'rev-list stops traversal at missing and promised blob' '
git -C repo config core.repositoryformatversion 1 &&
git -C repo config extensions.partialclone "arbitrary string" &&
git -C repo rev-list --exclude-promisor-objects --objects HEAD >out &&
grep $(git -C repo rev-parse HEAD) out &&
! grep $BLOB out
test_grep $(git -C repo rev-parse HEAD) out &&
test_grep ! $BLOB out
'

test_expect_success 'rev-list stops traversal at promisor commit, tree, and blob' '
@ -451,10 +451,10 @@ test_expect_success 'rev-list stops traversal at promisor commit, tree, and blob
git -C repo config core.repositoryformatversion 1 &&
git -C repo config extensions.partialclone "arbitrary string" &&
git -C repo rev-list --exclude-promisor-objects --objects HEAD >out &&
! grep $COMMIT out &&
! grep $TREE out &&
! grep $BLOB out &&
grep $(git -C repo rev-parse bar) out # sanity check that some walking was done
test_grep ! $COMMIT out &&
test_grep ! $TREE out &&
test_grep ! $BLOB out &&
test_grep $(git -C repo rev-parse bar) out # sanity check that some walking was done
'

test_expect_success 'rev-list dies for missing objects on cmd line' '
@ -523,10 +523,10 @@ test_expect_success 'gc repacks promisor objects separately from non-promisor ob
test_line_count = 1 promisorlist &&
PROMISOR_PACKFILE=$(sed "s/.promisor/.pack/" <promisorlist) &&
git verify-pack $PROMISOR_PACKFILE -v >out &&
grep "$TREE_ONE" out &&
grep "$TREE_TWO" out &&
! grep "$(git -C repo rev-parse one)" out &&
! grep "$(git -C repo rev-parse two)" out &&
test_grep "$TREE_ONE" out &&
test_grep "$TREE_TWO" out &&
test_grep ! "$(git -C repo rev-parse one)" out &&
test_grep ! "$(git -C repo rev-parse two)" out &&

# Remove the promisor packfile and associated files
rm $(sed "s/.promisor//" <promisorlist).* &&
@ -536,10 +536,10 @@ test_expect_success 'gc repacks promisor objects separately from non-promisor ob
ls repo/.git/objects/pack/pack-*.pack >packlist &&
test_line_count = 1 packlist &&
git verify-pack repo/.git/objects/pack/pack-*.pack -v >out &&
grep "$(git -C repo rev-parse one)" out &&
grep "$(git -C repo rev-parse two)" out &&
! grep "$TREE_ONE" out &&
! grep "$TREE_TWO" out
test_grep "$(git -C repo rev-parse one)" out &&
test_grep "$(git -C repo rev-parse two)" out &&
test_grep ! "$TREE_ONE" out &&
test_grep ! "$TREE_TWO" out
'

test_expect_success 'gc does not repack promisor objects if there are none' '
@ -616,8 +616,8 @@ test_expect_success 'gc stops traversal when a missing but promised object is re
ls repo/.git/objects/pack/pack-*.pack >packlist &&
test_line_count = 1 packlist &&
git verify-pack repo/.git/objects/pack/pack-*.pack -v >out &&
grep "$(git -C repo rev-parse HEAD)" out &&
! grep "$TREE_HASH" out
test_grep "$(git -C repo rev-parse HEAD)" out &&
test_grep ! "$TREE_HASH" out
'

test_expect_success 'do not fetch when checking existence of tree we construct ourselves' '
@ -647,10 +647,10 @@ test_expect_success 'exact rename does not need to fetch the blob lazily' '

git clone --filter=blob:none --bare "file://$(pwd)/repo" partial.git &&
git -C partial.git rev-list --objects --missing=print HEAD >out &&
grep "[?]$FILE_HASH" out &&
test_grep "[?]$FILE_HASH" out &&
git -C partial.git log --follow -- new-file.txt &&
git -C partial.git rev-list --objects --missing=print HEAD >out &&
grep "[?]$FILE_HASH" out
test_grep "[?]$FILE_HASH" out
'

test_expect_success 'lazy-fetch when accessing object not in the_repository' '
@ -665,7 +665,7 @@ test_expect_success 'lazy-fetch when accessing object not in the_repository' '

# Sanity check that the file is missing
git -C partial.git rev-list --objects --missing=print HEAD >out &&
grep "[?]$FILE_HASH" out &&
test_grep "[?]$FILE_HASH" out &&

# The no-lazy-fetch mechanism prevents Git from fetching
test_must_fail env GIT_NO_LAZY_FETCH=1 \
@ -680,7 +680,7 @@ test_expect_success 'lazy-fetch when accessing object not in the_repository' '

# Sanity check that the file is still missing
git -C partial.git rev-list --objects --missing=print HEAD >out &&
grep "[?]$FILE_HASH" out &&
test_grep "[?]$FILE_HASH" out &&

git -C full cat-file -s "$FILE_HASH" >expect &&
test-tool partial-clone object-info partial.git "$FILE_HASH" >actual &&
@ -688,7 +688,7 @@ test_expect_success 'lazy-fetch when accessing object not in the_repository' '

# Sanity check that the file is now present
git -C partial.git rev-list --objects --missing=print HEAD >out &&
! grep "[?]$FILE_HASH" out
test_grep ! "[?]$FILE_HASH" out
'

test_expect_success 'push should not fetch new commit objects' '
@ -705,9 +705,9 @@ test_expect_success 'push should not fetch new commit objects' '
COMMIT=$(git -C server rev-parse server2) &&

test_must_fail git -C client push 2>err &&
grep "fetch first" err &&
test_grep "fetch first" err &&
git -C client rev-list --objects --missing=print "$COMMIT" >objects &&
grep "^[?]$COMMIT" objects
test_grep "^[?]$COMMIT" objects
'

test_expect_success 'setup for promisor.quiet tests' '
@ -750,7 +750,7 @@ test_expect_success TTY 'promisor.quiet=false shows progress messages' '
test_terminal git -C repo cat-file -p foo:foo.t 2>err &&

# Ensure that progress messages are written
grep "Receiving objects" err
test_grep "Receiving objects" err
'

test_expect_success TTY 'promisor.quiet=true does not show progress messages' '
@ -761,7 +761,7 @@ test_expect_success TTY 'promisor.quiet=true does not show progress messages' '
test_terminal git -C repo cat-file -p foo:foo.t 2>err &&

# Ensure that no progress messages are written
! grep "Receiving objects" err
test_grep ! "Receiving objects" err
'

test_expect_success TTY 'promisor.quiet=unconfigured shows progress messages' '
@ -771,7 +771,7 @@ test_expect_success TTY 'promisor.quiet=unconfigured shows progress messages' '
test_terminal git -C repo cat-file -p foo:foo.t 2>err &&

# Ensure that progress messages are written
grep "Receiving objects" err
test_grep "Receiving objects" err
'

test_expect_success 'promisor.quiet from submodule repo is honored' '
@ -819,7 +819,7 @@ test_expect_success 'fetching of missing objects from an HTTP server' '
test_line_count = 1 promisorlist &&
IDX=$(sed "s/promisor$/idx/" promisorlist) &&
git verify-pack --verbose "$IDX" >out &&
grep "$HASH" out
test_grep "$HASH" out
'

# DO NOT add non-httpd-specific tests here, because the last part of this

View File

@ -87,7 +87,7 @@ do
# -h output assertions
test_expect_success "$builtin -h output has no \t" '
h2s="$(help_to_synopsis "$builtin")" &&
! grep "$HT" "$h2s"
test_grep ! "$HT" "$h2s"
'

test_expect_success "$builtin -h output has dashed labels" '

View File

@ -320,8 +320,8 @@ test_expect_success 'progress generates traces' '

# t0212/parse_events.perl intentionally omits regions and data.
test_region progress "Working hard" trace.event &&
grep "\"key\":\"total_objects\",\"value\":\"40\"" trace.event &&
grep "\"key\":\"total_bytes\",\"value\":\"409600\"" trace.event
test_grep "\"key\":\"total_objects\",\"value\":\"40\"" trace.event &&
test_grep "\"key\":\"total_bytes\",\"value\":\"409600\"" trace.event
'

test_expect_success 'progress generates traces: stop / start' '
@ -344,8 +344,8 @@ test_expect_success 'progress generates traces: start without stop' '
LSAN_OPTIONS=detect_leaks=0 \
test-tool progress \
<in 2>stderr &&
grep region_enter.*progress trace-start.event &&
! grep region_leave.*progress trace-start.event
test_grep region_enter.*progress trace-start.event &&
test_grep ! region_leave.*progress trace-start.event
'

test_expect_success 'progress generates traces: stop without start' '
@ -355,8 +355,8 @@ test_expect_success 'progress generates traces: stop without start' '

GIT_TRACE2_EVENT="$PWD/trace-stop.event" test-tool progress \
<in 2>stderr &&
! grep region_enter.*progress trace-stop.event &&
! grep region_leave.*progress trace-stop.event
test_grep ! region_enter.*progress trace-stop.event &&
test_grep ! region_leave.*progress trace-stop.event
'

test_expect_success 'progress generates traces: start with active progress bar (no stops)' '
@ -369,9 +369,9 @@ test_expect_success 'progress generates traces: start with active progress bar (
LSAN_OPTIONS=detect_leaks=0 \
test-tool progress \
<in 2>stderr &&
grep region_enter.*progress.*One trace-2start.event &&
grep region_enter.*progress.*Two trace-2start.event &&
! grep region_leave trace-2start.event
test_grep region_enter.*progress.*One trace-2start.event &&
test_grep region_enter.*progress.*Two trace-2start.event &&
test_grep ! region_leave trace-2start.event
'

test_done

View File

@ -776,11 +776,11 @@ test_expect_success 'reflog: can delete separate reflog entries' '
test_commit file3 &&
test_commit file4 &&
git reflog >actual &&
grep file3 actual &&
test_grep file3 actual &&

git reflog delete HEAD@{1} &&
git reflog >actual &&
! grep file3 actual
test_grep ! file3 actual
)
'

@ -902,8 +902,8 @@ test_expect_success 'reflog: garbage collection deletes reflog entries' '
done &&
git reflog refs/heads/main >actual &&
test_line_count = 10 actual &&
grep "commit (initial): number 1" actual &&
grep "commit: number 10" actual &&
test_grep "commit (initial): number 1" actual &&
test_grep "commit: number 10" actual &&

git gc &&
git reflog refs/heads/main >actual &&

View File

@ -142,8 +142,8 @@ test_expect_success '3-way not overwriting local changes (our side)' '

echo >>file1 "local changes" &&
read_tree_u_must_succeed -m -u branch-point side-a side-b &&
grep "new line to be kept" file1 &&
grep "local changes" file1
test_grep "new line to be kept" file1 &&
test_grep "local changes" file1

'

@ -156,8 +156,8 @@ test_expect_success '3-way not overwriting local changes (their side)' '

echo >>file2 "local changes" &&
read_tree_u_must_fail -m -u branch-point side-a side-b &&
! grep "new line to be kept" file2 &&
grep "local changes" file2
test_grep ! "new line to be kept" file2 &&
test_grep "local changes" file2

'


View File

@ -696,8 +696,8 @@ test_expect_success '%(deltabase) reports packed delta bases' '
git repack -ad &&
git cat-file --batch-check="%(deltabase)" <blobs >actual &&
{
grep "$(git rev-parse HEAD:foo)" actual ||
grep "$(git rev-parse HEAD:foo-plus)" actual
test_grep "$(git rev-parse HEAD:foo)" actual ||
test_grep "$(git rev-parse HEAD:foo-plus)" actual
}
'

@ -826,7 +826,7 @@ test_expect_success 'cat-file -t and -s on corrupt loose object' '
# Swap the two to corrupt the repository
mv -f "$other_path" "$empty_path" &&
test_must_fail git fsck 2>err.fsck &&
grep "hash-path mismatch" err.fsck &&
test_grep "hash-path mismatch" err.fsck &&

# confirm that cat-file is reading the new swapped-in
# blob...
@ -1318,37 +1318,37 @@ test_expect_success 'cat-file --batch-all-objects --batch-check ignores replace'
test_expect_success 'batch-command empty command' '
echo "" >cmd &&
test_expect_code 128 git cat-file --batch-command <cmd 2>err &&
grep "^fatal:.*empty command in input.*" err
test_grep "^fatal:.*empty command in input.*" err
'

test_expect_success 'batch-command whitespace before command' '
echo " info deadbeef" >cmd &&
test_expect_code 128 git cat-file --batch-command <cmd 2>err &&
grep "^fatal:.*whitespace before command.*" err
test_grep "^fatal:.*whitespace before command.*" err
'

test_expect_success 'batch-command unknown command' '
echo unknown_command >cmd &&
test_expect_code 128 git cat-file --batch-command <cmd 2>err &&
grep "^fatal:.*unknown command.*" err
test_grep "^fatal:.*unknown command.*" err
'

test_expect_success 'batch-command missing arguments' '
echo "info" >cmd &&
test_expect_code 128 git cat-file --batch-command <cmd 2>err &&
grep "^fatal:.*info requires arguments.*" err
test_grep "^fatal:.*info requires arguments.*" err
'

test_expect_success 'batch-command flush with arguments' '
echo "flush arg" >cmd &&
test_expect_code 128 git cat-file --batch-command --buffer <cmd 2>err &&
grep "^fatal:.*flush takes no arguments.*" err
test_grep "^fatal:.*flush takes no arguments.*" err
'

test_expect_success 'batch-command flush without --buffer' '
echo "flush" >cmd &&
test_expect_code 128 git cat-file --batch-command <cmd 2>err &&
grep "^fatal:.*flush is only for --buffer mode.*" err
test_grep "^fatal:.*flush is only for --buffer mode.*" err
'

perl_script='

View File

@ -205,7 +205,7 @@ done
test_expect_success 'too-short tree' '
echo abc >malformed-tree &&
test_must_fail git hash-object -t tree malformed-tree 2>err &&
grep "too-short tree object" err
test_grep "too-short tree object" err
'

test_expect_success PERL_TEST_HELPERS 'malformed mode in tree' '
@ -213,7 +213,7 @@ test_expect_success PERL_TEST_HELPERS 'malformed mode in tree' '
bin_oid=$(echo $hex_oid | hex2oct) &&
printf "9100644 \0$bin_oid" >tree-with-malformed-mode &&
test_must_fail git hash-object -t tree tree-with-malformed-mode 2>err &&
grep "malformed mode in tree entry" err
test_grep "malformed mode in tree entry" err
'

test_expect_success PERL_TEST_HELPERS 'empty filename in tree' '
@ -221,7 +221,7 @@ test_expect_success PERL_TEST_HELPERS 'empty filename in tree' '
bin_oid=$(echo $hex_oid | hex2oct) &&
printf "100644 \0$bin_oid" >tree-with-empty-filename &&
test_must_fail git hash-object -t tree tree-with-empty-filename 2>err &&
grep "empty filename in tree entry" err
test_grep "empty filename in tree entry" err
'

test_expect_success PERL_TEST_HELPERS 'duplicate filename in tree' '
@ -232,7 +232,7 @@ test_expect_success PERL_TEST_HELPERS 'duplicate filename in tree' '
printf "100644 file\0$bin_oid"
} >tree-with-duplicate-filename &&
test_must_fail git hash-object -t tree tree-with-duplicate-filename 2>err &&
grep "duplicateEntries" err
test_grep "duplicateEntries" err
'

test_expect_success 'corrupt commit' '

View File

@ -196,7 +196,7 @@ test_expect_success 'read-tree will not throw away dirty changes, non-sparse' '
echo dirty >init.t &&
read_tree_u_must_fail -m -u HEAD^ &&
test_path_is_file init.t &&
grep -q dirty init.t
test_grep -q dirty init.t
'

test_expect_success 'read-tree will not throw away dirty changes, sparse' '
@ -207,7 +207,7 @@ test_expect_success 'read-tree will not throw away dirty changes, sparse' '
echo sub/added >.git/info/sparse-checkout &&
read_tree_u_must_fail -m -u HEAD^ &&
test_path_is_file init.t &&
grep -q dirty init.t
test_grep -q dirty init.t
'

test_expect_success 'read-tree updates worktree, dirty case' '
@ -215,7 +215,7 @@ test_expect_success 'read-tree updates worktree, dirty case' '
git checkout -f top &&
echo dirty >init.t &&
read_tree_u_must_fail -m -u HEAD^ &&
grep -q dirty init.t &&
test_grep -q dirty init.t &&
rm init.t
'

@ -224,7 +224,7 @@ test_expect_success 'read-tree removes worktree, dirty case' '
git checkout -f top &&
echo dirty >added &&
read_tree_u_must_succeed -m -u HEAD^ &&
grep -q dirty added
test_grep -q dirty added
'

test_expect_success 'read-tree adds to worktree, absent case' '
@ -240,7 +240,7 @@ test_expect_success 'read-tree adds to worktree, dirty case' '
mkdir sub &&
echo dirty >sub/added &&
read_tree_u_must_succeed -u -m HEAD^ &&
grep -q dirty sub/added
test_grep -q dirty sub/added
'

test_expect_success 'index removal and worktree narrowing at the same time' '

View File

@ -8,7 +8,7 @@ test_description='adding and checking out large blobs'
test_expect_success 'core.bigFileThreshold must be non-negative' '
: >input &&
test_must_fail git -c core.bigFileThreshold=-1 hash-object input >out 2>err &&
grep "bad numeric config value" err &&
test_grep "bad numeric config value" err &&
test_must_be_empty out
'

@ -148,12 +148,12 @@ test_expect_success 'diff --stat' '

test_expect_success 'diff' '
git diff HEAD^ HEAD >actual &&
grep "Binary files.*differ" actual
test_grep "Binary files.*differ" actual
'

test_expect_success 'diff --cached' '
git diff --cached HEAD^ >actual &&
grep "Binary files.*differ" actual
test_grep "Binary files.*differ" actual
'

test_expect_success 'hash-object' '

View File

@ -129,7 +129,7 @@ test_expect_success 'switching to cone mode with non-cone mode patterns' '
git sparse-checkout add dir &&
git config --worktree core.sparseCheckoutCone true &&
test_must_fail git sparse-checkout add dir 2>err &&
grep "existing sparse-checkout patterns do not use cone mode" err
test_grep "existing sparse-checkout patterns do not use cone mode" err
)
'

@ -803,7 +803,7 @@ test_expect_success 'cone mode clears ignored subdirectories' '
# When an untracked file is in the way, all untracked files
# (even ignored files) are preserved.
git -C repo sparse-checkout set folder1 2>err &&
grep "contains untracked files" err &&
test_grep "contains untracked files" err &&
test_path_is_file repo/deep/deeper2/ignored.o &&
test_path_is_file repo/deep/deeper2/untracked &&

@ -882,8 +882,8 @@ test_expect_success 'malformed cone-mode patterns' '
# of using the cone-mode translation to a set of directories.
git -C repo sparse-checkout list >actual 2>err &&
test_cmp repo/.git/info/sparse-checkout actual &&
grep "warning: your sparse-checkout file may have issues: pattern .* is repeated" err &&
grep "warning: disabling cone pattern matching" err
test_grep "warning: your sparse-checkout file may have issues: pattern .* is repeated" err &&
test_grep "warning: disabling cone pattern matching" err
'

test_expect_success 'set from subdir pays attention to prefix' '
@ -917,34 +917,34 @@ test_expect_success 'set from subdir in non-cone mode throws an error' '
git -C repo sparse-checkout disable &&
test_must_fail git -C repo/deep sparse-checkout set --no-cone deeper2 ../folder1 2>error &&

grep "run from the toplevel directory in non-cone mode" error
test_grep "run from the toplevel directory in non-cone mode" error
'

test_expect_success 'set from subdir in non-cone mode throws an error' '
git -C repo sparse-checkout set --no-cone deep/deeper2 &&
test_must_fail git -C repo/deep sparse-checkout add deeper1/deepest ../folder1 2>error &&

grep "run from the toplevel directory in non-cone mode" error
test_grep "run from the toplevel directory in non-cone mode" error
'

test_expect_success 'by default, cone mode will error out when passed files' '
git -C repo sparse-checkout reapply --cone &&
test_must_fail git -C repo sparse-checkout add .gitignore 2>error &&

grep ".gitignore.*is not a directory" error
test_grep ".gitignore.*is not a directory" error
'

test_expect_success 'error on mistyped command line options' '
test_must_fail git -C repo sparse-checkout add --sikp-checks .gitignore 2>error &&

grep "unknown option.*sikp-checks" error
test_grep "unknown option.*sikp-checks" error
'

test_expect_success 'by default, non-cone mode will warn on individual files' '
git -C repo sparse-checkout reapply --no-cone &&
git -C repo sparse-checkout add .gitignore 2>warning &&

grep "pass a leading slash before paths.*if you want a single file" warning
test_grep "pass a leading slash before paths.*if you want a single file" warning
'

test_expect_success 'setup bare repo' '
@ -1108,11 +1108,11 @@ test_expect_success 'clean' '
touch repo/folder1/extra/inside/file &&

test_must_fail git -C repo sparse-checkout clean 2>err &&
grep "refusing to clean" err &&
test_grep "refusing to clean" err &&

git -C repo config clean.requireForce true &&
test_must_fail git -C repo sparse-checkout clean 2>err &&
grep "refusing to clean" err &&
test_grep "refusing to clean" err &&

cat >expect <<-\EOF &&
Would remove deep/deeper2/
@ -1255,7 +1255,7 @@ test_expect_success 'sparse-checkout operations with merge conflicts' '
test_must_fail git merge -m "will-conflict" right &&

test_must_fail git sparse-checkout clean -f 2>err &&
grep "failed to convert index to a sparse index" err &&
test_grep "failed to convert index to a sparse index" err &&

echo merged >folder1/even/more/dirs/file &&
git add --sparse folder1 &&

View File

@ -454,10 +454,10 @@ test_expect_success 'add outside sparse cone' '
run_on_sparse ../edit-contents folder1/a &&
run_on_sparse ../edit-contents folder1/newfile &&
test_sparse_match test_must_fail git add folder1/a &&
grep "Disable or modify the sparsity rules" sparse-checkout-err &&
test_grep "Disable or modify the sparsity rules" sparse-checkout-err &&
test_sparse_unstaged folder1/a &&
test_sparse_match test_must_fail git add folder1/newfile &&
grep "Disable or modify the sparsity rules" sparse-checkout-err &&
test_grep "Disable or modify the sparsity rules" sparse-checkout-err &&
test_sparse_unstaged folder1/newfile
'

@ -509,13 +509,13 @@ test_expect_success 'status/add: outside sparse cone' '

# Adding the path outside of the sparse-checkout cone should fail.
test_sparse_match test_must_fail git add folder1/a &&
grep "Disable or modify the sparsity rules" sparse-checkout-err &&
test_grep "Disable or modify the sparsity rules" sparse-checkout-err &&
test_sparse_unstaged folder1/a &&
test_all_match git add --refresh folder1/a &&
test_must_be_empty sparse-checkout-err &&
test_sparse_unstaged folder1/a &&
test_sparse_match test_must_fail git add folder1/new &&
grep "Disable or modify the sparsity rules" sparse-checkout-err &&
test_grep "Disable or modify the sparsity rules" sparse-checkout-err &&
test_sparse_unstaged folder1/new &&
test_sparse_match git add --sparse folder1/a &&
test_sparse_match git add --sparse folder1/new &&
@ -661,8 +661,8 @@ test_expect_success 'checkout and reset (mixed)' '
# in sparse-checkout or sparse-index.
git -C full-checkout reset update-folder1 >full-checkout-out &&
test_sparse_match git reset update-folder1 &&
grep "M folder1/a" full-checkout-out &&
! grep "M folder1/a" sparse-checkout-out &&
test_grep "M folder1/a" full-checkout-out &&
test_grep ! "M folder1/a" sparse-checkout-out &&
run_on_sparse test_path_is_missing folder1
'

@ -880,8 +880,8 @@ test_expect_success 'update-index with directories' '
# update-index will exit silently when provided with a directory name
# containing a trailing slash
test_all_match git update-index deep/ folder1/ &&
grep "Ignoring path deep/" sparse-checkout-err &&
grep "Ignoring path folder1/" sparse-checkout-err &&
test_grep "Ignoring path deep/" sparse-checkout-err &&
test_grep "Ignoring path folder1/" sparse-checkout-err &&

# When update-index is given a directory name WITHOUT a trailing slash, it will
# behave in different ways depending on the status of the directory on disk:
@ -1067,7 +1067,7 @@ test_expect_success 'merge with conflict outside cone' '

# 2. Add the file with conflict markers
test_sparse_match test_must_fail git add folder1/a &&
grep "Disable or modify the sparsity rules" sparse-checkout-err &&
test_grep "Disable or modify the sparsity rules" sparse-checkout-err &&
test_sparse_unstaged folder1/a &&
test_all_match git add --sparse folder1/a &&
test_all_match git status --porcelain=v2 &&
@ -1076,7 +1076,7 @@ test_expect_success 'merge with conflict outside cone' '
# accept conflict markers as resolved content.
run_on_all mv folder2/a folder2/z &&
test_sparse_match test_must_fail git add folder2 &&
grep "Disable or modify the sparsity rules" sparse-checkout-err &&
test_grep "Disable or modify the sparsity rules" sparse-checkout-err &&
test_sparse_unstaged folder2/z &&
test_all_match git add --sparse folder2 &&
test_all_match git status --porcelain=v2 &&
@ -1107,7 +1107,7 @@ test_expect_success 'cherry-pick/rebase with conflict outside cone' '
# SKIP_WORKTREE bit from the index entry for folder1/a, we should
# warn that this is a problematic add.
test_sparse_match test_must_fail git add folder1/a &&
grep "Disable or modify the sparsity rules" sparse-checkout-err &&
test_grep "Disable or modify the sparsity rules" sparse-checkout-err &&
test_sparse_unstaged folder1/a &&
test_all_match git add --sparse folder1/a &&
test_all_match git status --porcelain=v2 &&
@ -1119,7 +1119,7 @@ test_expect_success 'cherry-pick/rebase with conflict outside cone' '
# existing index entry with the SKIP_WORKTREE bit cleared.
run_on_all mv folder2/a folder2/z &&
test_sparse_match test_must_fail git add folder2 &&
grep "Disable or modify the sparsity rules" sparse-checkout-err &&
test_grep "Disable or modify the sparsity rules" sparse-checkout-err &&
test_sparse_unstaged folder2/z &&
test_all_match git add --sparse folder2 &&
test_all_match git status --porcelain=v2 &&
@ -1266,7 +1266,7 @@ test_expect_success 'checkout-index with folders' '
run_on_all test_must_fail git checkout-index -f -- folder1/ &&
test_cmp full-checkout-err sparse-checkout-err &&
! test_cmp full-checkout-err sparse-index-err &&
grep "is a sparse directory" sparse-index-err
test_grep "is a sparse directory" sparse-index-err
'

test_expect_success 'checkout-index --all' '
@ -1374,8 +1374,8 @@ test_expect_success 'submodule handling' '
# having a submodule prevents "modules" from collapse
test_sparse_match git sparse-checkout set deep/deeper1 &&
git -C sparse-index ls-files --sparse --stage >cache &&
grep "100644 .* modules/a" cache &&
grep "160000 $(git -C initial-repo rev-parse HEAD) 0 modules/sub" cache
test_grep "100644 .* modules/a" cache &&
test_grep "160000 $(git -C initial-repo rev-parse HEAD) 0 modules/sub" cache
'

test_expect_success 'git apply functionality' '
@ -1392,7 +1392,7 @@ test_expect_success 'git apply functionality' '

# Apply a patch to a file outside the sparse definition
test_sparse_match test_must_fail git apply ../patch-outside &&
grep "No such file or directory" sparse-checkout-err &&
test_grep "No such file or directory" sparse-checkout-err &&

# But it works with --index and --cached
test_all_match git apply --index --stat ../patch-outside &&
@ -2013,9 +2013,9 @@ test_expect_success 'mv directory from out-of-cone to in-cone' '
test_all_match git status --porcelain=v2 &&
test_sparse_match git ls-files -t &&
git -C sparse-checkout ls-files -t >actual &&
grep -e "H deep/folder1/0/0/0" actual &&
grep -e "H deep/folder1/0/1" actual &&
grep -e "H deep/folder1/a" actual &&
test_grep -e "H deep/folder1/0/0/0" actual &&
test_grep -e "H deep/folder1/0/1" actual &&
test_grep -e "H deep/folder1/a" actual &&

test_all_match git reset --hard &&

@ -2025,8 +2025,8 @@ test_expect_success 'mv directory from out-of-cone to in-cone' '
test_sparse_match git status --porcelain=v2 &&
test_sparse_match git ls-files -t &&
git -C sparse-checkout ls-files -t >actual &&
grep -e "H deep/0/0/0" actual &&
grep -e "H deep/0/1" actual
test_grep -e "H deep/0/0/0" actual &&
test_grep -e "H deep/0/1" actual
'

test_expect_success 'rm pathspec inside sparse definition' '
@ -2517,7 +2517,7 @@ test_expect_success 'advice.sparseIndexExpanded' '
mkdir -p sparse-index/deep/deeper2/deepest &&
touch sparse-index/deep/deeper2/deepest/bogus &&
git -C sparse-index status 2>err &&
grep "The sparse index is expanding to a full index" err &&
test_grep "The sparse index is expanding to a full index" err &&

git -C sparse-index sparse-checkout disable 2>err &&
test_line_count = 0 err

View File

@ -856,7 +856,7 @@ test_expect_success 'renaming a section with an overly-long line' '
printf "[a] g = h\\n"
} >y &&
test_must_fail git config ${mode_prefix}rename-section -f y a xyz 2>err &&
grep "refusing to work with overly long line in .y. on line 2" err
test_grep "refusing to work with overly long line in .y. on line 2" err
'

cat >> .git/config << EOF
@ -1671,9 +1671,9 @@ test_expect_success 'git --config-env=key=envvar support' '

test_expect_success 'git --config-env with missing value' '
test_must_fail env ENVVAR=value git --config-env 2>error &&
grep "no config key given for --config-env" error &&
test_grep "no config key given for --config-env" error &&
test_must_fail env ENVVAR=value git --config-env config core.name 2>error &&
grep "invalid config format: config" error
test_grep "invalid config format: config" error
'

test_expect_success 'git --config-env fails with invalid parameters' '
@ -2104,7 +2104,7 @@ test_expect_success '--unset last key removes section (except if commented)' '
key = true
EOF
git config ${mode_unset} two.key &&
! grep two .git/config &&
test_grep ! two .git/config &&

q_to_tab >.git/config <<-\EOF &&
[one]
@ -2124,7 +2124,7 @@ test_expect_success '--unset last key removes section (except if commented)' '
Qkey = true
EOF
git config ${mode_unset} two.key &&
grep two .git/config &&
test_grep two .git/config &&

q_to_tab >.git/config <<-\EOF &&
[one]
@ -2655,7 +2655,7 @@ test_expect_success '--type rejects unknown specifiers' '

test_expect_success '--type=int requires at least one digit' '
test_must_fail git config --type int --default m some.key >out 2>error &&
grep "bad numeric config value" error &&
test_grep "bad numeric config value" error &&
test_must_be_empty out
'

@ -2967,12 +2967,12 @@ test_expect_success 'includeIf.hasconfig:remote.*.url forbids remote url in such

# test with any Git command
test_must_fail git -C hasremoteurlTest status 2>err &&
grep "fatal: remote URLs cannot be configured in file directly or indirectly included by includeIf.hasconfig:remote.*.url" err
test_grep "fatal: remote URLs cannot be configured in file directly or indirectly included by includeIf.hasconfig:remote.*.url" err
'

test_expect_success 'negated mode causes failure' '
test_must_fail git config --no-get 2>err &&
grep "unknown option \`no-get${SQ}" err
test_grep "unknown option \`no-get${SQ}" err
'

test_expect_success 'specifying multiple modes causes failure' '

View File

@ -353,7 +353,7 @@ test_expect_success 'include cycles are detected' '
git -C cycle --git-dir=. config include.path cycle &&
git config -f cycle/cycle include.path config &&
test_must_fail git -C cycle --git-dir=. config --get-all test.value 2>stderr &&
grep "exceeded maximum include depth" stderr
test_grep "exceeded maximum include depth" stderr
'

test_expect_success 'onbranch with unborn branch' '

View File

@ -180,7 +180,7 @@ test_expect_success 'find integer if value is non parse-able' '

test_expect_success 'non parse-able integer value during iteration' '
check_config expect_code 128 git_config_int lamb.head 2>result &&
grep "fatal: bad numeric config value .* in file \.git/config" result
test_grep "fatal: bad numeric config value .* in file \.git/config" result
'

test_expect_success 'find bool value for the entered key' '
@ -302,7 +302,7 @@ test_expect_success 'proper error on directory "files"' '
echo "Error (-1) reading configuration file a-directory." >expect &&
mkdir a-directory &&
test_expect_code 2 test-tool config configset_get_value foo.bar a-directory 2>output &&
grep "^warning:" output &&
test_grep "^warning:" output &&
grep "^Error" output >actual &&
test_cmp expect actual
'
@ -312,7 +312,7 @@ test_expect_success POSIXPERM,SANITY 'proper error on non-accessible files' '
test_when_finished "chmod +r .git/config" &&
echo "Error (-1) reading configuration file .git/config." >expect &&
test_expect_code 2 test-tool config configset_get_value foo.bar .git/config 2>output &&
grep "^warning:" output &&
test_grep "^warning:" output &&
grep "^Error" output >actual &&
test_cmp expect actual
'

View File

@ -92,7 +92,7 @@ test_expect_success "deleting current branch adds message to HEAD's log" '
git update-ref -m delete-$m -d $m &&
test_must_fail git show-ref --verify -q $m &&
test-tool ref-store main for-each-reflog-ent HEAD >actual &&
grep "delete-$m$" actual
test_grep "delete-$m$" actual
'

test_expect_success "deleting by HEAD adds message to HEAD's log" '
@ -102,7 +102,7 @@ test_expect_success "deleting by HEAD adds message to HEAD's log" '
git update-ref -m delete-by-head -d HEAD &&
test_must_fail git show-ref --verify -q $m &&
test-tool ref-store main for-each-reflog-ent HEAD >actual &&
grep "delete-by-head$" actual
test_grep "delete-by-head$" actual
'

test_expect_success 'update-ref does not create reflogs by default' '
@ -204,7 +204,10 @@ test_expect_success "move $m (by HEAD)" '
test_expect_success "delete $m (by HEAD) should remove both packed and loose $m" '
test_when_finished "git update-ref -d $m" &&
git update-ref -d HEAD $B &&
! grep "$m" .git/packed-refs &&
if test_have_prereq REFFILES
then
test_grep ! "$m" .git/packed-refs
fi &&
test_must_fail git show-ref --verify -q $m
'

@ -587,103 +590,103 @@ test_expect_success 'stdin works with no input' '
test_expect_success 'stdin fails on empty line' '
echo "" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: empty command in input" err
test_grep "fatal: empty command in input" err
'

test_expect_success 'stdin fails on only whitespace' '
echo " " >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: whitespace before command: " err
test_grep "fatal: whitespace before command: " err
'

test_expect_success 'stdin fails on leading whitespace' '
echo " create $a $m" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: whitespace before command: create $a $m" err
test_grep "fatal: whitespace before command: create $a $m" err
'

test_expect_success 'stdin fails on unknown command' '
echo "unknown $a" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: unknown command: unknown $a" err
test_grep "fatal: unknown command: unknown $a" err
'

test_expect_success 'stdin fails on unbalanced quotes' '
echo "create $a \"main" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: badly quoted argument: \\\"main" err
test_grep "fatal: badly quoted argument: \\\"main" err
'

test_expect_success 'stdin fails on invalid escape' '
echo "create $a \"ma\zn\"" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: badly quoted argument: \\\"ma\\\\zn\\\"" err
test_grep "fatal: badly quoted argument: \\\"ma\\\\zn\\\"" err
'

test_expect_success 'stdin fails on junk after quoted argument' '
echo "create \"$a\"main" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: unexpected character after quoted argument: \\\"$a\\\"main" err
test_grep "fatal: unexpected character after quoted argument: \\\"$a\\\"main" err
'

test_expect_success 'stdin fails create with no ref' '
echo "create " >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: create: missing <ref>" err
test_grep "fatal: create: missing <ref>" err
'

test_expect_success 'stdin fails create with no new value' '
echo "create $a" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: create $a: missing <new-oid>" err
test_grep "fatal: create $a: missing <new-oid>" err
'

test_expect_success 'stdin fails create with too many arguments' '
echo "create $a $m $m" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: create $a: extra input: $m" err
test_grep "fatal: create $a: extra input: $m" err
'

test_expect_success 'stdin fails update with no ref' '
echo "update " >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: update: missing <ref>" err
test_grep "fatal: update: missing <ref>" err
'

test_expect_success 'stdin fails update with no new value' '
echo "update $a" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: update $a: missing <new-oid>" err
test_grep "fatal: update $a: missing <new-oid>" err
'

test_expect_success 'stdin fails update with too many arguments' '
echo "update $a $m $m $m" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: update $a: extra input: $m" err
test_grep "fatal: update $a: extra input: $m" err
'

test_expect_success 'stdin fails delete with no ref' '
echo "delete " >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: delete: missing <ref>" err
test_grep "fatal: delete: missing <ref>" err
'

test_expect_success 'stdin fails delete with too many arguments' '
echo "delete $a $m $m" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: delete $a: extra input: $m" err
test_grep "fatal: delete $a: extra input: $m" err
'

test_expect_success 'stdin fails verify with too many arguments' '
echo "verify $a $m $m" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: verify $a: extra input: $m" err
test_grep "fatal: verify $a: extra input: $m" err
'

test_expect_success 'stdin fails option with unknown name' '
echo "option unknown" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: option unknown: unknown" err
test_grep "fatal: option unknown: unknown" err
'

test_expect_success 'stdin fails with duplicate refs' '
@ -771,28 +774,28 @@ test_expect_success 'stdin create ref works with path with space to blob' '
test_expect_success 'stdin update ref fails with wrong old value' '
echo "update $c $m $m~1" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: cannot lock ref '"'"'$c'"'"'" err &&
test_grep "fatal: cannot lock ref '"'"'$c'"'"'" err &&
test_must_fail git rev-parse --verify -q $c
'

test_expect_success 'stdin update ref fails with bad old value' '
echo "update $c $m does-not-exist" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: update $c: invalid <old-oid>: does-not-exist" err &&
test_grep "fatal: update $c: invalid <old-oid>: does-not-exist" err &&
test_must_fail git rev-parse --verify -q $c
'

test_expect_success 'stdin create ref fails with bad new value' '
echo "create $c does-not-exist" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: create $c: invalid <new-oid>: does-not-exist" err &&
test_grep "fatal: create $c: invalid <new-oid>: does-not-exist" err &&
test_must_fail git rev-parse --verify -q $c
'

test_expect_success 'stdin create ref fails with zero new value' '
echo "create $c " >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: create $c: zero <new-oid>" err &&
test_grep "fatal: create $c: zero <new-oid>" err &&
test_must_fail git rev-parse --verify -q $c
'

@ -807,7 +810,7 @@ test_expect_success 'stdin update ref works with right old value' '
test_expect_success 'stdin delete ref fails with wrong old value' '
echo "delete $a $m~1" >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: cannot lock ref '"'"'$a'"'"'" err &&
test_grep "fatal: cannot lock ref '"'"'$a'"'"'" err &&
git rev-parse $m >expect &&
git rev-parse $a >actual &&
test_cmp expect actual
@ -816,7 +819,7 @@ test_expect_success 'stdin delete ref fails with wrong old value' '
test_expect_success 'stdin delete ref fails with zero old value' '
echo "delete $a " >stdin &&
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: delete $a: zero <old-oid>" err &&
test_grep "fatal: delete $a: zero <old-oid>" err &&
git rev-parse $m >expect &&
git rev-parse $a >actual &&
test_cmp expect actual
@ -977,7 +980,7 @@ test_expect_success 'stdin update refs fails with wrong old value' '
update $c ''
EOF
test_must_fail git update-ref --stdin <stdin 2>err &&
grep "fatal: cannot lock ref '"'"'$c'"'"'" err &&
test_grep "fatal: cannot lock ref '"'"'$c'"'"'" err &&
git rev-parse $m >expect &&
git rev-parse $a >actual &&
test_cmp expect actual &&
@ -1010,123 +1013,123 @@ test_expect_success 'stdin -z works on empty input' '
test_expect_success 'stdin -z fails on empty line' '
echo "" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: whitespace before command: " err
test_grep "fatal: whitespace before command: " err
'

test_expect_success 'stdin -z fails on empty command' '
printf $F "" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: empty command in input" err
test_grep "fatal: empty command in input" err
'

test_expect_success 'stdin -z fails on only whitespace' '
printf $F " " >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: whitespace before command: " err
test_grep "fatal: whitespace before command: " err
'

test_expect_success 'stdin -z fails on leading whitespace' '
printf $F " create $a" "$m" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: whitespace before command: create $a" err
test_grep "fatal: whitespace before command: create $a" err
'

test_expect_success 'stdin -z fails on unknown command' '
printf $F "unknown $a" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: unknown command: unknown $a" err
test_grep "fatal: unknown command: unknown $a" err
'

test_expect_success 'stdin -z fails create with no ref' '
printf $F "create " >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: create: missing <ref>" err
test_grep "fatal: create: missing <ref>" err
'

test_expect_success 'stdin -z fails create with no new value' '
printf $F "create $a" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: create $a: unexpected end of input when reading <new-oid>" err
test_grep "fatal: create $a: unexpected end of input when reading <new-oid>" err
'

test_expect_success 'stdin -z fails create with too many arguments' '
printf $F "create $a" "$m" "$m" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: unknown command: $m" err
test_grep "fatal: unknown command: $m" err
'

test_expect_success 'stdin -z fails update with no ref' '
printf $F "update " >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: update: missing <ref>" err
test_grep "fatal: update: missing <ref>" err
'

test_expect_success 'stdin -z fails update with too few args' '
printf $F "update $a" "$m" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: update $a: unexpected end of input when reading <old-oid>" err
test_grep "fatal: update $a: unexpected end of input when reading <old-oid>" err
'

test_expect_success 'stdin -z emits warning with empty new value' '
git update-ref $a $m &&
printf $F "update $a" "" "" >stdin &&
git update-ref -z --stdin <stdin 2>err &&
grep "warning: update $a: missing <new-oid>, treating as zero" err &&
test_grep "warning: update $a: missing <new-oid>, treating as zero" err &&
test_must_fail git rev-parse --verify -q $a
'

test_expect_success 'stdin -z fails update with no new value' '
printf $F "update $a" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: update $a: unexpected end of input when reading <new-oid>" err
test_grep "fatal: update $a: unexpected end of input when reading <new-oid>" err
'

test_expect_success 'stdin -z fails update with no old value' '
printf $F "update $a" "$m" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: update $a: unexpected end of input when reading <old-oid>" err
test_grep "fatal: update $a: unexpected end of input when reading <old-oid>" err
'

test_expect_success 'stdin -z fails update with too many arguments' '
printf $F "update $a" "$m" "$m" "$m" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: unknown command: $m" err
test_grep "fatal: unknown command: $m" err
'

test_expect_success 'stdin -z fails delete with no ref' '
printf $F "delete " >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: delete: missing <ref>" err
test_grep "fatal: delete: missing <ref>" err
'

test_expect_success 'stdin -z fails delete with no old value' '
printf $F "delete $a" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: delete $a: unexpected end of input when reading <old-oid>" err
test_grep "fatal: delete $a: unexpected end of input when reading <old-oid>" err
'

test_expect_success 'stdin -z fails delete with too many arguments' '
printf $F "delete $a" "$m" "$m" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: unknown command: $m" err
test_grep "fatal: unknown command: $m" err
'

test_expect_success 'stdin -z fails verify with too many arguments' '
printf $F "verify $a" "$m" "$m" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: unknown command: $m" err
test_grep "fatal: unknown command: $m" err
'

test_expect_success 'stdin -z fails verify with no old value' '
printf $F "verify $a" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: verify $a: unexpected end of input when reading <old-oid>" err
test_grep "fatal: verify $a: unexpected end of input when reading <old-oid>" err
'

test_expect_success 'stdin -z fails option with unknown name' '
printf $F "option unknown" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: option unknown: unknown" err
test_grep "fatal: option unknown: unknown" err
'

test_expect_success 'stdin -z fails with duplicate refs' '
@ -1172,14 +1175,14 @@ test_expect_success 'stdin -z create ref works with path with space to blob' '
test_expect_success 'stdin -z update ref fails with wrong old value' '
printf $F "update $c" "$m" "$m~1" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: cannot lock ref '"'"'$c'"'"'" err &&
test_grep "fatal: cannot lock ref '"'"'$c'"'"'" err &&
test_must_fail git rev-parse --verify -q $c
'

test_expect_success 'stdin -z update ref fails with bad old value' '
printf $F "update $c" "$m" "does-not-exist" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: update $c: invalid <old-oid>: does-not-exist" err &&
test_grep "fatal: update $c: invalid <old-oid>: does-not-exist" err &&
test_must_fail git rev-parse --verify -q $c
'

@ -1188,7 +1191,7 @@ test_expect_success 'stdin -z create ref fails when ref exists' '
git rev-parse "$c" >expect &&
printf $F "create $c" "$m~1" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: cannot lock ref '"'"'$c'"'"'" err &&
test_grep "fatal: cannot lock ref '"'"'$c'"'"'" err &&
git rev-parse "$c" >actual &&
test_cmp expect actual
'
@ -1197,28 +1200,28 @@ test_expect_success 'stdin -z create ref fails with bad new value' '
git update-ref -d "$c" &&
printf $F "create $c" "does-not-exist" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: create $c: invalid <new-oid>: does-not-exist" err &&
test_grep "fatal: create $c: invalid <new-oid>: does-not-exist" err &&
test_must_fail git rev-parse --verify -q $c
'

test_expect_success 'stdin -z create ref fails with empty new value' '
printf $F "create $c" "" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: create $c: missing <new-oid>" err &&
test_grep "fatal: create $c: missing <new-oid>" err &&
test_must_fail git rev-parse --verify -q $c
'

test_expect_success 'stdin -z create ref fails with non commit object' '
printf $F "create $c" "$(test_oid 001)" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: trying to write ref ${SQ}$c${SQ} with nonexistent object" err &&
test_grep "fatal: trying to write ref ${SQ}$c${SQ} with nonexistent object" err &&
test_must_fail git rev-parse --verify -q $c
'

test_expect_success 'stdin -z update ref fails with non commit object' '
printf $F "update $b" "$(test_oid 001)" "" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: trying to write ref ${SQ}$b${SQ} with nonexistent object" err &&
test_grep "fatal: trying to write ref ${SQ}$b${SQ} with nonexistent object" err &&
test_must_fail git rev-parse --verify -q $c
'

@ -1233,7 +1236,7 @@ test_expect_success 'stdin -z update ref works with right old value' '
test_expect_success 'stdin -z delete ref fails with wrong old value' '
printf $F "delete $a" "$m~1" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: cannot lock ref '"'"'$a'"'"'" err &&
test_grep "fatal: cannot lock ref '"'"'$a'"'"'" err &&
git rev-parse $m >expect &&
git rev-parse $a >actual &&
test_cmp expect actual
@ -1242,7 +1245,7 @@ test_expect_success 'stdin -z delete ref fails with wrong old value' '
test_expect_success 'stdin -z delete ref fails with zero old value' '
printf $F "delete $a" "$Z" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: delete $a: zero <old-oid>" err &&
test_grep "fatal: delete $a: zero <old-oid>" err &&
git rev-parse $m >expect &&
git rev-parse $a >actual &&
test_cmp expect actual
@ -1348,7 +1351,7 @@ test_expect_success 'stdin -z update refs fails with wrong old value' '
git update-ref $c $m &&
printf $F "update $a" "$m" "$m" "update $b" "$m" "$m" "update $c" "$m" "$Z" >stdin &&
test_must_fail git update-ref -z --stdin <stdin 2>err &&
grep "fatal: cannot lock ref '"'"'$c'"'"'" err &&
test_grep "fatal: cannot lock ref '"'"'$c'"'"'" err &&
git rev-parse $m >expect &&
git rev-parse $a >actual &&
test_cmp expect actual &&
@ -1427,13 +1430,13 @@ test_expect_success 'handle per-worktree refs in refs/bisect' '
cd worktree &&
git commit --allow-empty -m "test commit" &&
git for-each-ref >for-each-ref.out &&
! grep refs/bisect for-each-ref.out &&
test_grep ! refs/bisect for-each-ref.out &&
git update-ref refs/bisect/something HEAD &&
git rev-parse refs/bisect/something >../worktree-head &&
git for-each-ref | grep refs/bisect/something
) &&
git show-ref >actual &&
! grep 'refs/bisect' actual &&
test_grep ! 'refs/bisect' actual &&
test_must_fail git rev-parse refs/bisect/something &&
git update-ref refs/bisect/something HEAD &&
git rev-parse refs/bisect/something >main-head &&
@ -1489,7 +1492,7 @@ test_expect_success 'transaction exits on multiple aborts' '
test_must_fail git update-ref --stdin <stdin >actual 2>err &&
printf "%s: ok\n" abort >expect &&
test_cmp expect actual &&
grep "fatal: transaction is closed" err
test_grep "fatal: transaction is closed" err
'

test_expect_success 'transaction exits on start after prepare' '
@ -1500,7 +1503,7 @@ test_expect_success 'transaction exits on start after prepare' '
test_must_fail git update-ref --stdin <stdin 2>err >actual &&
printf "%s: ok\n" prepare >expect &&
test_cmp expect actual &&
grep "fatal: prepared transactions can only be closed" err
test_grep "fatal: prepared transactions can only be closed" err
'

test_expect_success 'transaction handles empty abort with missing prepare' '
@ -1661,7 +1664,7 @@ test_expect_success PIPE 'transaction flushes status updates' '

# This must now fail given that we have locked the ref.
test_must_fail git update-ref refs/heads/flush $B 2>stderr &&
grep "fatal: update_ref failed for ref ${SQ}refs/heads/flush${SQ}: cannot lock ref" stderr &&
test_grep "fatal: update_ref failed for ref ${SQ}refs/heads/flush${SQ}: cannot lock ref" stderr &&

echo commit >&9 &&
echo "commit: ok" >expected &&
@ -1687,7 +1690,7 @@ do
git symbolic-ref refs/heads/symref $a &&
format_command $type "symref-verify refs/heads/symref" "$a" >stdin &&
test_must_fail git update-ref --stdin $type <stdin 2>err &&
grep "fatal: symref-verify: cannot operate with deref mode" err
test_grep "fatal: symref-verify: cannot operate with deref mode" err
'

test_expect_success "stdin $type symref-verify fails with too many arguments" '
@ -1695,9 +1698,9 @@ do
test_must_fail git update-ref --stdin $type --no-deref <stdin 2>err &&
if test "$type" = "-z"
then
grep "fatal: unknown command: $a" err
test_grep "fatal: unknown command: $a" err
else
grep "fatal: symref-verify refs/heads/symref: extra input: $a" err
test_grep "fatal: symref-verify refs/heads/symref: extra input: $a" err
fi
'

@ -1730,7 +1733,7 @@ do
test-tool ref-store main for-each-reflog-ent refs/heads/symref >before &&
format_command $type "symref-verify refs/heads/missing" "refs/heads/unknown" >stdin &&
test_must_fail git update-ref --stdin $type --no-deref <stdin 2>err &&
grep "fatal: cannot lock ref ${SQ}refs/heads/missing${SQ}: unable to resolve reference ${SQ}refs/heads/missing${SQ}" err &&
test_grep "fatal: cannot lock ref ${SQ}refs/heads/missing${SQ}: unable to resolve reference ${SQ}refs/heads/missing${SQ}" err &&
test_must_fail git rev-parse --verify -q refs/heads/missing &&
test-tool ref-store main for-each-reflog-ent refs/heads/symref >after &&
test_cmp before after
@ -1756,13 +1759,13 @@ do
git symbolic-ref refs/heads/symref $a &&
format_command $type "symref-delete refs/heads/symref" "$a" >stdin &&
test_must_fail git update-ref --stdin $type <stdin 2>err &&
grep "fatal: symref-delete: cannot operate with deref mode" err
test_grep "fatal: symref-delete: cannot operate with deref mode" err
'

test_expect_success "stdin $type symref-delete fails with no ref" '
format_command $type "symref-delete " >stdin &&
test_must_fail git update-ref --stdin $type --no-deref <stdin 2>err &&
grep "fatal: symref-delete: missing <ref>" err
test_grep "fatal: symref-delete: missing <ref>" err
'

test_expect_success "stdin $type symref-delete fails deleting regular ref" '
@ -1770,7 +1773,7 @@ do
git update-ref refs/heads/regularref $a &&
format_command $type "symref-delete refs/heads/regularref" "$a" >stdin &&
test_must_fail git update-ref --stdin $type --no-deref <stdin 2>err &&
grep "fatal: cannot lock ref ${SQ}refs/heads/regularref${SQ}: expected symref with target ${SQ}$a${SQ}: but is a regular ref" err
test_grep "fatal: cannot lock ref ${SQ}refs/heads/regularref${SQ}: expected symref with target ${SQ}$a${SQ}: but is a regular ref" err
'

test_expect_success "stdin $type symref-delete fails with too many arguments" '
@ -1778,16 +1781,16 @@ do
test_must_fail git update-ref --stdin $type --no-deref <stdin 2>err &&
if test "$type" = "-z"
then
grep "fatal: unknown command: $a" err
test_grep "fatal: unknown command: $a" err
else
grep "fatal: symref-delete refs/heads/symref: extra input: $a" err
test_grep "fatal: symref-delete refs/heads/symref: extra input: $a" err
fi
'

test_expect_success "stdin $type symref-delete fails with wrong old value" '
format_command $type "symref-delete refs/heads/symref" "$m" >stdin &&
test_must_fail git update-ref --stdin $type --no-deref <stdin 2>err &&
grep "fatal: verifying symref target: ${SQ}refs/heads/symref${SQ}: is at $a but expected refs/heads/main" err &&
test_grep "fatal: verifying symref target: ${SQ}refs/heads/symref${SQ}: is at $a but expected refs/heads/main" err &&
git symbolic-ref refs/heads/symref >expect &&
echo $a >actual &&
test_cmp expect actual
@ -1825,9 +1828,9 @@ do
test_must_fail git update-ref --stdin $type --no-deref <stdin 2>err &&
if test "$type" = "-z"
then
grep "fatal: unknown command: $a" err
test_grep "fatal: unknown command: $a" err
else
grep "fatal: symref-create refs/heads/symref: extra input: $a" err
test_grep "fatal: symref-create refs/heads/symref: extra input: $a" err
fi
'

@ -1890,16 +1893,16 @@ do
test_must_fail git update-ref --stdin $type --no-deref <stdin 2>err &&
if test "$type" = "-z"
then
grep "fatal: unknown command: $a" err
test_grep "fatal: unknown command: $a" err
else
grep "fatal: symref-update refs/heads/symref: extra input: $a" err
test_grep "fatal: symref-update refs/heads/symref: extra input: $a" err
fi
'

test_expect_success "stdin $type symref-update fails with wrong old value argument" '
format_command $type "symref-update refs/heads/symref" "$a" "foo" "$a" "$a" >stdin &&
test_must_fail git update-ref --stdin $type --no-deref <stdin 2>err &&
grep "fatal: symref-update refs/heads/symref: invalid arg ${SQ}foo${SQ} for old value" err
test_grep "fatal: symref-update refs/heads/symref: invalid arg ${SQ}foo${SQ} for old value" err
'

test_expect_success "stdin $type symref-update creates with zero old value" '
@ -1935,7 +1938,7 @@ do
git symbolic-ref refs/heads/symref $a &&
format_command $type "symref-update refs/heads/symref" "$m" "ref" "$b" >stdin &&
test_must_fail git update-ref --stdin $type --no-deref <stdin 2>err &&
grep "fatal: verifying symref target: ${SQ}refs/heads/symref${SQ}: is at $a but expected $b" err &&
test_grep "fatal: verifying symref target: ${SQ}refs/heads/symref${SQ}: is at $a but expected $b" err &&
test_must_fail git rev-parse --verify -q $c
'

@ -2010,7 +2013,7 @@ do
git symbolic-ref --no-recurse refs/heads/symref >actual &&
test_cmp expect actual &&
test-tool ref-store main for-each-reflog-ent refs/heads/symref >actual &&
grep "$Z $(git rev-parse $a)" actual
test_grep "$Z $(git rev-parse $a)" actual
'

test_expect_success "stdin $type symref-update regular ref to symref with correct old-oid" '
@ -2022,7 +2025,7 @@ do
git symbolic-ref --no-recurse refs/heads/regularref >actual &&
test_cmp expect actual &&
test-tool ref-store main for-each-reflog-ent refs/heads/regularref >actual &&
grep "$(git rev-parse $a) $(git rev-parse $a)" actual
test_grep "$(git rev-parse $a) $(git rev-parse $a)" actual
'

test_expect_success "stdin $type symref-update regular ref to symref fails with wrong old-oid" '
@ -2030,7 +2033,7 @@ do
git update-ref --no-deref refs/heads/regularref $a &&
format_command $type "symref-update refs/heads/regularref" "$a" "oid" "$(git rev-parse refs/heads/target2)" >stdin &&
test_must_fail git update-ref --stdin $type <stdin 2>err &&
grep "fatal: cannot lock ref ${SQ}refs/heads/regularref${SQ}: is at $(git rev-parse $a) but expected $(git rev-parse refs/heads/target2)" err &&
test_grep "fatal: cannot lock ref ${SQ}refs/heads/regularref${SQ}: is at $(git rev-parse $a) but expected $(git rev-parse refs/heads/target2)" err &&
echo $(git rev-parse $a) >expect &&
git rev-parse refs/heads/regularref >actual &&
test_cmp expect actual
@ -2041,7 +2044,7 @@ do
git update-ref --no-deref refs/heads/regularref $a &&
format_command $type "symref-update refs/heads/regularref" "$a" "oid" "not-a-ref-oid" >stdin &&
test_must_fail git update-ref --stdin $type <stdin 2>err &&
grep "fatal: symref-update refs/heads/regularref: invalid oid: not-a-ref-oid" err &&
test_grep "fatal: symref-update refs/heads/regularref: invalid oid: not-a-ref-oid" err &&
echo $(git rev-parse $a) >expect &&
git rev-parse refs/heads/regularref >actual &&
test_cmp expect actual
@ -2052,7 +2055,7 @@ do
git symbolic-ref refs/heads/symref refs/heads/target2 &&
format_command $type "symref-update refs/heads/symref" "$a" "oid" "$Z" >stdin &&
test_must_fail git update-ref --stdin $type <stdin 2>err &&
grep "fatal: cannot lock ref ${SQ}refs/heads/symref${SQ}: reference already exists" err &&
test_grep "fatal: cannot lock ref ${SQ}refs/heads/symref${SQ}: reference already exists" err &&
echo refs/heads/target2 >expect &&
git symbolic-ref refs/heads/symref >actual &&
test_cmp expect actual
@ -2072,7 +2075,7 @@ do
git symbolic-ref --no-recurse refs/heads/symref >actual &&
test_cmp expect actual &&
test-tool ref-store main for-each-reflog-ent refs/heads/symref >actual &&
grep "$(git rev-parse $a) $(git rev-parse $a)" actual
test_grep "$(git rev-parse $a) $(git rev-parse $a)" actual
'

test_expect_success "stdin $type symref-update regular ref to symref" '
@ -2084,7 +2087,7 @@ do
git symbolic-ref --no-recurse refs/heads/regularref >actual &&
test_cmp expect actual &&
test-tool ref-store main for-each-reflog-ent refs/heads/regularref >actual &&
grep "$(git rev-parse $a) $(git rev-parse $a)" actual
test_grep "$(git rev-parse $a) $(git rev-parse $a)" actual
'

test_expect_success "stdin $type batch-updates" '

View File

@ -213,19 +213,19 @@ test_expect_success 'show-ref --verify with dangling ref' '

test_expect_success 'show-ref sub-modes are mutually exclusive' '
test_must_fail git show-ref --verify --exclude-existing 2>err &&
grep "verify" err &&
grep "exclude-existing" err &&
grep "cannot be used together" err &&
test_grep "verify" err &&
test_grep "exclude-existing" err &&
test_grep "cannot be used together" err &&

test_must_fail git show-ref --verify --exists 2>err &&
grep "verify" err &&
grep "exists" err &&
grep "cannot be used together" err &&
test_grep "verify" err &&
test_grep "exists" err &&
test_grep "cannot be used together" err &&

test_must_fail git show-ref --exclude-existing --exists 2>err &&
grep "exclude-existing" err &&
grep "exists" err &&
grep "cannot be used together" err
test_grep "exclude-existing" err &&
test_grep "exists" err &&
test_grep "cannot be used together" err
'

test_done

View File

@ -108,12 +108,12 @@ test_expect_success setup '

test_expect_success 'correct usage on sub-command -h' '
git reflog expire -h >err &&
grep "git reflog expire" err
test_grep "git reflog expire" err
'

test_expect_success 'correct usage on "git reflog show -h"' '
git reflog show -h >err &&
grep -F "git reflog [show]" err
test_grep -F "git reflog [show]" err
'

test_expect_success 'pass through -- to sub-command' '

View File

@ -32,7 +32,7 @@ test_expect_success 'ambiguous main-worktree/HEAD' '
test_when_finished git update-ref -d refs/heads/main-worktree/HEAD &&
git update-ref refs/heads/main-worktree/HEAD $(git rev-parse HEAD) &&
git rev-parse main-worktree/HEAD 2>warn &&
grep "main-worktree/HEAD.*ambiguous" warn
test_grep "main-worktree/HEAD.*ambiguous" warn
'

test_expect_success 'resolve worktrees/xx/HEAD' '
@ -45,7 +45,7 @@ test_expect_success 'ambiguous worktrees/xx/HEAD' '
git update-ref refs/heads/worktrees/wt1/HEAD $(git rev-parse HEAD) &&
test_when_finished git update-ref -d refs/heads/worktrees/wt1/HEAD &&
git rev-parse worktrees/wt1/HEAD 2>warn &&
grep "worktrees/wt1/HEAD.*ambiguous" warn
test_grep "worktrees/wt1/HEAD.*ambiguous" warn
'

test_expect_success 'reflog of main-worktree/HEAD' '

Some files were not shown because too many files have changed in this diff Show More