aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/diff-options.txt5
-rw-r--r--Documentation/git-grep.txt11
-rw-r--r--Documentation/git-rebase.txt5
-rw-r--r--archive-zip.c17
-rw-r--r--builtin-apply.c4
-rw-r--r--builtin-grep.c4
-rw-r--r--combine-diff.c9
-rw-r--r--diff.c30
-rw-r--r--diff.h16
-rwxr-xr-xgit-bisect.sh11
-rwxr-xr-xgit-fetch.sh41
-rwxr-xr-xgit-merge.sh2
-rwxr-xr-xgit-rebase.sh14
-rwxr-xr-xgit-resolve.sh2
-rwxr-xr-xgit-send-email.perl35
-rwxr-xr-xgit-svnimport.perl93
-rw-r--r--gitweb/README16
-rwxr-xr-xgitweb/gitweb.perl89
-rw-r--r--grep.c114
-rw-r--r--grep.h2
-rw-r--r--revision.c9
-rw-r--r--t/t4013/diff.diff-tree_--pretty_--root_--summary_initial2
-rwxr-xr-xt/t5000-tar-tree.sh35
23 files changed, 434 insertions, 132 deletions
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index 7b7b9e8ce..e112172ca 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -16,6 +16,11 @@
The width of the filename part can be controlled by
giving another width to it separated by a comma.
+--numstat::
+ Similar to \--stat, but shows number of added and
+ deleted lines in decimal notation and pathname without
+ abbreviation, to make it more machine friendly.
+
--summary::
Output a condensed summary of extended header information
such as creations, renames and mode changes.
diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt
index d8af4d961..bfbece986 100644
--- a/Documentation/git-grep.txt
+++ b/Documentation/git-grep.txt
@@ -14,7 +14,7 @@ SYNOPSIS
[-v | --invert-match] [-h|-H] [--full-name]
[-E | --extended-regexp] [-G | --basic-regexp] [-F | --fixed-strings]
[-n] [-l | --files-with-matches] [-L | --files-without-match]
- [-c | --count]
+ [-c | --count] [--all-match]
[-A <post-context>] [-B <pre-context>] [-C <context>]
[-f <file>] [-e] <pattern> [--and|--or|--not|(|)|-e <pattern>...]
[<tree>...]
@@ -96,6 +96,11 @@ OPTIONS
higher precedence than `--or`. `-e` has to be used for all
patterns.
+--all-match::
+ When giving multiple pattern expressions combined with `--or`,
+ this flag is specified to limit the match to files that
+ have lines to match all of them.
+
`<tree>...`::
Search blobs in the trees for specified patterns.
@@ -111,6 +116,10 @@ git grep -e \'#define\' --and \( -e MAX_PATH -e PATH_MAX \)::
Looks for a line that has `#define` and either `MAX_PATH` or
`PATH_MAX`.
+git grep --all-match -e NODE -e Unexpected::
+ Looks for a line that has `NODE` or `Unexpected` in
+ files that have lines that match both.
+
Author
------
Originally written by Linus Torvalds <torvalds@osdl.org>, later
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 9d7bcaa38..10f2924f4 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -7,7 +7,7 @@ git-rebase - Rebase local commits to a new head
SYNOPSIS
--------
-'git-rebase' [--merge] [--onto <newbase>] <upstream> [<branch>]
+'git-rebase' [-v] [--merge] [--onto <newbase>] <upstream> [<branch>]
'git-rebase' --continue | --skip | --abort
@@ -121,6 +121,9 @@ OPTIONS
is used instead (`git-merge-recursive` when merging a single
head, `git-merge-octopus` otherwise). This implies --merge.
+-v, \--verbose::
+ Display a diffstat of what changed upstream since the last rebase.
+
include::merge-strategies.txt[]
NOTES
diff --git a/archive-zip.c b/archive-zip.c
index 3ffdad68d..28e7352e9 100644
--- a/archive-zip.c
+++ b/archive-zip.c
@@ -145,6 +145,7 @@ static int write_zip_entry(const unsigned char *sha1,
{
struct zip_local_header header;
struct zip_dir_header dirent;
+ unsigned long attr2;
unsigned long compressed_size;
unsigned long uncompressed_size;
unsigned long crc;
@@ -172,12 +173,16 @@ static int write_zip_entry(const unsigned char *sha1,
if (S_ISDIR(mode)) {
method = 0;
+ attr2 = 16;
result = READ_TREE_RECURSIVE;
out = NULL;
uncompressed_size = 0;
compressed_size = 0;
- } else if (S_ISREG(mode)) {
- method = zlib_compression_level == 0 ? 0 : 8;
+ } else if (S_ISREG(mode) || S_ISLNK(mode)) {
+ method = 0;
+ attr2 = S_ISLNK(mode) ? ((mode | 0777) << 16) : 0;
+ if (S_ISREG(mode) && zlib_compression_level != 0)
+ method = 8;
result = 0;
buffer = read_sha1_file(sha1, type, &size);
if (!buffer)
@@ -213,8 +218,8 @@ static int write_zip_entry(const unsigned char *sha1,
}
copy_le32(dirent.magic, 0x02014b50);
- copy_le16(dirent.creator_version, 0);
- copy_le16(dirent.version, 20);
+ copy_le16(dirent.creator_version, S_ISLNK(mode) ? 0x0317 : 0);
+ copy_le16(dirent.version, 10);
copy_le16(dirent.flags, 0);
copy_le16(dirent.compression_method, method);
copy_le16(dirent.mtime, zip_time);
@@ -227,7 +232,7 @@ static int write_zip_entry(const unsigned char *sha1,
copy_le16(dirent.comment_length, 0);
copy_le16(dirent.disk, 0);
copy_le16(dirent.attr1, 0);
- copy_le32(dirent.attr2, 0);
+ copy_le32(dirent.attr2, attr2);
copy_le32(dirent.offset, zip_offset);
memcpy(zip_dir + zip_dir_offset, &dirent, sizeof(struct zip_dir_header));
zip_dir_offset += sizeof(struct zip_dir_header);
@@ -236,7 +241,7 @@ static int write_zip_entry(const unsigned char *sha1,
zip_dir_entries++;
copy_le32(header.magic, 0x04034b50);
- copy_le16(header.version, 20);
+ copy_le16(header.version, 10);
copy_le16(header.flags, 0);
copy_le16(header.compression_method, method);
copy_le16(header.mtime, zip_time);
diff --git a/builtin-apply.c b/builtin-apply.c
index 11a5277a6..11397f550 100644
--- a/builtin-apply.c
+++ b/builtin-apply.c
@@ -360,7 +360,7 @@ static int gitdiff_hdrend(const char *line, struct patch *patch)
static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, const char *oldnew)
{
if (!orig_name && !isnull)
- return find_name(line, NULL, 1, 0);
+ return find_name(line, NULL, 1, TERM_TAB);
if (orig_name) {
int len;
@@ -370,7 +370,7 @@ static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name,
len = strlen(name);
if (isnull)
die("git-apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr);
- another = find_name(line, NULL, 1, 0);
+ another = find_name(line, NULL, 1, TERM_TAB);
if (!another || memcmp(another, name, len))
die("git-apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr);
free(another);
diff --git a/builtin-grep.c b/builtin-grep.c
index 4205e5d38..ad7dc00cd 100644
--- a/builtin-grep.c
+++ b/builtin-grep.c
@@ -596,6 +596,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
GREP_CLOSE_PAREN);
continue;
}
+ if (!strcmp("--all-match", arg)) {
+ opt.all_match = 1;
+ continue;
+ }
if (!strcmp("-e", arg)) {
if (1 < argc) {
append_grep_pattern(&opt, argv[1],
diff --git a/combine-diff.c b/combine-diff.c
index 46d9121ba..65c786807 100644
--- a/combine-diff.c
+++ b/combine-diff.c
@@ -856,8 +856,10 @@ void diff_tree_combined(const unsigned char *sha1,
/* show stat against the first parent even
* when doing combined diff.
*/
- if (i == 0 && opt->output_format & DIFF_FORMAT_DIFFSTAT)
- diffopts.output_format = DIFF_FORMAT_DIFFSTAT;
+ int stat_opt = (opt->output_format &
+ (DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIFFSTAT));
+ if (i == 0 && stat_opt)
+ diffopts.output_format = stat_opt;
else
diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
diff_tree_sha1(parent[i], sha1, "", &diffopts);
@@ -887,7 +889,8 @@ void diff_tree_combined(const unsigned char *sha1,
}
needsep = 1;
}
- else if (opt->output_format & DIFF_FORMAT_DIFFSTAT)
+ else if (opt->output_format &
+ (DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIFFSTAT))
needsep = 1;
if (opt->output_format & DIFF_FORMAT_PATCH) {
if (needsep)
diff --git a/diff.c b/diff.c
index fb8243261..33153787b 100644
--- a/diff.c
+++ b/diff.c
@@ -795,6 +795,23 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options)
set, total_files, adds, dels, reset);
}
+static void show_numstat(struct diffstat_t* data, struct diff_options *options)
+{
+ int i;
+
+ for (i = 0; i < data->nr; i++) {
+ struct diffstat_file *file = data->files[i];
+
+ printf("%d\t%d\t", file->added, file->deleted);
+ if (options->line_termination &&
+ quote_c_style(file->name, NULL, NULL, 0))
+ quote_c_style(file->name, NULL, stdout, 0);
+ else
+ fputs(file->name, stdout);
+ putchar(options->line_termination);
+ }
+}
+
struct checkdiff_t {
struct xdiff_emit_state xm;
const char *filename;
@@ -1731,6 +1748,7 @@ int diff_setup_done(struct diff_options *options)
DIFF_FORMAT_CHECKDIFF |
DIFF_FORMAT_NO_OUTPUT))
options->output_format &= ~(DIFF_FORMAT_RAW |
+ DIFF_FORMAT_NUMSTAT |
DIFF_FORMAT_DIFFSTAT |
DIFF_FORMAT_SUMMARY |
DIFF_FORMAT_PATCH);
@@ -1740,7 +1758,9 @@ int diff_setup_done(struct diff_options *options)
* recursive bits for other formats here.
*/
if (options->output_format & (DIFF_FORMAT_PATCH |
+ DIFF_FORMAT_NUMSTAT |
DIFF_FORMAT_DIFFSTAT |
+ DIFF_FORMAT_SUMMARY |
DIFF_FORMAT_CHECKDIFF))
options->recursive = 1;
/*
@@ -1828,6 +1848,9 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
else if (!strcmp(arg, "--patch-with-raw")) {
options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_RAW;
}
+ else if (!strcmp(arg, "--numstat")) {
+ options->output_format |= DIFF_FORMAT_NUMSTAT;
+ }
else if (!strncmp(arg, "--stat", 6)) {
char *end;
int width = options->stat_width;
@@ -2602,7 +2625,7 @@ void diff_flush(struct diff_options *options)
separator++;
}
- if (output_format & DIFF_FORMAT_DIFFSTAT) {
+ if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_NUMSTAT)) {
struct diffstat_t diffstat;
memset(&diffstat, 0, sizeof(struct diffstat_t));
@@ -2612,7 +2635,10 @@ void diff_flush(struct diff_options *options)
if (check_pair_status(p))
diff_flush_stat(p, options, &diffstat);
}
- show_stats(&diffstat, options);
+ if (output_format & DIFF_FORMAT_NUMSTAT)
+ show_numstat(&diffstat, options);
+ if (output_format & DIFF_FORMAT_DIFFSTAT)
+ show_stats(&diffstat, options);
separator++;
}
diff --git a/diff.h b/diff.h
index b48c9914e..ce3058e43 100644
--- a/diff.h
+++ b/diff.h
@@ -26,20 +26,21 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
#define DIFF_FORMAT_RAW 0x0001
#define DIFF_FORMAT_DIFFSTAT 0x0002
-#define DIFF_FORMAT_SUMMARY 0x0004
-#define DIFF_FORMAT_PATCH 0x0008
+#define DIFF_FORMAT_NUMSTAT 0x0004
+#define DIFF_FORMAT_SUMMARY 0x0008
+#define DIFF_FORMAT_PATCH 0x0010
/* These override all above */
-#define DIFF_FORMAT_NAME 0x0010
-#define DIFF_FORMAT_NAME_STATUS 0x0020
-#define DIFF_FORMAT_CHECKDIFF 0x0040
+#define DIFF_FORMAT_NAME 0x0100
+#define DIFF_FORMAT_NAME_STATUS 0x0200
+#define DIFF_FORMAT_CHECKDIFF 0x0400
/* Same as output_format = 0 but we know that -s flag was given
* and we should not give default value to output_format.
*/
-#define DIFF_FORMAT_NO_OUTPUT 0x0080
+#define DIFF_FORMAT_NO_OUTPUT 0x0800
-#define DIFF_FORMAT_CALLBACK 0x0100
+#define DIFF_FORMAT_CALLBACK 0x1000
struct diff_options {
const char *filter;
@@ -170,6 +171,7 @@ extern void diffcore_std_no_resolve(struct diff_options *);
" --patch-with-raw\n" \
" output both a patch and the diff-raw format.\n" \
" --stat show diffstat instead of patch.\n" \
+" --numstat show numeric diffstat instead of patch.\n" \
" --patch-with-stat\n" \
" output a patch and prepend its diffstat.\n" \
" --name-only show only names of changed files.\n" \
diff --git a/git-bisect.sh b/git-bisect.sh
index 06a8d2694..6da31e87a 100755
--- a/git-bisect.sh
+++ b/git-bisect.sh
@@ -179,11 +179,12 @@ bisect_reset() {
*)
usage ;;
esac
- git checkout "$branch" &&
- rm -fr "$GIT_DIR/refs/bisect"
- rm -f "$GIT_DIR/refs/heads/bisect" "$GIT_DIR/head-name"
- rm -f "$GIT_DIR/BISECT_LOG"
- rm -f "$GIT_DIR/BISECT_NAMES"
+ if git checkout "$branch"; then
+ rm -fr "$GIT_DIR/refs/bisect"
+ rm -f "$GIT_DIR/refs/heads/bisect" "$GIT_DIR/head-name"
+ rm -f "$GIT_DIR/BISECT_LOG"
+ rm -f "$GIT_DIR/BISECT_NAMES"
+ fi
}
bisect_replay () {
diff --git a/git-fetch.sh b/git-fetch.sh
index 79222fbb1..b15fc2b38 100755
--- a/git-fetch.sh
+++ b/git-fetch.sh
@@ -129,22 +129,25 @@ append_fetch_head () {
then
headc_=$(git-rev-parse --verify "$head_^0") || exit
echo "$headc_ $not_for_merge_ $note_" >>"$GIT_DIR/FETCH_HEAD"
- [ "$verbose" ] && echo >&2 "* committish: $head_"
- [ "$verbose" ] && echo >&2 " $note_"
else
echo "$head_ not-for-merge $note_" >>"$GIT_DIR/FETCH_HEAD"
- [ "$verbose" ] && echo >&2 "* non-commit: $head_"
- [ "$verbose" ] && echo >&2 " $note_"
- fi
- if test "$local_name_" != ""
- then
- # We are storing the head locally. Make sure that it is
- # a fast forward (aka "reverse push").
- fast_forward_local "$local_name_" "$head_" "$note_"
fi
+
+ update_local_ref "$local_name_" "$head_" "$note_"
}
-fast_forward_local () {
+update_local_ref () {
+ # If we are storing the head locally make sure that it is
+ # a fast forward (aka "reverse push").
+
+ label_=$(git-cat-file -t $2)
+ newshort_=$(git-rev-parse --short $2)
+ if test -z "$1" ; then
+ [ "$verbose" ] && echo >&2 "* fetched $3"
+ [ "$verbose" ] && echo >&2 " $label_: $newshort_"
+ return 0
+ fi
+ oldshort_=$(git-rev-parse --short "$1" 2>/dev/null)
mkdir -p "$(dirname "$GIT_DIR/$1")"
case "$1" in
refs/tags/*)
@@ -154,13 +157,16 @@ fast_forward_local () {
then
if now_=$(cat "$GIT_DIR/$1") && test "$now_" = "$2"
then
- [ "$verbose" ] && echo >&2 "* $1: same as $3" ||:
+ [ "$verbose" ] && echo >&2 "* $1: same as $3"
+ [ "$verbose" ] && echo >&2 " $label_: $newshort_" ||:
else
echo >&2 "* $1: updating with $3"
+ echo >&2 " $label_: $newshort_"
git-update-ref -m "$rloga: updating tag" "$1" "$2"
fi
else
echo >&2 "* $1: storing $3"
+ echo >&2 " $label_: $newshort_"
git-update-ref -m "$rloga: storing tag" "$1" "$2"
fi
;;
@@ -178,31 +184,34 @@ fast_forward_local () {
if test -n "$verbose"
then
echo >&2 "* $1: same as $3"
+ echo >&2 " $label_: $newshort_"
fi
;;
*,$local)
echo >&2 "* $1: fast forward to $3"
- echo >&2 " from $local to $2"
+ echo >&2 " old..new: $oldshort_..$newshort_"
git-update-ref -m "$rloga: fast-forward" "$1" "$2" "$local"
;;
*)
false
;;
esac || {
- echo >&2 "* $1: does not fast forward to $3;"
case ",$force,$single_force," in
*,t,*)
- echo >&2 " forcing update."
+ echo >&2 "* $1: forcing update to non-fast forward $3"
+ echo >&2 " old...new: $oldshort_...$newshort_"
git-update-ref -m "$rloga: forced-update" "$1" "$2" "$local"
;;
*)
- echo >&2 " not updating."
+ echo >&2 "* $1: not updating to non-fast forward $3"
+ echo >&2 " old...new: $oldshort_...$newshort_"
exit 1
;;
esac
}
else
echo >&2 "* $1: storing $3"
+ echo >&2 " $label_: $newshort_"
git-update-ref -m "$rloga: storing head" "$1" "$2"
fi
;;
diff --git a/git-merge.sh b/git-merge.sh
index 5b34b4de9..49c46d55d 100755
--- a/git-merge.sh
+++ b/git-merge.sh
@@ -197,7 +197,7 @@ f,*)
;;
?,1,"$head",*)
# Again the most common case of merging one remote.
- echo "Updating from $head to $1"
+ echo "Updating $(git-rev-parse --short $head)..$(git-rev-parse --short $1)"
git-update-index --refresh 2>/dev/null
new_head=$(git-rev-parse --verify "$1^0") &&
git-read-tree -u -v -m $head "$new_head" &&
diff --git a/git-rebase.sh b/git-rebase.sh
index a7373c053..546fa446f 100755
--- a/git-rebase.sh
+++ b/git-rebase.sh
@@ -3,7 +3,7 @@
# Copyright (c) 2005 Junio C Hamano.
#
-USAGE='[--onto <newbase>] <upstream> [<branch>]'
+USAGE='[-v] [--onto <newbase>] <upstream> [<branch>]'
LONG_USAGE='git-rebase replaces <branch> with a new branch of the
same name. When the --onto option is provided the new branch starts
out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
@@ -39,6 +39,7 @@ strategy=recursive
do_merge=
dotest=$GIT_DIR/.dotest-merge
prec=4
+verbose=
continue_merge () {
test -n "$prev_head" || die "prev_head must be defined"
@@ -190,6 +191,9 @@ do
esac
do_merge=t
;;
+ -v|--verbose)
+ verbose=t
+ ;;
-*)
usage
;;
@@ -273,6 +277,12 @@ then
exit 0
fi
+if test -n "$verbose"
+then
+ echo "Changes from $mb to $onto:"
+ git-diff-tree --stat --summary "$mb" "$onto"
+fi
+
# Rewind the head to "$onto"; this saves our current head in ORIG_HEAD.
git-reset --hard "$onto"
@@ -286,7 +296,7 @@ fi
if test -z "$do_merge"
then
- git-format-patch -k --stdout --full-index "$upstream"..ORIG_HEAD |
+ git-format-patch -k --stdout --full-index --ignore-if-in-upstream "$upstream"..ORIG_HEAD |
git am --binary -3 -k --resolvemsg="$RESOLVEMSG" \
--reflog-action=rebase
exit $?
diff --git a/git-resolve.sh b/git-resolve.sh
index 729ec65dc..36b90e384 100755
--- a/git-resolve.sh
+++ b/git-resolve.sh
@@ -46,7 +46,7 @@ case "$common" in
exit 0
;;
"$head")
- echo "Updating from $head to $merge"
+ echo "Updating $(git-rev-parse --short $head)..$(git-rev-parse --short $merge)"
git-read-tree -u -m $head $merge || exit 1
git-update-ref -m "resolve $merge_name: Fast forward" \
HEAD "$merge" "$head"
diff --git a/git-send-email.perl b/git-send-email.perl
index 3f50abaeb..1c6d2cc78 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -83,11 +83,12 @@ sub cleanup_compose_files();
my $compose_filename = ".msg.$$";
# Variables we fill in automatically, or via prompting:
-my (@to,@cc,@initial_cc,@bcclist,
+my (@to,@cc,@initial_cc,@bcclist,@xh,
$initial_reply_to,$initial_subject,@files,$from,$compose,$time);
# Behavior modification variables
-my ($chain_reply_to, $quiet, $suppress_from, $no_signed_off_cc) = (1, 0, 0, 0);
+my ($chain_reply_to, $quiet, $suppress_from, $no_signed_off_cc,
+ $dry_run) = (1, 0, 0, 0, 0);
my $smtp_server;
# Example reply to:
@@ -116,6 +117,7 @@ my $rc = GetOptions("from=s" => \$from,
"quiet" => \$quiet,
"suppress-from" => \$suppress_from,
"no-signed-off-cc|no-signed-off-by-cc" => \$no_signed_off_cc,
+ "dry-run" => \$dry_run,
);
# Verify the user input
@@ -409,6 +411,11 @@ sub send_message
$gitversion = Git::version();
}
+ my ($author_name) = ($from =~ /^(.*?)\s+</);
+ if ($author_name && $author_name =~ /\./ && $author_name !~ /^".*"$/) {
+ my ($name, $addr) = ($from =~ /^(.*?)(\s+<.*)/);
+ $from = "\"$name\"$addr";
+ }
my $header = "From: $from
To: $to
Cc: $cc
@@ -422,8 +429,13 @@ X-Mailer: git-send-email $gitversion
$header .= "In-Reply-To: $reply_to\n";
$header .= "References: $references\n";
}
+ if (@xh) {
+ $header .= join("\n", @xh) . "\n";
+ }
- if ($smtp_server =~ m#^/#) {
+ if ($dry_run) {
+ # We don't want to send the email.
+ } elsif ($smtp_server =~ m#^/#) {
my $pid = open my $sm, '|-';
defined $pid or die $!;
if (!$pid) {
@@ -472,15 +484,22 @@ foreach my $t (@files) {
my $author_not_sender = undef;
@cc = @initial_cc;
- my $found_mbox = 0;
+ @xh = ();
+ my $input_format = undef;
my $header_done = 0;
$message = "";
while(<F>) {
if (!$header_done) {
- $found_mbox = 1, next if (/^From /);
+ if (/^From /) {
+ $input_format = 'mbox';
+ next;
+ }
chomp;
+ if (!defined $input_format && /^[-A-Za-z]+:\s/) {
+ $input_format = 'mbox';
+ }
- if ($found_mbox) {
+ if (defined $input_format && $input_format eq 'mbox') {
if (/^Subject:\s+(.*)$/) {
$subject = $1;
@@ -495,6 +514,9 @@ foreach my $t (@files) {
$2, $_) unless $quiet;
push @cc, $2;
}
+ elsif (/^[-A-Za-z]+:\s+\S/) {
+ push @xh, $_;
+ }
} else {
# In the traditional
@@ -502,6 +524,7 @@ foreach my $t (@files) {
# line 1 = cc
# line 2 = subject
# So let's support that, too.
+ $input_format = 'lots';
if (@cc == 0) {
printf("(non-mbox) Adding cc: %s from line '%s'\n",
$_, $_) unless $quiet;
diff --git a/git-svnimport.perl b/git-svnimport.perl
index aca0e4f64..f6eff8e32 100755
--- a/git-svnimport.perl
+++ b/git-svnimport.perl
@@ -193,6 +193,13 @@ sub ignore {
}
}
+sub dir_list {
+ my($self,$path,$rev) = @_;
+ my ($dirents,undef,$properties)
+ = $self->{'svn'}->get_dir($path,$rev,undef);
+ return $dirents;
+}
+
package main;
use URI;
@@ -342,35 +349,16 @@ if ($opt_A) {
open BRANCHES,">>", "$git_dir/svn2git";
-sub node_kind($$$) {
- my ($branch, $path, $revision) = @_;
+sub node_kind($$) {
+ my ($svnpath, $revision) = @_;
my $pool=SVN::Pool->new;
- my $kind = $svn->{'svn'}->check_path(revert_split_path($branch,$path),$revision,$pool);
+ my $kind = $svn->{'svn'}->check_path($svnpath,$revision,$pool);
$pool->clear;
return $kind;
}
-sub revert_split_path($$) {
- my($branch,$path) = @_;
-
- my $svnpath;
- $path = "" if $path eq "/"; # this should not happen, but ...
- if($branch eq "/") {
- $svnpath = "$trunk_name/$path";
- } elsif($branch =~ m#^/#) {
- $svnpath = "$tag_name$branch/$path";
- } else {
- $svnpath = "$branch_name/$branch/$path";
- }
-
- $svnpath =~ s#/+$##;
- return $svnpath;
-}
-
sub get_file($$$) {
- my($rev,$branch,$path) = @_;
-
- my $svnpath = revert_split_path($branch,$path);
+ my($svnpath,$rev,$path) = @_;
# now get it
my ($name,$mode);
@@ -413,10 +401,9 @@ sub get_file($$$) {
}
sub get_ignore($$$$$) {
- my($new,$old,$rev,$branch,$path) = @_;
+ my($new,$old,$rev,$path,$svnpath) = @_;
return unless $opt_I;
- my $svnpath = revert_split_path($branch,$path);
my $name = $svn->ignore("$svnpath",$rev);
if ($path eq '/') {
$path = $opt_I;
@@ -435,7 +422,7 @@ sub get_ignore($$$$$) {
close $F;
unlink $name;
push(@$new,['0644',$sha,$path]);
- } else {
+ } elsif (defined $old) {
push(@$old,$path);
}
}
@@ -480,6 +467,27 @@ sub branch_rev($$) {
return $therev;
}
+sub expand_svndir($$$);
+
+sub expand_svndir($$$)
+{
+ my ($svnpath, $rev, $path) = @_;
+ my @list;
+ get_ignore(\@list, undef, $rev, $path, $svnpath);
+ my $dirents = $svn->dir_list($svnpath, $rev);
+ foreach my $p(keys %$dirents) {
+ my $kind = node_kind($svnpath.'/'.$p, $rev);
+ if ($kind eq $SVN::Node::file) {
+ my $f = get_file($svnpath.'/'.$p, $rev, $path.'/'.$p);
+ push(@list, $f) if $f;
+ } elsif ($kind eq $SVN::Node::dir) {
+ push(@list,
+ expand_svndir($svnpath.'/'.$p, $rev, $path.'/'.$p));
+ }
+ }
+ return @list;
+}
+
sub copy_path($$$$$$$$) {
# Somebody copied a whole subdirectory.
# We need to find the index entries from the old version which the
@@ -488,8 +496,11 @@ sub copy_path($$$$$$$$) {
my($newrev,$newbranch,$path,$oldpath,$rev,$node_kind,$new,$parents) = @_;
my($srcbranch,$srcpath) = split_path($rev,$oldpath);
- unless(defined $srcbranch) {
- print "Path not found when copying from $oldpath @ $rev\n";
+ unless(defined $srcbranch && defined $srcpath) {
+ print "Path not found when copying from $oldpath @ $rev.\n".
+ "Will try to copy from original SVN location...\n"
+ if $opt_v;
+ push (@$new, expand_svndir($oldpath, $rev, $path));
return;
}
my $therev = branch_rev($srcbranch, $rev);
@@ -503,7 +514,7 @@ sub copy_path($$$$$$$$) {
}
print "$newrev:$newbranch:$path: copying from $srcbranch:$srcpath @ $rev\n" if $opt_v;
if ($node_kind eq $SVN::Node::dir) {
- $srcpath =~ s#/*$#/#;
+ $srcpath =~ s#/*$#/#;
}
my $pid = open my $f,'-|';
@@ -582,10 +593,12 @@ sub commit {
if(defined $oldpath) {
my $p;
($parent,$p) = split_path($revision,$oldpath);
- if($parent eq "/") {
- $parent = $opt_o;
- } else {
- $parent =~ s#^/##; # if it's a tag
+ if(defined $parent) {
+ if($parent eq "/") {
+ $parent = $opt_o;
+ } else {
+ $parent =~ s#^/##; # if it's a tag
+ }
}
} else {
$parent = undef;
@@ -651,9 +664,10 @@ sub commit {
push(@old,$path); # remove any old stuff
}
if(($action->[0] eq "A") || ($action->[0] eq "R")) {
- my $node_kind = node_kind($branch,$path,$revision);
+ my $node_kind = node_kind($action->[3], $revision);
if ($node_kind eq $SVN::Node::file) {
- my $f = get_file($revision,$branch,$path);
+ my $f = get_file($action->[3],
+ $revision, $path);
if ($f) {
push(@new,$f) if $f;
} else {
@@ -668,19 +682,20 @@ sub commit {
\@new, \@parents);
} else {
get_ignore(\@new, \@old, $revision,
- $branch, $path);
+ $path, $action->[3]);
}
}
} elsif ($action->[0] eq "D") {
push(@old,$path);
} elsif ($action->[0] eq "M") {
- my $node_kind = node_kind($branch,$path,$revision);
+ my $node_kind = node_kind($action->[3], $revision);
if ($node_kind eq $SVN::Node::file) {
- my $f = get_file($revision,$branch,$path);
+ my $f = get_file($action->[3],
+ $revision, $path);
push(@new,$f) if $f;
} elsif ($node_kind eq $SVN::Node::dir) {
get_ignore(\@new, \@old, $revision,
- $branch,$path);
+ $path, $action->[3]);
}
} else {
die "$revision: unknown action '".$action->[0]."' for $path\n";
diff --git a/gitweb/README b/gitweb/README
index 78e6fc05f..e02e90f04 100644
--- a/gitweb/README
+++ b/gitweb/README
@@ -26,12 +26,26 @@ You can specify the following configuration variables when building GIT:
* GITWEB_LOGO
Points to the location where you put git-logo.png on your web server.
* GITWEB_CONFIG
- This file will be loaded using 'require'. If the environment
+ This file will be loaded using 'require' and can be used to override any
+ of the options above as well as some other options - see the top of
+ 'gitweb.cgi' for their full list and description. If the environment
$GITWEB_CONFIG is set when gitweb.cgi is executed the file in the
environment variable will be loaded instead of the file
specified when gitweb.cgi was created.
+Runtime gitweb configuration
+----------------------------
+
+You can adjust gitweb behaviour using the file specified in `GITWEB_CONFIG`
+(defaults to 'gitweb_config.perl' in the same directory as the CGI).
+See the top of 'gitweb.cgi' for the list of variables and some description.
+The most notable thing that is not configurable at compile time are the
+optional features, stored in the '%features' variable. You can find further
+description on how to reconfigure the default features setting in your
+`GITWEB_CONFIG` or per-project in `project.git/config` inside 'gitweb.cgi'.
+
+
Webserver configuration
-----------------------
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 0ec1eeffa..035e85e6e 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -93,21 +93,66 @@ our %feature = (
#
# use gitweb_check_feature(<feature>) to check if <feature> is enabled
+ # Enable the 'blame' blob view, showing the last commit that modified
+ # each line in the file. This can be very CPU-intensive.
+
+ # To enable system wide have in $GITWEB_CONFIG
+ # $feature{'blame'}{'default'} = [1];
+ # To have project specific config enable override in $GITWEB_CONFIG
+ # $feature{'blame'}{'override'} = 1;
+ # and in project config gitweb.blame = 0|1;
'blame' => {
'sub' => \&feature_blame,
'override' => 0,
'default' => [0]},
+ # Enable the 'snapshot' link, providing a compressed tarball of any
+ # tree. This can potentially generate high traffic if you have large
+ # project.
+
+ # To disable system wide have in $GITWEB_CONFIG
+ # $feature{'snapshot'}{'default'} = [undef];
+ # To have project specific config enable override in $GITWEB_CONFIG
+ # $feature{'blame'}{'override'} = 1;
+ # and in project config gitweb.snapshot = none|gzip|bzip2;
'snapshot' => {
'sub' => \&feature_snapshot,
'override' => 0,
# => [content-encoding, suffix, program]
'default' => ['x-gzip', 'gz', 'gzip']},
+ # Enable the pickaxe search, which will list the commits that modified
+ # a given string in a file. This can be practical and quite faster
+ # alternative to 'blame', but still potentially CPU-intensive.
+
+ # To enable system wide have in $GITWEB_CONFIG
+ # $feature{'pickaxe'}{'default'} = [1];
+ # To have project specific config enable override in $GITWEB_CONFIG
+ # $feature{'pickaxe'}{'override'} = 1;
+ # and in project config gitweb.pickaxe = 0|1;
'pickaxe' => {
'sub' => \&feature_pickaxe,
'override' => 0,
'default' => [1]},
+
+ # Make gitweb use an alternative format of the URLs which can be
+ # more readable and natural-looking: project name is embedded
+ # directly in the path and the query string contains other
+ # auxiliary information. All gitweb installations recognize
+ # URL in either format; this configures in which formats gitweb
+ # generates links.
+
+ # To enable system wide have in $GITWEB_CONFIG
+ # $feature{'pathinfo'}{'default'} = [1];
+ # Project specific override is not supported.
+
+ # Note that you will need to change the default location of CSS,
+ # favicon, logo and possibly other files to an absolute URL. Also,
+ # if gitweb.cgi serves as your indexfile, you will need to force
+ # $my_uri to contain the script name in your $GITWEB_CONFIG.
+ 'pathinfo' => {
+ 'override' => 0,
+ 'default' => [0]},
);
sub gitweb_check_feature {
@@ -118,15 +163,13 @@ sub gitweb_check_feature {
$feature{$name}{'override'},
@{$feature{$name}{'default'}});
if (!$override) { return @defaults; }
+ if (!defined $sub) {
+ warn "feature $name is not overrideable";
+ return @defaults;
+ }
return $sub->(@defaults);
}
-# To enable system wide have in $GITWEB_CONFIG
-# $feature{'blame'}{'default'} = [1];
-# To have project specific config enable override in $GITWEB_CONFIG
-# $feature{'blame'}{'override'} = 1;
-# and in project config gitweb.blame = 0|1;
-
sub feature_blame {
my ($val) = git_get_project_config('blame', '--bool');
@@ -139,12 +182,6 @@ sub feature_blame {
return $_[0];
}
-# To disable system wide have in $GITWEB_CONFIG
-# $feature{'snapshot'}{'default'} = [undef];
-# To have project specific config enable override in $GITWEB_CONFIG
-# $feature{'blame'}{'override'} = 1;
-# and in project config gitweb.snapshot = none|gzip|bzip2
-
sub feature_snapshot {
my ($ctype, $suffix, $command) = @_;
@@ -168,12 +205,6 @@ sub gitweb_have_snapshot {
return $have_snapshot;
}
-# To enable system wide have in $GITWEB_CONFIG
-# $feature{'pickaxe'}{'default'} = [1];
-# To have project specific config enable override in $GITWEB_CONFIG
-# $feature{'pickaxe'}{'override'} = 1;
-# and in project config gitweb.pickaxe = 0|1;
-
sub feature_pickaxe {
my ($val) = git_get_project_config('pickaxe', '--bool');
@@ -381,6 +412,10 @@ exit;
sub href(%) {
my %params = @_;
+ my $href = $my_uri;
+
+ # XXX: Warning: If you touch this, check the search form for updating,
+ # too.
my @mapping = (
project => "p",
@@ -399,6 +434,19 @@ sub href(%) {
$params{'project'} = $project unless exists $params{'project'};
+ my ($use_pathinfo) = gitweb_check_feature('pathinfo');
+ if ($use_pathinfo) {
+ # use PATH_INFO for project name
+ $href .= "/$params{'project'}" if defined $params{'project'};
+ delete $params{'project'};
+
+ # Summary just uses the project path URL
+ if (defined $params{'action'} && $params{'action'} eq 'summary') {
+ delete $params{'action'};
+ }
+ }
+
+ # now encode the parameters explicitly
my @result = ();
for (my $i = 0; $i < @mapping; $i += 2) {
my ($name, $symbol) = ($mapping[$i], $mapping[$i+1]);
@@ -406,7 +454,9 @@ sub href(%) {
push @result, $symbol . "=" . esc_param($params{$name});
}
}
- return "$my_uri?" . join(';', @result);
+ $href .= "?" . join(';', @result) if scalar @result;
+
+ return $href;
}
@@ -1411,6 +1461,7 @@ EOF
}
$cgi->param("a", "search");
$cgi->param("h", $search_hash);
+ $cgi->param("p", $project);
print $cgi->startform(-method => "get", -action => $my_uri) .
"<div class=\"search\">\n" .
$cgi->hidden(-name => "p") . "\n" .
diff --git a/grep.c b/grep.c
index c411ddd4d..0fc078ec0 100644
--- a/grep.c
+++ b/grep.c
@@ -34,7 +34,7 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
}
}
-static struct grep_expr *compile_pattern_expr(struct grep_pat **);
+static struct grep_expr *compile_pattern_or(struct grep_pat **);
static struct grep_expr *compile_pattern_atom(struct grep_pat **list)
{
struct grep_pat *p;
@@ -52,7 +52,7 @@ static struct grep_expr *compile_pattern_atom(struct grep_pat **list)
return x;
case GREP_OPEN_PAREN:
*list = p->next;
- x = compile_pattern_expr(list);
+ x = compile_pattern_or(list);
if (!x)
return NULL;
if (!*list || (*list)->token != GREP_CLOSE_PAREN)
@@ -138,6 +138,9 @@ void compile_grep_patterns(struct grep_opt *opt)
{
struct grep_pat *p;
+ if (opt->all_match)
+ opt->extended = 1;
+
for (p = opt->pattern_list; p; p = p->next) {
switch (p->token) {
case GREP_PATTERN: /* atom */
@@ -309,40 +312,63 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol
return hit;
}
-static int match_expr_eval(struct grep_opt *opt,
+static int match_expr_eval(struct grep_opt *o,
struct grep_expr *x,
char *bol, char *eol,
- enum grep_context ctx)
+ enum grep_context ctx,
+ int collect_hits)
{
+ int h = 0;
+
switch (x->node) {
case GREP_NODE_ATOM:
- return match_one_pattern(opt, x->u.atom, bol, eol, ctx);
+ h = match_one_pattern(o, x->u.atom, bol, eol, ctx);
break;
case GREP_NODE_NOT:
- return !match_expr_eval(opt, x->u.unary, bol, eol, ctx);
+ h = !match_expr_eval(o, x->u.unary, bol, eol, ctx, 0);
+ break;
case GREP_NODE_AND:
- return (match_expr_eval(opt, x->u.binary.left, bol, eol, ctx) &&
- match_expr_eval(opt, x->u.binary.right, bol, eol, ctx));
+ if (!collect_hits)
+ return (match_expr_eval(o, x->u.binary.left,
+ bol, eol, ctx, 0) &&
+ match_expr_eval(o, x->u.binary.right,
+ bol, eol, ctx, 0));
+ h = match_expr_eval(o, x->u.binary.left, bol, eol, ctx, 0);
+ h &= match_expr_eval(o, x->u.binary.right, bol, eol, ctx, 0);
+ break;
case GREP_NODE_OR:
- return (match_expr_eval(opt, x->u.binary.left, bol, eol, ctx) ||
- match_expr_eval(opt, x->u.binary.right, bol, eol, ctx));
+ if (!collect_hits)
+ return (match_expr_eval(o, x->u.binary.left,
+ bol, eol, ctx, 0) ||
+ match_expr_eval(o, x->u.binary.right,
+ bol, eol, ctx, 0));
+ h = match_expr_eval(o, x->u.binary.left, bol, eol, ctx, 0);
+ x->u.binary.left->hit |= h;
+ h |= match_expr_eval(o, x->u.binary.right, bol, eol, ctx, 1);
+ break;
+ default:
+ die("Unexpected node type (internal error) %d\n", x->node);
}
- die("Unexpected node type (internal error) %d\n", x->node);
+ if (collect_hits)
+ x->hit |= h;
+ return h;
}
static int match_expr(struct grep_opt *opt, char *bol, char *eol,
- enum grep_context ctx)
+ enum grep_context ctx, int collect_hits)
{
struct grep_expr *x = opt->pattern_expression;
- return match_expr_eval(opt, x, bol, eol, ctx);
+ return match_expr_eval(opt, x, bol, eol, ctx, collect_hits);
}
static int match_line(struct grep_opt *opt, char *bol, char *eol,
- enum grep_context ctx)
+ enum grep_context ctx, int collect_hits)
{
struct grep_pat *p;
if (opt->extended)
- return match_expr(opt, bol, eol, ctx);
+ return match_expr(opt, bol, eol, ctx, collect_hits);
+
+ /* we do not call with collect_hits without being extended */
for (p = opt->pattern_list; p; p = p->next) {
if (match_one_pattern(opt, p, bol, eol, ctx))
return 1;
@@ -350,7 +376,8 @@ static int match_line(struct grep_opt *opt, char *bol, char *eol,
return 0;
}
-int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size)
+static int grep_buffer_1(struct grep_opt *opt, const char *name,
+ char *buf, unsigned long size, int collect_hits)
{
char *bol = buf;
unsigned long left = size;
@@ -386,7 +413,7 @@ int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long
while (left) {
char *eol, ch;
- int hit = 0;
+ int hit;
eol = end_of_line(bol, &left);
ch = *eol;
@@ -395,9 +422,12 @@ int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long
if ((ctx == GREP_CONTEXT_HEAD) && (eol == bol))
ctx = GREP_CONTEXT_BODY;
- hit = match_line(opt, bol, eol, ctx);
+ hit = match_line(opt, bol, eol, ctx, collect_hits);
*eol = ch;
+ if (collect_hits)
+ goto next_line;
+
/* "grep -v -e foo -e bla" should list lines
* that do not have either, so inversion should
* be done outside.
@@ -477,6 +507,8 @@ int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long
}
free(prev);
+ if (collect_hits)
+ return 0;
if (opt->status_only)
return 0;
@@ -496,3 +528,49 @@ int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long
return !!last_hit;
}
+static void clr_hit_marker(struct grep_expr *x)
+{
+ /* All-hit markers are meaningful only at the very top level
+ * OR node.
+ */
+ while (1) {
+ x->hit = 0;
+ if (x->node != GREP_NODE_OR)
+ return;
+ x->u.binary.left->hit = 0;
+ x = x->u.binary.right;
+ }
+}
+
+static int chk_hit_marker(struct grep_expr *x)
+{
+ /* Top level nodes have hit markers. See if they all are hits */
+ while (1) {
+ if (x->node != GREP_NODE_OR)
+ return x->hit;
+ if (!x->u.binary.left->hit)
+ return 0;
+ x = x->u.binary.right;
+ }
+}
+
+int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size)
+{
+ /*
+ * we do not have to do the two-pass grep when we do not check
+ * buffer-wide "all-match".
+ */
+ if (!opt->all_match)
+ return grep_buffer_1(opt, name, buf, size, 0);
+
+ /* Otherwise the toplevel "or" terms hit a bit differently.
+ * We first clear hit markers from them.
+ */
+ clr_hit_marker(opt->pattern_expression);
+ grep_buffer_1(opt, name, buf, size, 1);
+
+ if (!chk_hit_marker(opt->pattern_expression))
+ return 0;
+
+ return grep_buffer_1(opt, name, buf, size, 0);
+}
diff --git a/grep.h b/grep.h
index af9098cfe..d252dd25f 100644
--- a/grep.h
+++ b/grep.h
@@ -35,6 +35,7 @@ enum grep_expr_node {
struct grep_expr {
enum grep_expr_node node;
+ unsigned hit;
union {
struct grep_pat *atom;
struct grep_expr *unary;
@@ -59,6 +60,7 @@ struct grep_opt {
unsigned count:1;
unsigned word_regexp:1;
unsigned fixed:1;
+ unsigned all_match:1;
#define GREP_BINARY_DEFAULT 0
#define GREP_BINARY_NOMATCH 1
#define GREP_BINARY_TEXT 2
diff --git a/revision.c b/revision.c
index 93f25130a..f1e0caaae 100644
--- a/revision.c
+++ b/revision.c
@@ -732,6 +732,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
int i, flags, seen_dashdash, show_merge;
const char **unrecognized = argv + 1;
int left = 1;
+ int all_match = 0;
/* First, search for "--" */
seen_dashdash = 0;
@@ -967,6 +968,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
add_message_grep(revs, arg+7);
continue;
}
+ if (!strcmp(arg, "--all-match")) {
+ all_match = 1;
+ continue;
+ }
opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i);
if (opts > 0) {
@@ -1028,8 +1033,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
if (diff_setup_done(&revs->diffopt) < 0)
die("diff_setup_done failed");
- if (revs->grep_filter)
+ if (revs->grep_filter) {
+ revs->grep_filter->all_match = all_match;
compile_grep_patterns(revs->grep_filter);
+ }
return left;
}
diff --git a/t/t4013/diff.diff-tree_--pretty_--root_--summary_initial b/t/t4013/diff.diff-tree_--pretty_--root_--summary_initial
index ea4820553..58e5f74ae 100644
--- a/t/t4013/diff.diff-tree_--pretty_--root_--summary_initial
+++ b/t/t4013/diff.diff-tree_--pretty_--root_--summary_initial
@@ -5,7 +5,7 @@ Date: Mon Jun 26 00:00:00 2006 +0000
Initial
- create mode 040000 dir
+ create mode 100644 dir/sub
create mode 100644 file0
create mode 100644 file2
$
diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh
index 278eb6670..cf08e9279 100755
--- a/t/t5000-tar-tree.sh
+++ b/t/t5000-tar-tree.sh
@@ -26,6 +26,7 @@ commit id embedding:
. ./test-lib.sh
TAR=${TAR:-tar}
+UNZIP=${UNZIP:-unzip}
test_expect_success \
'populate workdir' \
@@ -95,4 +96,38 @@ test_expect_success \
'validate file contents with prefix' \
'diff -r a c/prefix/a'
+test_expect_success \
+ 'git-archive --format=zip' \
+ 'git-archive --format=zip HEAD >d.zip'
+
+test_expect_success \
+ 'extract ZIP archive' \
+ '(mkdir d && cd d && $UNZIP ../d.zip)'
+
+test_expect_success \
+ 'validate filenames' \
+ '(cd d/a && find .) | sort >d.lst &&
+ diff a.lst d.lst'
+
+test_expect_success \
+ 'validate file contents' \
+ 'diff -r a d/a'
+
+test_expect_success \
+ 'git-archive --format=zip with prefix' \
+ 'git-archive --format=zip --prefix=prefix/ HEAD >e.zip'
+
+test_expect_success \
+ 'extract ZIP archive with prefix' \
+ '(mkdir e && cd e && $UNZIP ../e.zip)'
+
+test_expect_success \
+ 'validate filenames with prefix' \
+ '(cd e/prefix/a && find .) | sort >e.lst &&
+ diff a.lst e.lst'
+
+test_expect_success \
+ 'validate file contents with prefix' \
+ 'diff -r a e/prefix/a'
+
test_done