diff options
-rw-r--r-- | Documentation/diff-options.txt | 5 | ||||
-rw-r--r-- | Documentation/git-grep.txt | 11 | ||||
-rw-r--r-- | Documentation/git-rebase.txt | 5 | ||||
-rw-r--r-- | archive-zip.c | 17 | ||||
-rw-r--r-- | builtin-apply.c | 4 | ||||
-rw-r--r-- | builtin-grep.c | 4 | ||||
-rw-r--r-- | combine-diff.c | 9 | ||||
-rw-r--r-- | diff.c | 30 | ||||
-rw-r--r-- | diff.h | 16 | ||||
-rwxr-xr-x | git-bisect.sh | 11 | ||||
-rwxr-xr-x | git-fetch.sh | 41 | ||||
-rwxr-xr-x | git-merge.sh | 2 | ||||
-rwxr-xr-x | git-rebase.sh | 14 | ||||
-rwxr-xr-x | git-resolve.sh | 2 | ||||
-rwxr-xr-x | git-send-email.perl | 35 | ||||
-rwxr-xr-x | git-svnimport.perl | 93 | ||||
-rw-r--r-- | gitweb/README | 16 | ||||
-rwxr-xr-x | gitweb/gitweb.perl | 89 | ||||
-rw-r--r-- | grep.c | 114 | ||||
-rw-r--r-- | grep.h | 2 | ||||
-rw-r--r-- | revision.c | 9 | ||||
-rw-r--r-- | t/t4013/diff.diff-tree_--pretty_--root_--summary_initial | 2 | ||||
-rwxr-xr-x | t/t5000-tar-tree.sh | 35 |
23 files changed, 434 insertions, 132 deletions
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 7b7b9e8ce9..e112172ca5 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 d8af4d961b..bfbece9864 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 9d7bcaa38c..10f2924f4d 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 3ffdad68d1..28e7352e98 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 11a5277a69..11397f5504 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 4205e5d38d..ad7dc00cde 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 46d9121baf..65c786807b 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) @@ -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++; } @@ -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 06a8d26945..6da31e87a0 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 79222fbb1a..b15fc2b389 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 5b34b4de99..49c46d55df 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 a7373c0532..546fa446fc 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 729ec65dc9..36b90e3849 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 3f50abaeb6..1c6d2cc787 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 aca0e4f64e..f6eff8e32a 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 78e6fc05f3..e02e90f042 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 0ec1eeffa1..035e85e6e9 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" . @@ -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); +} @@ -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 93f25130a0..f1e0caaae3 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 ea48205537..58e5f74aea 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 278eb66701..cf08e9279c 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 |