From 937a515a15f776aa84430574f71367292a52b978 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Wed, 5 Jul 2006 21:28:20 -0700 Subject: [PATCH 01/27] upload-pack: stop the other side when they have more roots than we do. When the downloader has more roots than we do, we will see many "have" that leads to the root we do not have and let the other side walk all the way to that root. Tell them to stop sending the branch'es ancestors by sending a fake "ACK" when we already have common ancestor for the wanted refs. Signed-off-by: Junio C Hamano --- upload-pack.c | 101 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 92 insertions(+), 9 deletions(-) diff --git a/upload-pack.c b/upload-pack.c index 35c7ecb7b..e8f4be373 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -11,9 +11,15 @@ static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] "; -#define THEY_HAVE (1U << 0) -#define OUR_REF (1U << 1) -#define WANTED (1U << 2) +/* bits #0..7 in revision.h, #8..10 in commit.c */ +#define THEY_HAVE (1u << 11) +#define OUR_REF (1u << 12) +#define WANTED (1u << 13) +#define COMMON_KNOWN (1u << 14) +#define REACHABLE (1u << 15) + +static unsigned long oldest_have = 0; + static int multi_ack = 0, nr_our_refs = 0; static int use_thin_pack = 0; static struct object_array have_obj; @@ -323,11 +329,12 @@ static void create_pack_file(void) static int got_sha1(char *hex, unsigned char *sha1) { struct object *o; + int we_knew_they_have = 0; if (get_sha1_hex(hex, sha1)) die("git-upload-pack: expected SHA1 object, got '%s'", hex); if (!has_sha1_file(sha1)) - return 0; + return -1; o = lookup_object(sha1); if (!(o && o->parsed)) @@ -336,15 +343,84 @@ static int got_sha1(char *hex, unsigned char *sha1) die("oops (%s)", sha1_to_hex(sha1)); if (o->type == OBJ_COMMIT) { struct commit_list *parents; + struct commit *commit = (struct commit *)o; if (o->flags & THEY_HAVE) - return 0; - o->flags |= THEY_HAVE; - for (parents = ((struct commit*)o)->parents; + we_knew_they_have = 1; + else + o->flags |= THEY_HAVE; + if (!oldest_have || (commit->date < oldest_have)) + oldest_have = commit->date; + for (parents = commit->parents; parents; parents = parents->next) parents->item->object.flags |= THEY_HAVE; } - add_object_array(o, NULL, &have_obj); + if (!we_knew_they_have) { + add_object_array(o, NULL, &have_obj); + return 1; + } + return 0; +} + +static int reachable(struct commit *want) +{ + struct commit_list *work = NULL; + + insert_by_date(want, &work); + while (work) { + struct commit_list *list = work->next; + struct commit *commit = work->item; + free(work); + work = list; + + if (commit->object.flags & THEY_HAVE) { + want->object.flags |= COMMON_KNOWN; + break; + } + if (!commit->object.parsed) + parse_object(commit->object.sha1); + if (commit->object.flags & REACHABLE) + continue; + commit->object.flags |= REACHABLE; + if (commit->date < oldest_have) + continue; + for (list = commit->parents; list; list = list->next) { + struct commit *parent = list->item; + if (!(parent->object.flags & REACHABLE)) + insert_by_date(parent, &work); + } + } + want->object.flags |= REACHABLE; + clear_commit_marks(want, REACHABLE); + free_commit_list(work); + return (want->object.flags & COMMON_KNOWN); +} + +static int ok_to_give_up(void) +{ + int i; + + if (!have_obj.nr) + return 0; + + for (i = 0; i < want_obj.nr; i++) { + struct object *want = want_obj.objects[i].item; + + if (want->flags & COMMON_KNOWN) + continue; + want = deref_tag(want, "a want line", 0); + if (!want || want->type != OBJ_COMMIT) { + /* no way to tell if this is reachable by + * looking at the ancestry chain alone, so + * leave a note to ourselves not to worry about + * this object anymore. + */ + want_obj.objects[i].item->flags |= COMMON_KNOWN; + continue; + } + if (!reachable((struct commit *)want)) + return 0; + } return 1; } @@ -369,7 +445,13 @@ static int get_common_commits(void) } len = strip(line, len); if (!strncmp(line, "have ", 5)) { - if (got_sha1(line+5, sha1)) { + switch (got_sha1(line+5, sha1)) { + case -1: /* they have what we do not */ + if (multi_ack && ok_to_give_up()) + packet_write(1, "ACK %s continue\n", + sha1_to_hex(sha1)); + break; + default: memcpy(hex, sha1_to_hex(sha1), 41); if (multi_ack) { const char *msg = "ACK %s continue\n"; @@ -378,6 +460,7 @@ static int get_common_commits(void) } else if (have_obj.nr == 1) packet_write(1, "ACK %s\n", hex); + break; } continue; } From ef58d9587ebad999b3decfcf369ba3b4ff72b121 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 14 Nov 2006 22:23:18 -0800 Subject: [PATCH 02/27] apply --numstat: mark binary diffstat with - -, not 0 0 We do not even know number of lines so showing it as 0 0 is lying. This would also help Porcelains like cvsexportcommit. Signed-off-by: Junio C Hamano --- builtin-apply.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/builtin-apply.c b/builtin-apply.c index aad55261f..b80ad2ce2 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -2116,7 +2116,11 @@ static void numstat_patch_list(struct patch *patch) for ( ; patch; patch = patch->next) { const char *name; name = patch->new_name ? patch->new_name : patch->old_name; - printf("%d\t%d\t", patch->lines_added, patch->lines_deleted); + if (patch->is_binary) + printf("-\t-\t"); + else + printf("%d\t%d\t", + patch->lines_added, patch->lines_deleted); if (line_termination && quote_c_style(name, NULL, NULL, 0)) quote_c_style(name, NULL, stdout, 0); else From e9195b584f6ad49b637b56be0705a7362a95d692 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 14 Nov 2006 22:18:31 -0800 Subject: [PATCH 03/27] pack-objects: tweak "do not even attempt delta" heuristics The heuristics to give up deltification when both the source and the target are both in the same pack affects negatively when we are repacking the subset of objects in the existing pack. This caused any incremental updates to use suboptimal packs. Tweak the heuristics to avoid this problem. Signed-off-by: Junio C Hamano --- builtin-pack-objects.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 69e5dd39c..753bcd57b 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -1176,7 +1176,9 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, * on an earlier try, but only when reusing delta data. */ if (!no_reuse_delta && trg_entry->in_pack && - trg_entry->in_pack == src_entry->in_pack) + trg_entry->in_pack == src_entry->in_pack && + trg_entry->in_pack_type != OBJ_REF_DELTA && + trg_entry->in_pack_type != OBJ_OFS_DELTA) return 0; /* From 04408c3578300ac7cc1b1530bbaec54de299b200 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 18 Nov 2006 23:35:38 +0100 Subject: [PATCH 04/27] gitweb: Protect against possible warning in git_commitdiff We may read an undef from <$fd> and unconditionally chomping it would result in a warning. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 758759576..b2482febf 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3731,7 +3731,8 @@ sub git_commitdiff { $hash_parent, $hash, "--" or die_error(undef, "Open git-diff-tree failed"); - while (chomp(my $line = <$fd>)) { + while (my $line = <$fd>) { + chomp $line; # empty line ends raw part of diff-tree output last unless $line; push @difftree, $line; From 6d55f05576851aedc7c53f7438c1d505c22ee78f Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 18 Nov 2006 23:35:39 +0100 Subject: [PATCH 05/27] gitweb: Buffer diff header to deal with split patches + git_patchset_body refactoring There are some cases when one line from "raw" git-diff output (raw format) corresponds to more than one patch in the patchset git-diff output. To deal with this buffer git diff header and extended diff header (everything up to actual patch) to check from information from "index .." extended header line if the patch corresponds to the same or next difftree raw line. This could also be used to gather information needed for hyperlinking, and used for printing gitweb quoted filenames, from extended diff header instead of raw git-diff output. While at it, refactor git_patchset_body subroutine from the event-driven, AWK-like state-machine parsing to sequential parsing: for each patch parse (and output) git diff header, parse extended diff header, parse two-line from-file/to-file diff header, parse patch itself; patch ends with the end of input [file] or the line matching m/^diff /. For better understanding the code, there were added assertions in the comments a la Carp::Assert module. Just in case there is commented out code dealing with unexpected end of input (should not happen, hence commented out). Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 235 ++++++++++++++++++++++++++------------------- 1 file changed, 136 insertions(+), 99 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index b2482febf..bf58c3bac 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2202,31 +2202,56 @@ sub git_patchset_body { my ($fd, $difftree, $hash, $hash_parent) = @_; my $patch_idx = 0; - my $in_header = 0; - my $patch_found = 0; + my $patch_line; my $diffinfo; my (%from, %to); + my ($from_id, $to_id); print "
\n"; - LINE: - while (my $patch_line = <$fd>) { + # skip to first patch + while ($patch_line = <$fd>) { chomp $patch_line; - if ($patch_line =~ m/^diff /) { # "git diff" header - # beginning of patch (in patchset) - if ($patch_found) { - # close extended header for previous empty patch - if ($in_header) { - print "
\n" # class="diff extended_header" - } - # close previous patch - print "\n"; # class="patch" - } else { - # first patch in patchset - $patch_found = 1; + last if ($patch_line =~ m/^diff /); + } + + PATCH: + while ($patch_line) { + my @diff_header; + + # git diff header + #assert($patch_line =~ m/^diff /) if DEBUG; + #assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed + push @diff_header, $patch_line; + + # extended diff header + EXTENDED_HEADER: + while ($patch_line = <$fd>) { + chomp $patch_line; + + last EXTENDED_HEADER if ($patch_line =~ m/^--- /); + + if ($patch_line =~ m/^index ([0-9a-fA-F]{40})..([0-9a-fA-F]{40})/) { + $from_id = $1; + $to_id = $2; } - print "
\n"; + + push @diff_header, $patch_line; + } + #last PATCH unless $patch_line; + my $last_patch_line = $patch_line; + + # check if current patch belong to current raw line + # and parse raw git-diff line if needed + if (defined $diffinfo && + $diffinfo->{'from_id'} eq $from_id && + $diffinfo->{'to_id'} eq $to_id) { + # this is split patch + print "
\n"; + } else { + # advance raw git-diff output if needed + $patch_idx++ if defined $diffinfo; # read and prepare patch information if (ref($difftree->[$patch_idx]) eq "HASH") { @@ -2247,100 +2272,112 @@ sub git_patchset_body { hash=>$diffinfo->{'to_id'}, file_name=>$to{'file'}); } - $patch_idx++; - - # print "git diff" header - $patch_line =~ s!^(diff (.*?) )"?a/.*$!$1!; - if ($from{'href'}) { - $patch_line .= $cgi->a({-href => $from{'href'}, -class => "path"}, - 'a/' . esc_path($from{'file'})); - } else { # file was added - $patch_line .= 'a/' . esc_path($from{'file'}); - } - $patch_line .= ' '; - if ($to{'href'}) { - $patch_line .= $cgi->a({-href => $to{'href'}, -class => "path"}, - 'b/' . esc_path($to{'file'})); - } else { # file was deleted - $patch_line .= 'b/' . esc_path($to{'file'}); - } - - print "
$patch_line
\n"; - print "
\n"; - $in_header = 1; - next LINE; + # this is first patch for raw difftree line with $patch_idx index + # we index @$difftree array from 0, but number patches from 1 + print "
\n"; } - if ($in_header) { - if ($patch_line !~ m/^---/) { - # match - if ($patch_line =~ s!^((copy|rename) from ).*$!$1! && $from{'href'}) { - $patch_line .= $cgi->a({-href=>$from{'href'}, -class=>"path"}, - esc_path($from{'file'})); - } - if ($patch_line =~ s!^((copy|rename) to ).*$!$1! && $to{'href'}) { - $patch_line = $cgi->a({-href=>$to{'href'}, -class=>"path"}, - esc_path($to{'file'})); - } - # match - if ($patch_line =~ m/\s(\d{6})$/) { - $patch_line .= ' (' . - file_type_long($1) . - ')'; + # print "git diff" header + $patch_line = shift @diff_header; + $patch_line =~ s!^(diff (.*?) )"?a/.*$!$1!; + if ($from{'href'}) { + $patch_line .= $cgi->a({-href => $from{'href'}, -class => "path"}, + 'a/' . esc_path($from{'file'})); + } else { # file was added + $patch_line .= 'a/' . esc_path($from{'file'}); + } + $patch_line .= ' '; + if ($to{'href'}) { + $patch_line .= $cgi->a({-href => $to{'href'}, -class => "path"}, + 'b/' . esc_path($to{'file'})); + } else { # file was deleted + $patch_line .= 'b/' . esc_path($to{'file'}); + } + print "
$patch_line
\n"; + + # print extended diff header + print "
\n" if (@diff_header > 0); + EXTENDED_HEADER: + foreach $patch_line (@diff_header) { + # match + if ($patch_line =~ s!^((copy|rename) from ).*$!$1! && $from{'href'}) { + $patch_line .= $cgi->a({-href=>$from{'href'}, -class=>"path"}, + esc_path($from{'file'})); + } + if ($patch_line =~ s!^((copy|rename) to ).*$!$1! && $to{'href'}) { + $patch_line = $cgi->a({-href=>$to{'href'}, -class=>"path"}, + esc_path($to{'file'})); + } + # match + if ($patch_line =~ m/\s(\d{6})$/) { + $patch_line .= ' (' . + file_type_long($1) . + ')'; + } + # match + if ($patch_line =~ m/^index/) { + my ($from_link, $to_link); + if ($from{'href'}) { + $from_link = $cgi->a({-href=>$from{'href'}, -class=>"hash"}, + substr($diffinfo->{'from_id'},0,7)); + } else { + $from_link = '0' x 7; } - # match - if ($patch_line =~ m/^index/) { - my ($from_link, $to_link); - if ($from{'href'}) { - $from_link = $cgi->a({-href=>$from{'href'}, -class=>"hash"}, - substr($diffinfo->{'from_id'},0,7)); - } else { - $from_link = '0' x 7; - } - if ($to{'href'}) { - $to_link = $cgi->a({-href=>$to{'href'}, -class=>"hash"}, - substr($diffinfo->{'to_id'},0,7)); - } else { - $to_link = '0' x 7; - } - my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'}); - $patch_line =~ s!$from_id\.\.$to_id!$from_link..$to_link!; + if ($to{'href'}) { + $to_link = $cgi->a({-href=>$to{'href'}, -class=>"hash"}, + substr($diffinfo->{'to_id'},0,7)); + } else { + $to_link = '0' x 7; } - print $patch_line . "
\n"; - - } else { - #$in_header && $patch_line =~ m/^---/; - print "
\n"; # class="diff extended_header" - $in_header = 0; + #affirm { + # my ($from_hash, $to_hash) = + # ($patch_line =~ m/^index ([0-9a-fA-F]{40})..([0-9a-fA-F]{40})/); + # my ($from_id, $to_id) = + # ($diffinfo->{'from_id'}, $diffinfo->{'to_id'}); + # ($from_hash eq $from_id) && ($to_hash eq $to_id); + #} if DEBUG; + my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'}); + $patch_line =~ s!$from_id\.\.$to_id!$from_link..$to_link!; + } + print $patch_line . "
\n"; + } + print "
\n" if (@diff_header > 0); # class="diff extended_header" + + # from-file/to-file diff header + $patch_line = $last_patch_line; + #assert($patch_line =~ m/^---/) if DEBUG; + if ($from{'href'}) { + $patch_line = '--- a/' . + $cgi->a({-href=>$from{'href'}, -class=>"path"}, + esc_path($from{'file'})); + } + print "
$patch_line
\n"; - if ($from{'href'}) { - $patch_line = '--- a/' . - $cgi->a({-href=>$from{'href'}, -class=>"path"}, - esc_path($from{'file'})); - } - print "
$patch_line
\n"; + $patch_line = <$fd>; + #last PATCH unless $patch_line; + chomp $patch_line; - $patch_line = <$fd>; - chomp $patch_line; + #assert($patch_line =~ m/^+++/) if DEBUG; + if ($to{'href'}) { + $patch_line = '+++ b/' . + $cgi->a({-href=>$to{'href'}, -class=>"path"}, + esc_path($to{'file'})); + } + print "
$patch_line
\n"; - #$patch_line =~ m/^+++/; - if ($to{'href'}) { - $patch_line = '+++ b/' . - $cgi->a({-href=>$to{'href'}, -class=>"path"}, - esc_path($to{'file'})); - } - print "
$patch_line
\n"; + # the patch itself + LINE: + while ($patch_line = <$fd>) { + chomp $patch_line; - } + next PATCH if ($patch_line =~ m/^diff /); - next LINE; + print format_diff_line($patch_line); } - print format_diff_line($patch_line); + } continue { + print "
\n"; # class="patch" } - print "
\n" if $in_header; # extended header - - print "
\n" if $patch_found; # class="patch" print "\n"; # class="patchset" } From 9954f772eb73593d9e660e3b2c3f90341b03a087 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 18 Nov 2006 23:35:41 +0100 Subject: [PATCH 06/27] gitweb: Default to $hash_base or HEAD for $hash in "commit" and "commitdiff" Set $hash parameter to $hash_base || "HEAD" if it is not set (if it is not true to be more exact). This allows [hand-edited] URLs with 'action' "commit" or "commitdiff" but without 'hash' parameter. If there is 'h' (hash) parameter provided, then gitweb tries to use this. HEAD is used _only_ if nether hash, nor hash_base are provided, i.e. for URL like below URL?p=project.git;a=commit i.e. without neither 'h' nor 'hb'. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index bf58c3bac..19b3d36d1 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -3429,6 +3429,7 @@ sub git_log { } sub git_commit { + $hash ||= $hash_base || "HEAD"; my %co = parse_commit($hash); if (!%co) { die_error(undef, "Unknown commit object"); @@ -3706,6 +3707,7 @@ sub git_blobdiff_plain { sub git_commitdiff { my $format = shift || 'html'; + $hash ||= $hash_base || "HEAD"; my %co = parse_commit($hash); if (!%co) { die_error(undef, "Unknown commit object"); From 59e3b14e08bf309baa692bf251b2cedcde131308 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sat, 18 Nov 2006 23:35:40 +0100 Subject: [PATCH 07/27] gitweb: New improved formatting of chunk header in diff If we have provided enough info, and diff is not combined diff, and if provided diff line is chunk header, then: * split chunk header into .chunk_info and .section span elements, first containing proper chunk header, second section heading (aka. which function), for separate styling: the proper chunk header is on non-white background, section heading part uses slightly lighter color. * hyperlink from-file-range to starting line of from-file, if file was not created. * hyperlink to-file-range to starting line of to-file, if file was not deleted. Links are of invisible variety (and "list" class). Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.css | 13 +++++++++++++ gitweb/gitweb.perl | 23 ++++++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index 974b47f19..7177c6e86 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -334,11 +334,13 @@ div.diff.extended_header { padding: 2px 0px 2px 0px; } +div.diff a.list, div.diff a.path, div.diff a.hash { text-decoration: none; } +div.diff a.list:hover, div.diff a.path:hover, div.diff a.hash:hover { text-decoration: underline; @@ -362,14 +364,25 @@ div.diff.rem { color: #cc0000; } +div.diff.chunk_header a, div.diff.chunk_header { color: #990099; +} +div.diff.chunk_header { border: dotted #ffe0ff; border-width: 1px 0px 0px 0px; margin-top: 2px; } +div.diff.chunk_header span.chunk_info { + background-color: #ffeeff; +} + +div.diff.chunk_header span.section { + color: #aa22aa; +} + div.diff.incomplete { color: #cccccc; } diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 19b3d36d1..5875ba084 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -874,8 +874,10 @@ sub format_subject_html { } } +# format patch (diff) line (rather not to be used for diff headers) sub format_diff_line { my $line = shift; + my ($from, $to) = @_; my $char = substr($line, 0, 1); my $diff_class = ""; @@ -891,6 +893,25 @@ sub format_diff_line { $diff_class = " incomplete"; } $line = untabify($line); + if ($from && $to && $line =~ m/^\@{2} /) { + my ($from_text, $from_start, $from_lines, $to_text, $to_start, $to_lines, $section) = + $line =~ m/^\@{2} (-(\d+)(?:,(\d+))?) (\+(\d+)(?:,(\d+))?) \@{2}(.*)$/; + + $from_lines = 0 unless defined $from_lines; + $to_lines = 0 unless defined $to_lines; + + if ($from->{'href'}) { + $from_text = $cgi->a({-href=>"$from->{'href'}#l$from_start", + -class=>"list"}, $from_text); + } + if ($to->{'href'}) { + $to_text = $cgi->a({-href=>"$to->{'href'}#l$to_start", + -class=>"list"}, $to_text); + } + $line = "@@ $from_text $to_text @@" . + "" . esc_html($section, -nbsp=>1) . ""; + return "
$line
\n"; + } return "
" . esc_html($line, -nbsp=>1) . "
\n"; } @@ -2372,7 +2393,7 @@ sub git_patchset_body { next PATCH if ($patch_line =~ m/^diff /); - print format_diff_line($patch_line); + print format_diff_line($patch_line, \%from, \%to); } } continue { From bd5d1e42fb8edffa4e35d1257f648d586b2d054c Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 19 Nov 2006 15:05:21 +0100 Subject: [PATCH 08/27] gitweb: Add an option to href() to return full URL href subroutine by default generates absolute URL (generated using CGI::url(-absolute=>1), and saved in $my_uri) using $my_uri as base; add an option to generate full URL using $my_url as base. New feature usage: href(..., -full=>1) Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 5875ba084..873950126 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -459,7 +459,8 @@ sub evaluate_path_info { sub href(%) { my %params = @_; - my $href = $my_uri; + # default is to use -absolute url() i.e. $my_uri + my $href = $params{-full} ? $my_url : $my_uri; # XXX: Warning: If you touch this, check the search form for updating, # too. From af6feeb229110a0fa77a179c2655fca08e72f879 Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 19 Nov 2006 15:05:22 +0100 Subject: [PATCH 09/27] gitweb: Refactor feed generation, make output prettier, add Atom feed Add support for more modern Atom web feed format. Both RSS and Atom feeds are generated by git_feed subroutine to avoid code duplication; git_rss and git_atom are thin wrappers around git_feed. Add links to Atom feed in HTML header and in page footer (but not in OPML; we should use APP, Atom Publishing Proptocol instead). Allow for feed generation for branches other than current (HEAD) branch, and for generation of feeds for file or directory history. Do not use "pre ${\sub_returning_scalar(...)} post" trick, but join strings instead: "pre " . sub_returning_scalar(...) . " post". Use href(-full=>1, ...) instead of hand-crafting gitweb urls. Make output prettier: * Use title similar to the title of web page * Use project description (if exists) for description/subtitle * Do not add anything (committer name, commit date) to feed entry title * Wrap the commit message in
* Make file names into an unordered list
* Add links (diff, conditional blame, history) to the file list.

In addition to the above points, the attached patch emits a
Last-Changed: HTTP response header field, and doesn't compute the feed
body if the HTTP request type was HEAD. This helps keep the web server
load down for well-behaved feed readers that check if the feed needs
updating.

If browser (feed reader) sent Accept: header, and it prefers 'text/xml' type
to 'application/rss+xml' (in the case of RSS feed) or 'application/atom+xml'
(in the case of Atom feed), then use 'text/xml' as content type.

Both RSS and Atom feeds validate at http://feedvalidator.org
and at http://validator.w3.org/feed/

Signed-off-by: Jakub Narebski 
Signed-off-by: Andreas Fuchs 
Signed-off-by: Junio C Hamano 
---
 gitweb/gitweb.perl | 255 +++++++++++++++++++++++++++++++++++++--------
 1 file changed, 210 insertions(+), 45 deletions(-)

diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 873950126..a32a6b79c 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -425,6 +425,7 @@ sub evaluate_path_info {
 	"history" => \&git_history,
 	"log" => \&git_log,
 	"rss" => \&git_rss,
+	"atom" => \&git_atom,
 	"search" => \&git_search,
 	"search_help" => \&git_search_help,
 	"shortlog" => \&git_shortlog,
@@ -1198,10 +1199,12 @@ sub parse_date {
 	$date{'mday'} = $mday;
 	$date{'day'} = $days[$wday];
 	$date{'month'} = $months[$mon];
-	$date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000",
-	                   $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
+	$date{'rfc2822'}   = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000",
+	                     $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
 	$date{'mday-time'} = sprintf "%d %s %02d:%02d",
 	                     $mday, $months[$mon], $hour ,$min;
+	$date{'iso-8601'}  = sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ",
+	                     1900+$year, $mon, $mday, $hour ,$min, $sec;
 
 	$tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/;
 	my $local = $epoch + ((int $1 + ($2/60)) * 3600);
@@ -1209,9 +1212,9 @@ sub parse_date {
 	$date{'hour_local'} = $hour;
 	$date{'minute_local'} = $min;
 	$date{'tz_local'} = $tz;
-	$date{'iso-tz'} = sprintf ("%04d-%02d-%02d %02d:%02d:%02d %s",
-				   1900+$year, $mon+1, $mday,
-				   $hour, $min, $sec, $tz);
+	$date{'iso-tz'} = sprintf("%04d-%02d-%02d %02d:%02d:%02d %s",
+	                          1900+$year, $mon+1, $mday,
+	                          $hour, $min, $sec, $tz);
 	return %date;
 }
 
@@ -1672,14 +1675,17 @@ sub git_header_html {
 		}
 	}
 	if (defined $project) {
-		printf(''."\n",
+		printf(''."\n",
 		       esc_param($project), href(action=>"rss"));
+		printf(''."\n",
+		       esc_param($project), href(action=>"atom"));
 	} else {
 		printf(''."\n",
 		       $site_name, href(project=>undef, action=>"project_index"));
-		printf(''."\n",
 		       $site_name, href(project=>undef, action=>"opml"));
 	}
@@ -1745,7 +1751,9 @@ sub git_footer_html {
 			print "\n";
 		}
 		print $cgi->a({-href => href(action=>"rss"),
-		              -class => "rss_logo"}, "RSS") . "\n";
+		              -class => "rss_logo"}, "RSS") . " ";
+		print $cgi->a({-href => href(action=>"atom"),
+		              -class => "rss_logo"}, "Atom") . "\n";
 	} else {
 		print $cgi->a({-href => href(project=>undef, action=>"opml"),
 		              -class => "rss_logo"}, "OPML") . " ";
@@ -4150,26 +4158,125 @@ sub git_shortlog {
 }
 
 ## ......................................................................
-## feeds (RSS, OPML)
+## feeds (RSS, Atom; OPML)
 
-sub git_rss {
-	# http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
+sub git_feed {
+	my $format = shift || 'atom';
+	my ($have_blame) = gitweb_check_feature('blame');
+
+	# Atom: http://www.atomenabled.org/developers/syndication/
+	# RSS:  http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
+	if ($format ne 'rss' && $format ne 'atom') {
+		die_error(undef, "Unknown web feed format");
+	}
+
+	# log/feed of current (HEAD) branch, log of given branch, history of file/directory
+	my $head = $hash || 'HEAD';
 	open my $fd, "-|", git_cmd(), "rev-list", "--max-count=150",
-		git_get_head_hash($project), "--"
+		$head, "--", (defined $file_name ? $file_name : ())
 		or die_error(undef, "Open git-rev-list failed");
 	my @revlist = map { chomp; $_ } <$fd>;
 	close $fd or die_error(undef, "Reading git-rev-list failed");
-	print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
-	print <
+
+	my %latest_commit;
+	my %latest_date;
+	my $content_type = "application/$format+xml";
+	if (defined $cgi->http('HTTP_ACCEPT') &&
+		 $cgi->Accept('text/xml') > $cgi->Accept($content_type)) {
+		# browser (feed reader) prefers text/xml
+		$content_type = 'text/xml';
+	}
+	if (defined($revlist[0])) {
+		%latest_commit = parse_commit($revlist[0]);
+		%latest_date   = parse_date($latest_commit{'committer_epoch'});
+		print $cgi->header(
+			-type => $content_type,
+			-charset => 'utf-8',
+			-last_modified => $latest_date{'rfc2822'});
+	} else {
+		print $cgi->header(
+			-type => $content_type,
+			-charset => 'utf-8');
+	}
+
+	# Optimization: skip generating the body if client asks only
+	# for Last-Modified date.
+	return if ($cgi->request_method() eq 'HEAD');
+
+	# header variables
+	my $title = "$site_name - $project/$action";
+	my $feed_type = 'log';
+	if (defined $hash) {
+		$title .= " - '$hash'";
+		$feed_type = 'branch log';
+		if (defined $file_name) {
+			$title .= " :: $file_name";
+			$feed_type = 'history';
+		}
+	} elsif (defined $file_name) {
+		$title .= " - $file_name";
+		$feed_type = 'history';
+	}
+	$title .= " $feed_type";
+	my $descr = git_get_project_description($project);
+	if (defined $descr) {
+		$descr = esc_html($descr);
+	} else {
+		$descr = "$project " .
+		         ($format eq 'rss' ? 'RSS' : 'Atom') .
+		         " feed";
+	}
+	my $owner = git_get_project_owner($project);
+	$owner = esc_html($owner);
+
+	#header
+	my $alt_url;
+	if (defined $file_name) {
+		$alt_url = href(-full=>1, action=>"history", hash=>$hash, file_name=>$file_name);
+	} elsif (defined $hash) {
+		$alt_url = href(-full=>1, action=>"log", hash=>$hash);
+	} else {
+		$alt_url = href(-full=>1, action=>"summary");
+	}
+	print qq!\n!;
+	if ($format eq 'rss') {
+		print <
 
-$project $my_uri $my_url
-${\esc_html("$my_url?p=$project;a=summary")}
-$project log
-en
 XML
+		print "$title\n" .
+		      "$alt_url\n" .
+		      "$descr\n" .
+		      "en\n";
+	} elsif ($format eq 'atom') {
+		print <
+XML
+		print "$title\n" .
+		      "$descr\n" .
+		      '' . "\n" .
+		      '' . "\n" .
+		      "" . href(-full=>1) . "\n" .
+		      # use project owner for feed author
+		      "$owner\n";
+		if (defined $favicon) {
+			print "" . esc_url($favicon) . "\n";
+		}
+		if (defined $logo_url) {
+			# not twice as wide as tall: 72 x 27 pixels
+			print "" . esc_url($logo_url) . "\n";
+		}
+		if (! %latest_date) {
+			# dummy date to keep the feed valid until commits trickle in:
+			print "1970-01-01T00:00:00Z\n";
+		} else {
+			print "$latest_date{'iso-8601'}\n";
+		}
+	}
 
+	# contents
 	for (my $i = 0; $i <= $#revlist; $i++) {
 		my $commit = $revlist[$i];
 		my %co = parse_commit($commit);
@@ -4178,42 +4285,100 @@ sub git_rss {
 			last;
 		}
 		my %cd = parse_date($co{'committer_epoch'});
+
+		# get list of changed files
 		open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
-			$co{'parent'}, $co{'id'}, "--"
+			$co{'parent'}, $co{'id'}, "--", (defined $file_name ? $file_name : ())
 			or next;
 		my @difftree = map { chomp; $_ } <$fd>;
 		close $fd
 			or next;
-		print "\n" .
-		      "" .
-		      sprintf("%d %s %02d:%02d", $cd{'mday'}, $cd{'month'}, $cd{'hour'}, $cd{'minute'}) . " - " . esc_html($co{'title'}) .
-		      "\n" .
-		      "" . esc_html($co{'author'}) . "\n" .
-		      "$cd{'rfc2822'}\n" .
-		      "" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "\n" .
-		      "" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "\n" .
-		      "" . esc_html($co{'title'}) . "\n" .
-		      "" .
-		      "1, action=>"commit", hash=>$commit);
+		if ($format eq 'rss') {
+			print "\n" .
+			      "" . esc_html($co{'title'}) . "\n" .
+			      "" . esc_html($co{'author'}) . "\n" .
+			      "$cd{'rfc2822'}\n" .
+			      "$co_url\n" .
+			      "$co_url\n" .
+			      "" . esc_html($co{'title'}) . "\n" .
+			      "" .
+			      "\n" .
+			      "" . esc_html($co{'title'}) . "\n" .
+			      "$cd{'iso-8601'}\n" .
+			      "" . esc_html($co{'author_name'}) . "\n" .
+			      # use committer for contributor
+			      "" . esc_html($co{'committer_name'}) . "\n" .
+			      "$cd{'iso-8601'}\n" .
+			      "\n" .
+			      "$co_url\n" .
+			      "\n" .
+			      "
\n"; + } my $comment = $co{'comment'}; + print "
\n";
 		foreach my $line (@$comment) {
-			$line = to_utf8($line);
-			print "$line
\n"; + $line = esc_html($line); + print "$line\n"; } - print "
\n"; - foreach my $line (@difftree) { - if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) { - next; + print "
    \n"; + foreach my $difftree_line (@difftree) { + my %difftree = parse_difftree_raw_line($difftree_line); + next if !$difftree{'from_id'}; + + my $file = $difftree{'file'} || $difftree{'to_file'}; + + print "
  • " . + "[" . + $cgi->a({-href => href(-full=>1, action=>"blobdiff", + hash=>$difftree{'to_id'}, hash_parent=>$difftree{'from_id'}, + hash_base=>$co{'id'}, hash_parent_base=>$co{'parent'}, + file_name=>$file, file_parent=>$difftree{'from_file'}), + -title => "diff"}, 'D'); + if ($have_blame) { + print $cgi->a({-href => href(-full=>1, action=>"blame", + file_name=>$file, hash_base=>$commit), + -title => "blame"}, 'B'); } - my $file = esc_path(unquote($7)); - $file = to_utf8($file); - print "$file
    \n"; + # if this is not a feed of a file history + if (!defined $file_name || $file_name ne $file) { + print $cgi->a({-href => href(-full=>1, action=>"history", + file_name=>$file, hash=>$commit), + -title => "history"}, 'H'); + } + $file = esc_path($file); + print "] ". + "$file
  • \n"; + } + if ($format eq 'rss') { + print "
]]>\n" . + "\n" . + "\n"; + } elsif ($format eq 'atom') { + print "\n
\n" . + "
\n" . + "\n"; } - print "]]>\n" . - "
\n" . - "
\n"; } - print "
"; + + # end of feed + if ($format eq 'rss') { + print "\n\n"; + } elsif ($format eq 'atom') { + print "\n"; + } +} + +sub git_rss { + git_feed('rss'); +} + +sub git_atom { + git_feed('atom'); } sub git_opml { From 897d1d2e2a36d433c49d6246cd38133a540adddc Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Sun, 19 Nov 2006 22:51:39 +0100 Subject: [PATCH 10/27] gitweb: Finish restoring "blob" links in git_difftree_body This finishes work started by commit 4777b0141a4812177390da4b6ebc9d40ac3da4b5 "gitweb: Restore object-named links in item lists" by Petr Baudis. It brings back rest of "blob" links in difftree-raw like part of "commit" and "commitdiff" views, namely in git_difftree_body subroutine. Now the td.link table cell has the following links: * link to diff ("blobdiff" view) in "commit" view, if applicable (there is no link to uninteresting creation/deletion diff), or link to patch anchor in "commitdiff" view. * link to current version of file ("blob" view), with the obvious exception of file deletion, where it is link to the parent version. * link to "blame" view, if it is enabled, and file was not just created (i.e. it has any history). * link to history of the file ("history" view), again with sole exception of the case of new file. Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index a32a6b79c..ce185d903 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2092,7 +2092,11 @@ sub git_difftree_body { # link to patch $patchno++; print $cgi->a({-href => "#patch$patchno"}, "patch"); + print " | "; } + print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, + hash_base=>$hash, file_name=>$diff{'file'})}, + "blob") . " | "; print "\n"; } elsif ($diff{'status'} eq "D") { # deleted @@ -2112,13 +2116,11 @@ sub git_difftree_body { } print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, hash_base=>$parent, file_name=>$diff{'file'})}, - "blob") . " | "; + "blob") . " | "; if ($have_blame) { - print $cgi->a({-href => - href(action=>"blame", - hash_base=>$parent, - file_name=>$diff{'file'})}, - "blame") . " | "; + print $cgi->a({-href => href(action=>"blame", hash_base=>$parent, + file_name=>$diff{'file'})}, + "blame") . " | "; } print $cgi->a({-href => href(action=>"history", hash_base=>$parent, file_name=>$diff{'file'})}, @@ -2163,13 +2165,12 @@ sub git_difftree_body { " | "; } print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, - hash_base=>$hash, file_name=>$diff{'file'})}, - "blob") . " | "; + hash_base=>$hash, file_name=>$diff{'file'})}, + "blob") . " | "; if ($have_blame) { - print $cgi->a({-href => href(action=>"blame", - hash_base=>$hash, - file_name=>$diff{'file'})}, - "blame") . " | "; + print $cgi->a({-href => href(action=>"blame", hash_base=>$hash, + file_name=>$diff{'file'})}, + "blame") . " | "; } print $cgi->a({-href => href(action=>"history", hash_base=>$hash, file_name=>$diff{'file'})}, @@ -2208,17 +2209,16 @@ sub git_difftree_body { "diff") . " | "; } - print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'}, - hash_base=>$parent, file_name=>$diff{'from_file'})}, - "blob") . " | "; + print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'}, + hash_base=>$parent, file_name=>$diff{'to_file'})}, + "blob") . " | "; if ($have_blame) { - print $cgi->a({-href => href(action=>"blame", - hash_base=>$hash, - file_name=>$diff{'to_file'})}, - "blame") . " | "; + print $cgi->a({-href => href(action=>"blame", hash_base=>$hash, + file_name=>$diff{'to_file'})}, + "blame") . " | "; } - print $cgi->a({-href => href(action=>"history", hash_base=>$parent, - file_name=>$diff{'from_file'})}, + print $cgi->a({-href => href(action=>"history", hash_base=>$hash, + file_name=>$diff{'to_file'})}, "history"); print "\n"; From 21f88ac84a024fcc73a22547e51509aa1459ab03 Mon Sep 17 00:00:00 2001 From: Andy Parkins Date: Thu, 2 Nov 2006 12:12:26 +0100 Subject: [PATCH 11/27] Improve git-prune -n output prune_object() in show_only mode would previously just show the path to the object that would be deleted. The path the object is stored in shouldn't be shown to users, they only know about sha1 identifiers so show that instead. Further, the sha1 alone isn't that useful for examining what is going to be deleted. This patch also adds the object type to the output, which makes it easy to pick out, say, the commits and use git-show to display them. Signed-off-by: Andy Parkins Signed-off-by: Junio C Hamano --- builtin-prune.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/builtin-prune.c b/builtin-prune.c index d853902c5..286a94c3f 100644 --- a/builtin-prune.c +++ b/builtin-prune.c @@ -16,8 +16,14 @@ static struct rev_info revs; static int prune_object(char *path, const char *filename, const unsigned char *sha1) { + char buf[20]; + const char *type; + if (show_only) { - printf("would prune %s/%s\n", path, filename); + type = buf; + if (sha1_object_info(sha1, type, NULL)) + type = "unknown"; + printf("%s %s\n", sha1_to_hex(sha1), type ); return 0; } unlink(mkpath("%s/%s", path, filename)); From bfcc921430423828233dadfe274802ae68900f6e Mon Sep 17 00:00:00 2001 From: Andy Parkins Date: Tue, 21 Nov 2006 19:31:24 +0000 Subject: [PATCH 12/27] Add support to git-branch to show local and remote branches Instead of storing a list of refnames in append_ref, a list of structures is created. Each of these stores the refname and a symbolic constant representing its type. The creation of the list is filtered based on a command line switch; no switch means "local branches only", "-r" means "remote branches only" (as they always did); but now "-a" means "local branches or remote branches". As a side effect, the list is now not global, but allocated in print_ref_list() where it used. Also a memory leak is plugged, the memory allocated during the list creation was never freed. It lays a groundwork to also display tags, but the command being 'git branch' it is not currently used. Signed-off-by: Andy Parkins Signed-off-by: Junio C Hamano --- Documentation/git-branch.txt | 11 +++- builtin-branch.c | 103 +++++++++++++++++++++++++++-------- 2 files changed, 89 insertions(+), 25 deletions(-) diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index d43ef1dec..537676081 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -8,14 +8,16 @@ git-branch - List, create, or delete branches. SYNOPSIS -------- [verse] -'git-branch' [-r] +'git-branch' [-r] [-a] 'git-branch' [-l] [-f] [] 'git-branch' (-d | -D) ... DESCRIPTION ----------- -With no arguments given (or just `-r`) a list of available branches +With no arguments given a list of existing branches will be shown, the current branch will be highlighted with an asterisk. +Option `-r` causes the remote-tracking branches to be listed, +and option `-a` shows both. In its second form, a new branch named will be created. It will start out with a head equal to the one given as . @@ -45,7 +47,10 @@ OPTIONS a branch that already exists with the same name. -r:: - List only the "remote" branches. + List the remote-tracking branches. + +-a:: + List both remote-tracking branches and local branches. :: The name of the branch to create or delete. diff --git a/builtin-branch.c b/builtin-branch.c index 368b68ec9..22e3285a4 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -11,7 +11,7 @@ #include "builtin.h" static const char builtin_branch_usage[] = -"git-branch (-d | -D) | [-l] [-f] [] | [-r]"; +"git-branch (-d | -D) | [-l] [-f] [] | [-r] | [-a]"; static const char *head; @@ -79,46 +79,100 @@ static void delete_branches(int argc, const char **argv, int force) } } -static int ref_index, ref_alloc; -static char **ref_list; +#define REF_UNKNOWN_TYPE 0x00 +#define REF_LOCAL_BRANCH 0x01 +#define REF_REMOTE_BRANCH 0x02 +#define REF_TAG 0x04 -static int append_ref(const char *refname, const unsigned char *sha1, int flags, - void *cb_data) +struct ref_item { + char *name; + unsigned int kind; +}; + +struct ref_list { + int index, alloc; + struct ref_item *list; + int kinds; +}; + +static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data) { - if (ref_index >= ref_alloc) { - ref_alloc = alloc_nr(ref_alloc); - ref_list = xrealloc(ref_list, ref_alloc * sizeof(char *)); + struct ref_list *ref_list = (struct ref_list*)(cb_data); + struct ref_item *newitem; + int kind = REF_UNKNOWN_TYPE; + + /* Detect kind */ + if (!strncmp(refname, "refs/heads/", 11)) { + kind = REF_LOCAL_BRANCH; + refname += 11; + } else if (!strncmp(refname, "refs/remotes/", 13)) { + kind = REF_REMOTE_BRANCH; + refname += 13; + } else if (!strncmp(refname, "refs/tags/", 10)) { + kind = REF_TAG; + refname += 10; + } + + /* Don't add types the caller doesn't want */ + if ((kind & ref_list->kinds) == 0) + return 0; + + /* Resize buffer */ + if (ref_list->index >= ref_list->alloc) { + ref_list->alloc = alloc_nr(ref_list->alloc); + ref_list->list = xrealloc(ref_list->list, + ref_list->alloc * sizeof(struct ref_item)); } - ref_list[ref_index++] = xstrdup(refname); + /* Record the new item */ + newitem = &(ref_list->list[ref_list->index++]); + newitem->name = xstrdup(refname); + newitem->kind = kind; return 0; } +static void free_ref_list(struct ref_list *ref_list) +{ + int i; + + for (i = 0; i < ref_list->index; i++) + free(ref_list->list[i].name); + free(ref_list->list); +} + static int ref_cmp(const void *r1, const void *r2) { - return strcmp(*(char **)r1, *(char **)r2); + struct ref_item *c1 = (struct ref_item *)(r1); + struct ref_item *c2 = (struct ref_item *)(r2); + + if (c1->kind != c2->kind) + return c1->kind - c2->kind; + return strcmp(c1->name, c2->name); } -static void print_ref_list(int remote_only) +static void print_ref_list(int kinds) { int i; char c; + struct ref_list ref_list; - if (remote_only) - for_each_remote_ref(append_ref, NULL); - else - for_each_branch_ref(append_ref, NULL); + memset(&ref_list, 0, sizeof(ref_list)); + ref_list.kinds = kinds; + for_each_ref(append_ref, &ref_list); - qsort(ref_list, ref_index, sizeof(char *), ref_cmp); + qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp); - for (i = 0; i < ref_index; i++) { + for (i = 0; i < ref_list.index; i++) { c = ' '; - if (!strcmp(ref_list[i], head)) + if (ref_list.list[i].kind == REF_LOCAL_BRANCH && + !strcmp(ref_list.list[i].name, head)) c = '*'; - printf("%c %s\n", c, ref_list[i]); + printf("%c %s\n", c, ref_list.list[i].name); } + + free_ref_list(&ref_list); } static void create_branch(const char *name, const char *start, @@ -160,8 +214,9 @@ static void create_branch(const char *name, const char *start, int cmd_branch(int argc, const char **argv, const char *prefix) { - int delete = 0, force_delete = 0, force_create = 0, remote_only = 0; + int delete = 0, force_delete = 0, force_create = 0; int reflog = 0; + int kinds = REF_LOCAL_BRANCH; int i; git_config(git_default_config); @@ -189,7 +244,11 @@ int cmd_branch(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "-r")) { - remote_only = 1; + kinds = REF_REMOTE_BRANCH; + continue; + } + if (!strcmp(arg, "-a")) { + kinds = REF_REMOTE_BRANCH | REF_LOCAL_BRANCH; continue; } if (!strcmp(arg, "-l")) { @@ -209,7 +268,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (delete) delete_branches(argc - i, argv + i, force_delete); else if (i == argc) - print_ref_list(remote_only); + print_ref_list(kinds); else if (i == argc - 1) create_branch(argv[i], head, force_create, reflog); else if (i == argc - 2) From f73da29fa2be2f4bbda86e006b743b8121bdbf19 Mon Sep 17 00:00:00 2001 From: Andy Parkins Date: Thu, 23 Nov 2006 10:05:17 +0000 Subject: [PATCH 13/27] Increase length of function name buffer In xemit.c:xdl_emit_diff() a buffer for showing the function name as commentary is allocated; this buffer was 40 characters. This is a bit small; particularly for C++ function names where there is often an identical prefix (like void LongNamespace::LongClassName) on multiple functions, which makes the context the same everywhere. In other words the context is useless. This patch increases that buffer to 80 characters - which may still not be enough, but is better Signed-off-by: Andy Parkins Signed-off-by: Junio C Hamano --- xdiff/xemit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xdiff/xemit.c b/xdiff/xemit.c index 07995ec33..e291dc760 100644 --- a/xdiff/xemit.c +++ b/xdiff/xemit.c @@ -118,7 +118,7 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, xdemitconf_t const *xecfg) { long s1, s2, e1, e2, lctx; xdchange_t *xch, *xche; - char funcbuf[40]; + char funcbuf[80]; long funclen = 0; if (xecfg->flags & XDL_EMIT_COMMON) From 29561ad0adadf4884858c209bb22b41d8d9a66ba Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 23 Nov 2006 21:52:18 -0800 Subject: [PATCH 14/27] refs outside refs/{heads,tags} match less strongly. This changes the refname matching logic used to decide which ref is updated with git-send-pack. We used to error out when pushing 'master' when the other end has both 'master' branch and a tracking branch 'remotes/$name/master' but with this, 'master' matches only 'refs/heads/master' when both and no other 'master' exist. Pushing 'foo' when both heads/foo and tags/foo exist at the remote end is still considered an error and you would need to disambiguate between them by being more explicit. When neither heads/foo nor tags/foo exists at the remote, pushing 'foo' when there is only remotes/origin/foo is not ambiguous, while it still is ambiguous when there are more than one such weaker match (remotes/origin/foo and remotes/alt/foo, for example). Signed-off-by: Junio C Hamano --- connect.c | 47 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/connect.c b/connect.c index c55a20a4a..b9666cc0d 100644 --- a/connect.c +++ b/connect.c @@ -174,21 +174,58 @@ static int count_refspec_match(const char *pattern, struct ref *refs, struct ref **matched_ref) { - int match; int patlen = strlen(pattern); + struct ref *matched_weak = NULL; + struct ref *matched = NULL; + int weak_match = 0; + int match = 0; - for (match = 0; refs; refs = refs->next) { + for (weak_match = match = 0; refs; refs = refs->next) { char *name = refs->name; int namelen = strlen(name); + int weak_match; + if (namelen < patlen || memcmp(name + namelen - patlen, pattern, patlen)) continue; if (namelen != patlen && name[namelen - patlen - 1] != '/') continue; - match++; - *matched_ref = refs; + + /* A match is "weak" if it is with refs outside + * heads or tags, and did not specify the pattern + * in full (e.g. "refs/remotes/origin/master") or at + * least from the toplevel (e.g. "remotes/origin/master"); + * otherwise "git push $URL master" would result in + * ambiguity between remotes/origin/master and heads/master + * at the remote site. + */ + if (namelen != patlen && + patlen != namelen - 5 && + strncmp(name, "refs/heads/", 11) && + strncmp(name, "refs/tags/", 10)) { + /* We want to catch the case where only weak + * matches are found and there are multiple + * matches, and where more than one strong + * matches are found, as ambiguous. One + * strong match with zero or more weak matches + * are acceptable as a unique match. + */ + matched_weak = refs; + weak_match++; + } + else { + matched = refs; + match++; + } + } + if (!matched) { + *matched_ref = matched_weak; + return weak_match; + } + else { + *matched_ref = matched; + return match; } - return match; } static void link_dst_tail(struct ref *ref, struct ref ***tail) From 7182135189d1942d8538a0d0c17fc79f5cb82c71 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Thu, 23 Nov 2006 23:58:35 +0100 Subject: [PATCH 15/27] Make git-clone --use-separate-remote the default We've talked about this for quite some time on the list, and it is a sane thing to do for a repository with an associcated working tree. For somebody who wants to use the traditional layout, there is a backward compatibility option --use-immingled-remote, but it is expected to be removed before the next major release. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano --- Documentation/git-clone.txt | 24 ++++++++++++++++++------ git-clone.sh | 14 +++++++------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index 86060472a..4cb42237b 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -11,7 +11,8 @@ SYNOPSIS [verse] 'git-clone' [--template=] [-l [-s]] [-q] [-n] [--bare] [-o ] [-u ] [--reference ] - [--use-separate-remote] [] + [--use-separate-remote | --use-immingled-remote] + [] DESCRIPTION ----------- @@ -71,9 +72,13 @@ OPTIONS Make a 'bare' GIT repository. That is, instead of creating `` and placing the administrative files in `/.git`, make the `` - itself the `$GIT_DIR`. This implies `-n` option. When - this option is used, neither the `origin` branch nor the - default `remotes/origin` file is created. + itself the `$GIT_DIR`. This obviously implies the `-n` + because there is nowhere to check out the working tree. + Also the branch heads at the remote are copied directly + to corresponding local branch heads, without mapping + them to `refs/remotes/origin/`. When this option is + used, neither the `origin` branch nor the default + `remotes/origin` file is created. --origin :: -o :: @@ -97,8 +102,15 @@ OPTIONS --use-separate-remote:: Save remotes heads under `$GIT_DIR/remotes/origin/` instead - of `$GIT_DIR/refs/heads/`. Only the master branch is saved - in the latter. + of `$GIT_DIR/refs/heads/`. Only the local master branch is + saved in the latter. This is the default. + +--use-immingled-remote:: + Save remotes heads in the same namespace as the local + heads, `$GIT_DIR/refs/heads/'. In regular repositories, + this is a legacy setup git-clone created by default in + older Git versions, and will be removed before the next + major release. :: The (possibly remote) repository to clone from. It can diff --git a/git-clone.sh b/git-clone.sh index 3f006d1a7..9ed413554 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -14,7 +14,7 @@ die() { } usage() { - die "Usage: $0 [--template=] [--use-separate-remote] [--reference ] [--bare] [-l [-s]] [-q] [-u ] [--origin ] [-n] []" + die "Usage: $0 [--template=] [--use-immingled-remote] [--reference ] [--bare] [-l [-s]] [-q] [-u ] [--origin ] [-n] []" } get_repo_base() { @@ -115,7 +115,7 @@ bare= reference= origin= origin_override= -use_separate_remote= +use_separate_remote=t while case "$#,$1" in 0,*) break ;; @@ -134,7 +134,10 @@ while template="$1" ;; *,-q|*,--quiet) quiet=-q ;; *,--use-separate-remote) + # default use_separate_remote=t ;; + *,--use-immingled-remote) + use_separate_remote= ;; 1,--reference) usage ;; *,--reference) shift; reference="$1" ;; @@ -169,18 +172,15 @@ repo="$1" test -n "$repo" || die 'you must specify a repository to clone.' -# --bare implies --no-checkout +# --bare implies --no-checkout and --use-immingled-remote if test yes = "$bare" then if test yes = "$origin_override" then die '--bare and --origin $origin options are incompatible.' fi - if test t = "$use_separate_remote" - then - die '--bare and --use-separate-remote options are incompatible.' - fi no_checkout=yes + use_separate_remote= fi if test -z "$origin" From 73bcf53342f16a66ae4e02ed50a08bd34d846bfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?I=C3=B1aki=20Arenaza?= Date: Wed, 22 Nov 2006 23:26:57 +0100 Subject: [PATCH 16/27] git-cvsimport: add support for CVS pserver method HTTP/1.x proxying MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds support for 'proxy' and 'proxyport' connection options when using the pserver method for the CVS Root. It has been tested with a Squid 2.5.x proxy server. Quoting from the CVS info manual: The `gserver' and `pserver' connection methods all accept optional method options, specified as part of the METHOD string, like so: :METHOD[;OPTION=ARG...]: Currently, the only two valid connection options are `proxy', which takes a hostname as an argument, and `proxyport', which takes a port number as an argument. These options can be used to connect via an HTTP tunnel style web proxy. For example, to connect pserver via a web proxy at www.myproxy.net and port 8000, you would use a method of: :pserver;proxy=www.myproxy.net;proxyport=8000: *NOTE: The rest of the connection string is required to connect to the server as noted in the upcoming sections on password authentication, gserver and kserver. The example above would only modify the METHOD portion of the repository name.* PROXY must be supplied to connect to a CVS server via a proxy server, but PROXYPORT will default to port 8080 if not supplied. PROXYPORT may also be set via the CVS_PROXY_PORT environment variable. Signed-off-by: IƱaki Arenaza Signed-off-by: Junio C Hamano --- git-cvsimport.perl | 54 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/git-cvsimport.perl b/git-cvsimport.perl index b54a9486d..4310dea13 100755 --- a/git-cvsimport.perl +++ b/git-cvsimport.perl @@ -161,8 +161,22 @@ sub new { sub conn { my $self = shift; my $repo = $self->{'fullrep'}; - if($repo =~ s/^:pserver:(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?//) { - my($user,$pass,$serv,$port) = ($1,$2,$3,$4); + if($repo =~ s/^:pserver(?:([^:]*)):(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?//) { + my($param,$user,$pass,$serv,$port) = ($1,$2,$3,$4,$5); + + my($proxyhost,$proxyport); + if($param && ($param =~ m/proxy=([^;]+)/)) { + $proxyhost = $1; + # Default proxyport, if not specified, is 8080. + $proxyport = 8080; + if($ENV{"CVS_PROXY_PORT"}) { + $proxyport = $ENV{"CVS_PROXY_PORT"}; + } + if($param =~ m/proxyport=([^;]+)/){ + $proxyport = $1; + } + } + $user="anonymous" unless defined $user; my $rr2 = "-"; unless($port) { @@ -187,13 +201,43 @@ sub conn { } $pass="A" unless $pass; - my $s = IO::Socket::INET->new(PeerHost => $serv, PeerPort => $port); - die "Socket to $serv: $!\n" unless defined $s; + my ($s, $rep); + if($proxyhost) { + + # Use a HTTP Proxy. Only works for HTTP proxies that + # don't require user authentication + # + # See: http://www.ietf.org/rfc/rfc2817.txt + + $s = IO::Socket::INET->new(PeerHost => $proxyhost, PeerPort => $proxyport); + die "Socket to $proxyhost: $!\n" unless defined $s; + $s->write("CONNECT $serv:$port HTTP/1.1\r\nHost: $serv:$port\r\n\r\n") + or die "Write to $proxyhost: $!\n"; + $s->flush(); + + $rep = <$s>; + + # The answer should look like 'HTTP/1.x 2yy ....' + if(!($rep =~ m#^HTTP/1\.. 2[0-9][0-9]#)) { + die "Proxy connect: $rep\n"; + } + # Skip up to the empty line of the proxy server output + # including the response headers. + while ($rep = <$s>) { + last if (!defined $rep || + $rep eq "\n" || + $rep eq "\r\n"); + } + } else { + $s = IO::Socket::INET->new(PeerHost => $serv, PeerPort => $port); + die "Socket to $serv: $!\n" unless defined $s; + } + $s->write("BEGIN AUTH REQUEST\n$repo\n$user\n$pass\nEND AUTH REQUEST\n") or die "Write to $serv: $!\n"; $s->flush(); - my $rep = <$s>; + $rep = <$s>; if($rep ne "I LOVE YOU\n") { $rep="" unless $rep; From 30d055aa1e45b4740edc3bfbff77fa5d65e106ff Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 24 Nov 2006 01:38:04 -0800 Subject: [PATCH 17/27] git-svn: handle authentication without relying on cached tokens on disk This is mostly gleaned off SVN::Mirror, with added support for --no-auth-cache and --config-dir. Even with this patch, git-svn does not yet support repositories where the user only has partial read permissions. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano --- git-svn.perl | 156 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 148 insertions(+), 8 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index bb8935afe..47cd3e27f 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -39,7 +39,7 @@ memoize('cmt_metadata'); memoize('get_commit_time'); -my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib); +my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib, $AUTH_BATON, $AUTH_CALLBACKS); sub nag_lib { print STDERR < \$_repack, 'no-metadata' => \$_no_metadata, 'quiet|q' => \$_q, + 'username=s' => \$_username, + 'config-dir=s' => \$_config_dir, + 'no-auth-cache' => \$_no_auth_cache, 'ignore-nodate' => \$_ignore_nodate, 'repack-flags|repack-args|repack-opts=s' => \$_repack_flags); @@ -2683,18 +2687,154 @@ sub libsvn_load { my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file. $SVN::Node::dir.$SVN::Node::unknown. $SVN::Node::none.$SVN::Node::file. - $SVN::Node::dir.$SVN::Node::unknown; + $SVN::Node::dir.$SVN::Node::unknown. + $SVN::Auth::SSL::CNMISMATCH. + $SVN::Auth::SSL::NOTYETVALID. + $SVN::Auth::SSL::EXPIRED. + $SVN::Auth::SSL::UNKNOWNCA. + $SVN::Auth::SSL::OTHER; 1; }; } +sub _simple_prompt { + my ($cred, $realm, $default_username, $may_save, $pool) = @_; + $may_save = undef if $_no_auth_cache; + $default_username = $_username if defined $_username; + if (defined $default_username && length $default_username) { + if (defined $realm && length $realm) { + print "Authentication realm: $realm\n"; + } + $cred->username($default_username); + } else { + _username_prompt($cred, $realm, $may_save, $pool); + } + $cred->password(_read_password("Password for '" . + $cred->username . "': ", $realm)); + $cred->may_save($may_save); + $SVN::_Core::SVN_NO_ERROR; +} + +sub _ssl_server_trust_prompt { + my ($cred, $realm, $failures, $cert_info, $may_save, $pool) = @_; + $may_save = undef if $_no_auth_cache; + print "Error validating server certificate for '$realm':\n"; + if ($failures & $SVN::Auth::SSL::UNKNOWNCA) { + print " - The certificate is not issued by a trusted ", + "authority. Use the\n", + " fingerprint to validate the certificate manually!\n"; + } + if ($failures & $SVN::Auth::SSL::CNMISMATCH) { + print " - The certificate hostname does not match.\n"; + } + if ($failures & $SVN::Auth::SSL::NOTYETVALID) { + print " - The certificate is not yet valid.\n"; + } + if ($failures & $SVN::Auth::SSL::EXPIRED) { + print " - The certificate has expired.\n"; + } + if ($failures & $SVN::Auth::SSL::OTHER) { + print " - The certificate has an unknown error.\n"; + } + printf( "Certificate information:\n". + " - Hostname: %s\n". + " - Valid: from %s until %s\n". + " - Issuer: %s\n". + " - Fingerprint: %s\n", + map $cert_info->$_, qw(hostname valid_from valid_until + issuer_dname fingerprint) ); + my $choice; +prompt: + print $may_save ? + "(R)eject, accept (t)emporarily or accept (p)ermanently? " : + "(R)eject or accept (t)emporarily? "; + $choice = lc(substr( || 'R', 0, 1)); + if ($choice =~ /^t$/i) { + $cred->may_save(undef); + } elsif ($choice =~ /^r$/i) { + return -1; + } elsif ($may_save && $choice =~ /^p$/i) { + $cred->may_save($may_save); + } else { + goto prompt; + } + $cred->accepted_failures($failures); + $SVN::_Core::SVN_NO_ERROR; +} + +sub _ssl_client_cert_prompt { + my ($cred, $realm, $may_save, $pool) = @_; + $may_save = undef if $_no_auth_cache; + print "Client certificate filename: "; + chomp(my $filename = ); + $cred->cert_file($filename); + $cred->may_save($may_save); + $SVN::_Core::SVN_NO_ERROR; +} + +sub _ssl_client_cert_pw_prompt { + my ($cred, $realm, $may_save, $pool) = @_; + $may_save = undef if $_no_auth_cache; + $cred->password(_read_password("Password: ", $realm)); + $cred->may_save($may_save); + $SVN::_Core::SVN_NO_ERROR; +} + +sub _username_prompt { + my ($cred, $realm, $may_save, $pool) = @_; + $may_save = undef if $_no_auth_cache; + if (defined $realm && length $realm) { + print "Authentication realm: $realm\n"; + } + my $username; + if (defined $_username) { + $username = $_username; + } else { + print "Username: "; + chomp($username = ); + } + $cred->username($username); + $cred->may_save($may_save); + $SVN::_Core::SVN_NO_ERROR; +} + +sub _read_password { + my ($prompt, $realm) = @_; + print $prompt; + require Term::ReadKey; + Term::ReadKey::ReadMode('noecho'); + my $password = ''; + while (defined(my $key = Term::ReadKey::ReadKey(0))) { + last if $key =~ /[\012\015]/; # \n\r + $password .= $key; + } + Term::ReadKey::ReadMode('restore'); + print "\n"; + $password; +} + sub libsvn_connect { my ($url) = @_; - my $auth = SVN::Core::auth_open([SVN::Client::get_simple_provider(), - SVN::Client::get_ssl_server_trust_file_provider(), - SVN::Client::get_username_provider()]); - my $s = eval { SVN::Ra->new(url => $url, auth => $auth) }; - return $s; + if (!$AUTH_BATON || !$AUTH_CALLBACKS) { + SVN::_Core::svn_config_ensure($_config_dir, undef); + ($AUTH_BATON, $AUTH_CALLBACKS) = SVN::Core::auth_open_helper([ + SVN::Client::get_simple_provider(), + SVN::Client::get_ssl_server_trust_file_provider(), + SVN::Client::get_simple_prompt_provider( + \&_simple_prompt, 2), + SVN::Client::get_ssl_client_cert_prompt_provider( + \&_ssl_client_cert_prompt, 2), + SVN::Client::get_ssl_client_cert_pw_prompt_provider( + \&_ssl_client_cert_pw_prompt, 2), + SVN::Client::get_username_provider(), + SVN::Client::get_ssl_server_trust_prompt_provider( + \&_ssl_server_trust_prompt), + SVN::Client::get_username_prompt_provider( + \&_username_prompt, 2), + ]); + } + SVN::Ra->new(url => $url, auth => $AUTH_BATON, + auth_provider_callbacks => $AUTH_CALLBACKS); } sub libsvn_get_file { From 0f03ca946142bd656c1af9ff811cb9efbc8314da Mon Sep 17 00:00:00 2001 From: Peter Baumann Date: Thu, 23 Nov 2006 10:36:33 +0100 Subject: [PATCH 18/27] config option log.showroot to show the diff of root commits This allows one to see a root commit as a diff in commands like git-log, git-show and git-whatchanged. Signed-off-by: Peter Baumann Signed-off-by: Junio C Hamano --- Documentation/config.txt | 6 ++++++ builtin-log.c | 20 ++++++++++++++++---- t/t4013-diff-various.sh | 1 + 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 9d3c71c3b..909076281 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -219,6 +219,12 @@ i18n.commitEncoding:: browser (and possibly at other places in the future or in other porcelains). See e.g. gitlink:git-mailinfo[1]. Defaults to 'utf-8'. +log.showroot:: + If true, the initial commit will be shown as a big creation event. + This is equivalent to a diff against an empty tree. + Tools like gitlink:git-log[1] or gitlink:git-whatchanged[1], which + normally hide the root commit will now show it. True by default. + merge.summary:: Whether to include summaries of merged commits in newly created merge commit messages. False by default. diff --git a/builtin-log.c b/builtin-log.c index fedb0137b..7acf5d3b0 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -13,6 +13,8 @@ #include #include +static int default_show_root = 1; + /* this is in builtin-diff.c */ void add_head(struct rev_info *revs); @@ -22,6 +24,7 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, rev->abbrev = DEFAULT_ABBREV; rev->commit_format = CMIT_FMT_DEFAULT; rev->verbose_header = 1; + rev->show_root_diff = default_show_root; argc = setup_revisions(argc, argv, rev, "HEAD"); if (rev->diffopt.pickaxe || rev->diffopt.filter) rev->always_show_header = 0; @@ -44,11 +47,20 @@ static int cmd_log_walk(struct rev_info *rev) return 0; } +static int git_log_config(const char *var, const char *value) +{ + if (!strcmp(var, "log.showroot")) { + default_show_root = git_config_bool(var, value); + return 0; + } + return git_diff_ui_config(var, value); +} + int cmd_whatchanged(int argc, const char **argv, const char *prefix) { struct rev_info rev; - git_config(git_diff_ui_config); + git_config(git_log_config); init_revisions(&rev, prefix); rev.diff = 1; rev.diffopt.recursive = 1; @@ -63,7 +75,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) { struct rev_info rev; - git_config(git_diff_ui_config); + git_config(git_log_config); init_revisions(&rev, prefix); rev.diff = 1; rev.diffopt.recursive = 1; @@ -80,7 +92,7 @@ int cmd_log(int argc, const char **argv, const char *prefix) { struct rev_info rev; - git_config(git_diff_ui_config); + git_config(git_log_config); init_revisions(&rev, prefix); rev.always_show_header = 1; cmd_log_init(argc, argv, prefix, &rev); @@ -109,7 +121,7 @@ static int git_format_config(const char *var, const char *value) if (!strcmp(var, "diff.color")) { return 0; } - return git_diff_ui_config(var, value); + return git_log_config(var, value); } diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index 71c454356..ed37141b6 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -73,6 +73,7 @@ test_expect_success setup ' for i in 1 2; do echo $i; done >>dir/sub && git update-index file0 dir/sub && + git repo-config log.showroot false && git commit --amend && git show-branch ' From ac1471b39f1413046d4e338d898a93e403cbd3e5 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 24 Nov 2006 02:54:37 -0800 Subject: [PATCH 19/27] Typefix builtin-prune.c::prune_object() It passed (const char*) to a function that took a (char *); the buffer itself was of course writable, so pass the buffer itself. Signed-off-by: Junio C Hamano --- builtin-prune.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/builtin-prune.c b/builtin-prune.c index 286a94c3f..8591d28b8 100644 --- a/builtin-prune.c +++ b/builtin-prune.c @@ -20,10 +20,11 @@ static int prune_object(char *path, const char *filename, const unsigned char *s const char *type; if (show_only) { - type = buf; - if (sha1_object_info(sha1, type, NULL)) + if (sha1_object_info(sha1, buf, NULL)) type = "unknown"; - printf("%s %s\n", sha1_to_hex(sha1), type ); + else + type = buf; + printf("%s %s\n", sha1_to_hex(sha1), type); return 0; } unlink(mkpath("%s/%s", path, filename)); From 793c400cc1028cca4078281d69aa43fac2d6d0ba Mon Sep 17 00:00:00 2001 From: Jakub Narebski Date: Fri, 24 Nov 2006 22:25:50 +0100 Subject: [PATCH 20/27] gitweb: Replace SPC with   also in tag comment Commit messages had SPC replaced with   entity; make it so also in tag message (tag comment). Signed-off-by: Jakub Narebski Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index ce185d903..75e3502fe 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2919,7 +2919,7 @@ sub git_tag { my $comment = $tag{'comment'}; foreach my $line (@$comment) { chomp($line); - print esc_html($line) . "
\n"; + print esc_html($line, -nbsp=>1) . "
\n"; } print "\n"; git_footer_html(); From 7002243f7ee7eb557a1e49420487e150730ff46f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 24 Nov 2006 14:04:01 -0800 Subject: [PATCH 21/27] gitweb: (style) use chomp without parentheses consistently. It seems that gitweb tries to consistently use chomp without parentheses around its operands, but there were two places that said "chomp($var);". Let's be consistent. Signed-off-by: Junio C Hamano --- gitweb/gitweb.perl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 75e3502fe..6ae7e8035 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -2918,7 +2918,7 @@ sub git_tag { print "
"; my $comment = $tag{'comment'}; foreach my $line (@$comment) { - chomp($line); + chomp $line; print esc_html($line, -nbsp=>1) . "
\n"; } print "
\n"; @@ -2988,7 +2988,7 @@ sub git_blame2 { } } my $data = $_; - chomp($data); + chomp $data; my $rev = substr($full_rev, 0, 8); my $author = $meta->{'author'}; my %date = parse_date($meta->{'author-time'}, From 983d2ee284936e8bf14823863d3945b4d8740b94 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 24 Nov 2006 19:07:24 -0800 Subject: [PATCH 22/27] git-clone: stop dumb protocol from copying refs outside heads/ and tags/. Most notably, the original code first copied refs/remotes/ that remote side had to local, and overwrote them by mapping refs/heads/ from the remote when a dumb protocol transport was used. This makes the clone behaviour by dumb protocol in line with the git native and rsync transports. Signed-off-by: Junio C Hamano --- git-clone.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/git-clone.sh b/git-clone.sh index 9ed413554..d4ee93f75 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -48,6 +48,10 @@ Perhaps git-update-server-info needs to be run there?" case "$name" in *^*) continue;; esac + case "$bare,$name" in + yes,* | ,heads/* | ,tags/*) ;; + *) continue ;; + esac if test -n "$use_separate_remote" && branch_name=`expr "z$name" : 'zheads/\(.*\)'` then From 75e6e2132006770156d9df1881b4114862919c94 Mon Sep 17 00:00:00 2001 From: Lars Hjemli Date: Fri, 24 Nov 2006 14:45:10 +0100 Subject: [PATCH 23/27] Add -v and --abbrev options to git-branch The new -v option makes git-branch show the abbreviated sha1 + subjectline for each branch. Additionally, minimum abbreviation length can be specified with --abbrev= Signed-off-by: Lars Hjemli Signed-off-by: Junio C Hamano --- Documentation/git-branch.txt | 9 ++++++- builtin-branch.c | 48 ++++++++++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 537676081..4f5b5d502 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -8,7 +8,7 @@ git-branch - List, create, or delete branches. SYNOPSIS -------- [verse] -'git-branch' [-r] [-a] +'git-branch' [-r] [-a] [-v] [--abbrev=] 'git-branch' [-l] [-f] [] 'git-branch' (-d | -D) ... @@ -52,6 +52,13 @@ OPTIONS -a:: List both remote-tracking branches and local branches. +-v:: + Show sha1 and subject message for each head. + +--abbrev=:: + Alter minimum display length for sha1 in output listing, + default value is 7. + :: The name of the branch to create or delete. The new branch name must pass all checks defined by diff --git a/builtin-branch.c b/builtin-branch.c index 22e3285a4..69b7b55d8 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -11,7 +11,7 @@ #include "builtin.h" static const char builtin_branch_usage[] = -"git-branch (-d | -D) | [-l] [-f] [] | [-r] | [-a]"; +"git-branch (-d | -D) | [-l] [-f] [] | [-r | -a] [-v] [--abbrev=] "; static const char *head; @@ -87,10 +87,11 @@ static void delete_branches(int argc, const char **argv, int force) struct ref_item { char *name; unsigned int kind; + unsigned char sha1[20]; }; struct ref_list { - int index, alloc; + int index, alloc, maxwidth; struct ref_item *list; int kinds; }; @@ -100,6 +101,7 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags, struct ref_list *ref_list = (struct ref_list*)(cb_data); struct ref_item *newitem; int kind = REF_UNKNOWN_TYPE; + int len; /* Detect kind */ if (!strncmp(refname, "refs/heads/", 11)) { @@ -128,6 +130,10 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags, newitem = &(ref_list->list[ref_list->index++]); newitem->name = xstrdup(refname); newitem->kind = kind; + hashcpy(newitem->sha1, sha1); + len = strlen(newitem->name); + if (len > ref_list->maxwidth) + ref_list->maxwidth = len; return 0; } @@ -151,7 +157,24 @@ static int ref_cmp(const void *r1, const void *r2) return strcmp(c1->name, c2->name); } -static void print_ref_list(int kinds) +static void print_ref_info(const unsigned char *sha1, int abbrev) +{ + struct commit *commit; + char subject[256]; + + + commit = lookup_commit(sha1); + if (commit && !parse_commit(commit)) + pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0, + subject, sizeof(subject), 0, + NULL, NULL, 0); + else + strcpy(subject, " **** invalid ref ****"); + + printf(" %s %s\n", find_unique_abbrev(sha1, abbrev), subject); +} + +static void print_ref_list(int kinds, int verbose, int abbrev) { int i; char c; @@ -169,7 +192,13 @@ static void print_ref_list(int kinds) !strcmp(ref_list.list[i].name, head)) c = '*'; - printf("%c %s\n", c, ref_list.list[i].name); + if (verbose) { + printf("%c %-*s", c, ref_list.maxwidth, + ref_list.list[i].name); + print_ref_info(ref_list.list[i].sha1, abbrev); + } + else + printf("%c %s\n", c, ref_list.list[i].name); } free_ref_list(&ref_list); @@ -215,6 +244,7 @@ static void create_branch(const char *name, const char *start, int cmd_branch(int argc, const char **argv, const char *prefix) { int delete = 0, force_delete = 0, force_create = 0; + int verbose = 0, abbrev = DEFAULT_ABBREV; int reflog = 0; int kinds = REF_LOCAL_BRANCH; int i; @@ -255,6 +285,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix) reflog = 1; continue; } + if (!strncmp(arg, "--abbrev=", 9)) { + abbrev = atoi(arg+9); + continue; + } + if (!strcmp(arg, "-v")) { + verbose = 1; + continue; + } usage(builtin_branch_usage); } @@ -268,7 +306,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (delete) delete_branches(argc - i, argv + i, force_delete); else if (i == argc) - print_ref_list(kinds); + print_ref_list(kinds, verbose, abbrev); else if (i == argc - 1) create_branch(argv[i], head, force_create, reflog); else if (i == argc - 2) From 67affd5173da059ca60aab7896985331acacd9b4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 24 Nov 2006 23:10:23 -0800 Subject: [PATCH 24/27] git-branch -D: make it work even when on a yet-to-be-born branch This makes "git branch -D other_branch" work even when HEAD points at a yet-to-be-born branch. Earlier, we checked the HEAD ref for the purpose of "subset" check even when the deletion was forced (i.e. not -d but -D). Because of this, you cannot delete a branch even with -D while on a yet-to-be-born branch. With this change, the following sequence that now works: mkdir newdir && cd newdir git init-db git fetch -k $other_repo refs/heads/master:refs/heads/othre # oops, typo git branch other othre git branch -D othre Signed-off-by: Junio C Hamano --- builtin-branch.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/builtin-branch.c b/builtin-branch.c index 69b7b55d8..3d5cb0e4b 100644 --- a/builtin-branch.c +++ b/builtin-branch.c @@ -38,12 +38,16 @@ static int in_merge_bases(const unsigned char *sha1, static void delete_branches(int argc, const char **argv, int force) { - struct commit *rev, *head_rev; + struct commit *rev, *head_rev = head_rev; unsigned char sha1[20]; char *name; int i; - head_rev = lookup_commit_reference(head_sha1); + if (!force) { + head_rev = lookup_commit_reference(head_sha1); + if (!head_rev) + die("Couldn't look up commit object for HEAD"); + } for (i = 0; i < argc; i++) { if (!strcmp(head, argv[i])) die("Cannot delete the branch you are currently on."); @@ -53,8 +57,8 @@ static void delete_branches(int argc, const char **argv, int force) die("Branch '%s' not found.", argv[i]); rev = lookup_commit_reference(sha1); - if (!rev || !head_rev) - die("Couldn't look up commit objects."); + if (!rev) + die("Couldn't look up commit object for '%s'", name); /* This checks whether the merge bases of branch and * HEAD contains branch -- which means that the HEAD From 747fa12cef73b6ca04fffaddaad7326cf546cdea Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 24 Nov 2006 22:38:17 -0800 Subject: [PATCH 25/27] git-svn: correctly access repos when only given partial read permissions Sometimes users are given only read access to a subtree inside a repository, and git-svn could not read log information (and thus fetch commits) when connecting a session to the root of the repository. We now start an SVN::Ra session with the full URL of what we're tracking, and not the repository root as before. This change was made much easier with a cleanup of repo_path_split() usage as well as improving the accounting of authentication batons. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano --- git-svn.perl | 137 ++++++++++++++++++++++++--------------------------- 1 file changed, 65 insertions(+), 72 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 47cd3e27f..5d67771c2 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -39,7 +39,7 @@ memoize('cmt_metadata'); memoize('get_commit_time'); -my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib, $AUTH_BATON, $AUTH_CALLBACKS); +my ($SVN, $_use_lib); sub nag_lib { print STDERR < $head) { @@ -426,7 +423,7 @@ sub fetch_lib { # performance sucks with it enabled, so it's much # faster to fetch revision ranges instead of relying # on the limiter. - libsvn_get_log($SVN_LOG, '/'.$SVN_PATH, + libsvn_get_log(libsvn_dup_ra($SVN), [''], $min, $max, 0, 1, 1, sub { my $log_msg; @@ -528,7 +525,6 @@ sub commit_lib { my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$"; my $repo; - ($repo, $SVN_PATH) = repo_path_split($SVN_URL); set_svn_commit_env(); foreach my $c (@revs) { my $log_msg = get_commit_message($c, $commit_msg); @@ -537,13 +533,11 @@ sub commit_lib { # can't track down... (it's probably in the SVN code) defined(my $pid = open my $fh, '-|') or croak $!; if (!$pid) { - $SVN_LOG = libsvn_connect($repo); - $SVN = libsvn_connect($repo); my $ed = SVN::Git::Editor->new( { r => $r_last, - ra => $SVN_LOG, + ra => libsvn_dup_ra($SVN), c => $c, - svn_path => $SVN_PATH + svn_path => $SVN->{svn_path}, }, $SVN->get_commit_editor( $log_msg->{msg}, @@ -661,10 +655,9 @@ sub show_ignore_cmd { sub show_ignore_lib { my $repo; - ($repo, $SVN_PATH) = repo_path_split($SVN_URL); - $SVN ||= libsvn_connect($repo); + $SVN ||= libsvn_connect($SVN_URL); my $r = defined $_revision ? $_revision : $SVN->get_latest_revnum; - libsvn_traverse_ignore(\*STDOUT, $SVN_PATH, $r); + libsvn_traverse_ignore(\*STDOUT, $SVN->{svn_path}, $r); } sub graft_branches { @@ -790,7 +783,7 @@ sub show_log { } elsif (/^:\d{6} \d{6} $sha1_short/o) { push @{$c->{raw}}, $_; } elsif (/^[ACRMDT]\t/) { - # we could add $SVN_PATH here, but that requires + # we could add $SVN->{svn_path} here, but that requires # remote access at the moment (repo_path_split)... s#^([ACRMDT])\t# $1 #; push @{$c->{changed}}, $_; @@ -856,10 +849,7 @@ sub commit_diff { $_message ||= get_commit_message($tb, "$GIT_DIR/.svn-commit.tmp.$$")->{msg}; } - my $repo; - ($repo, $SVN_PATH) = repo_path_split($SVN_URL); - $SVN_LOG ||= libsvn_connect($repo); - $SVN ||= libsvn_connect($repo); + $SVN ||= libsvn_connect($SVN_URL); if ($r eq 'HEAD') { $r = $SVN->get_latest_revnum; } elsif ($r !~ /^\d+$/) { @@ -868,8 +858,9 @@ sub commit_diff { my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : (); my $rev_committed; my $ed = SVN::Git::Editor->new({ r => $r, - ra => $SVN_LOG, c => $tb, - svn_path => $SVN_PATH + ra => libsvn_dup_ra($SVN), + c => $tb, + svn_path => $SVN->{svn_path} }, $SVN->get_commit_editor($_message, sub { @@ -1147,8 +1138,7 @@ sub graft_file_copy_lib { my $tree_paths = $l_map->{$u}; my $pfx = common_prefix([keys %$tree_paths]); my ($repo, $path) = repo_path_split($u.$pfx); - $SVN_LOG ||= libsvn_connect($repo); - $SVN ||= libsvn_connect($repo); + $SVN = libsvn_connect($repo); my ($base, $head) = libsvn_parse_revision(); my $inc = 1000; @@ -1157,7 +1147,8 @@ sub graft_file_copy_lib { $SVN::Error::handler = \&libsvn_skip_unknown_revs; while (1) { my $pool = SVN::Pool->new; - libsvn_get_log($SVN_LOG, "/$path", $min, $max, 0, 1, 1, + libsvn_get_log(libsvn_dup_ra($SVN), [$path], + $min, $max, 0, 1, 1, sub { libsvn_graft_file_copies($grafts, $tree_paths, $path, @_); @@ -1267,13 +1258,9 @@ sub repo_path_split { return ($u, $full_url); } } - if ($_use_lib) { my $tmp = libsvn_connect($full_url); - my $url = $tmp->get_repos_root; - $full_url =~ s#^\Q$url\E/*##; - push @repo_path_split_cache, qr/^(\Q$url\E)/; - return ($url, $full_url); + return ($tmp->{repos_root}, $tmp->{svn_path}); } else { my ($url, $path) = ($full_url =~ m!^([a-z\+]+://[^/]*)(.*)$!i); $path =~ s#^/+##; @@ -2815,34 +2802,41 @@ sub _read_password { sub libsvn_connect { my ($url) = @_; - if (!$AUTH_BATON || !$AUTH_CALLBACKS) { - SVN::_Core::svn_config_ensure($_config_dir, undef); - ($AUTH_BATON, $AUTH_CALLBACKS) = SVN::Core::auth_open_helper([ - SVN::Client::get_simple_provider(), - SVN::Client::get_ssl_server_trust_file_provider(), - SVN::Client::get_simple_prompt_provider( - \&_simple_prompt, 2), - SVN::Client::get_ssl_client_cert_prompt_provider( - \&_ssl_client_cert_prompt, 2), - SVN::Client::get_ssl_client_cert_pw_prompt_provider( - \&_ssl_client_cert_pw_prompt, 2), - SVN::Client::get_username_provider(), - SVN::Client::get_ssl_server_trust_prompt_provider( - \&_ssl_server_trust_prompt), - SVN::Client::get_username_prompt_provider( - \&_username_prompt, 2), - ]); - } - SVN::Ra->new(url => $url, auth => $AUTH_BATON, - auth_provider_callbacks => $AUTH_CALLBACKS); + SVN::_Core::svn_config_ensure($_config_dir, undef); + my ($baton, $callbacks) = SVN::Core::auth_open_helper([ + SVN::Client::get_simple_provider(), + SVN::Client::get_ssl_server_trust_file_provider(), + SVN::Client::get_simple_prompt_provider( + \&_simple_prompt, 2), + SVN::Client::get_ssl_client_cert_prompt_provider( + \&_ssl_client_cert_prompt, 2), + SVN::Client::get_ssl_client_cert_pw_prompt_provider( + \&_ssl_client_cert_pw_prompt, 2), + SVN::Client::get_username_provider(), + SVN::Client::get_ssl_server_trust_prompt_provider( + \&_ssl_server_trust_prompt), + SVN::Client::get_username_prompt_provider( + \&_username_prompt, 2), + ]); + my $ra = SVN::Ra->new(url => $url, auth => $baton, + pool => SVN::Pool->new, + auth_provider_callbacks => $callbacks); + $ra->{svn_path} = $url; + $ra->{repos_root} = $ra->get_repos_root; + $ra->{svn_path} =~ s#^\Q$ra->{repos_root}\E/*##; + push @repo_path_split_cache, qr/^(\Q$ra->{repos_root}\E)/; + return $ra; +} + +sub libsvn_dup_ra { + my ($ra) = @_; + SVN::Ra->new(map { $_ => $ra->{$_} } + qw/url auth auth_provider_callbacks repos_root svn_path/); } sub libsvn_get_file { my ($gui, $f, $rev, $chg) = @_; - my $p = $f; - if (length $SVN_PATH > 0) { - return unless ($p =~ s#^\Q$SVN_PATH\E/##); - } + $f =~ s#^/##; print "\t$chg\t$f\n" unless $_q; my ($hash, $pid, $in, $out); @@ -2879,7 +2873,7 @@ sub libsvn_get_file { waitpid $pid, 0; $hash =~ /^$sha1$/o or die "not a sha1: $hash\n"; } - print $gui $mode,' ',$hash,"\t",$p,"\0" or croak $!; + print $gui $mode,' ',$hash,"\t",$f,"\0" or croak $!; } sub libsvn_log_entry { @@ -2897,7 +2891,6 @@ sub libsvn_log_entry { sub process_rm { my ($gui, $last_commit, $f) = @_; - $f =~ s#^\Q$SVN_PATH\E/?## or return; # remove entire directories. if (safe_qx('git-ls-tree',$last_commit,'--',$f) =~ /^040000 tree/) { defined(my $pid = open my $ls, '-|') or croak $!; @@ -2919,9 +2912,11 @@ sub libsvn_fetch { my ($last_commit, $paths, $rev, $author, $date, $msg) = @_; open my $gui, '| git-update-index -z --index-info' or croak $!; my @amr; + my $p = $SVN->{svn_path}; foreach my $f (keys %$paths) { my $m = $paths->{$f}->action(); - $f =~ s#^/+##; + $f =~ s#^/\Q$p\E/##; + next if $f =~ m#^/#; if ($m =~ /^[DR]$/) { print "\t$m\t$f\n" unless $_q; process_rm($gui, $last_commit, $f); @@ -3011,9 +3006,9 @@ sub libsvn_parse_revision { sub libsvn_traverse { my ($gui, $pfx, $path, $rev, $files) = @_; - my $cwd = "$pfx/$path"; + my $cwd = length $pfx ? "$pfx/$path" : $path; my $pool = SVN::Pool->new; - $cwd =~ s#^/+##g; + $cwd =~ s#^\Q$SVN->{svn_path}\E##; my ($dirent, $r, $props) = $SVN->get_dir($cwd, $rev, $pool); foreach my $d (keys %$dirent) { my $t = $dirent->{$d}->kind; @@ -3037,7 +3032,7 @@ sub libsvn_traverse_ignore { my $pool = SVN::Pool->new; my ($dirent, undef, $props) = $SVN->get_dir($path, $r, $pool); my $p = $path; - $p =~ s#^\Q$SVN_PATH\E/?##; + $p =~ s#^\Q$SVN->{svn_path}\E/##; print $fh length $p ? "\n# $p\n" : "\n# /\n"; if (my $s = $props->{'svn:ignore'}) { $s =~ s/[\r\n]+/\n/g; @@ -3064,7 +3059,7 @@ sub revisions_eq { if ($_use_lib) { # should be OK to use Pool here (r1 - r0) should be small my $pool = SVN::Pool->new; - libsvn_get_log($SVN, "/$path", $r0, $r1, + libsvn_get_log($SVN, [$path], $r0, $r1, 0, 1, 1, sub {$nr++}, $pool); $pool->clear; } else { @@ -3079,7 +3074,7 @@ sub revisions_eq { sub libsvn_find_parent_branch { my ($paths, $rev, $author, $date, $msg) = @_; - my $svn_path = '/'.$SVN_PATH; + my $svn_path = '/'.$SVN->{svn_path}; # look for a parent from another branch: my $i = $paths->{$svn_path} or return; @@ -3090,7 +3085,7 @@ sub libsvn_find_parent_branch { $branch_from =~ s#^/##; my $l_map = {}; read_url_paths_all($l_map, '', "$GIT_DIR/svn"); - my $url = $SVN->{url}; + my $url = $SVN->{repos_root}; defined $l_map->{$url} or return; my $id = $l_map->{$url}->{$branch_from}; if (!defined $id && $_follow_parent) { @@ -3112,7 +3107,7 @@ sub libsvn_find_parent_branch { $GIT_SVN = $ENV{GIT_SVN_ID} = $id; init_vars(); $SVN_URL = "$url/$branch_from"; - $SVN_LOG = $SVN = undef; + $SVN = undef; setup_git_svn(); # we can't assume SVN_URL exists at r+1: $_revision = "0:$r"; @@ -3149,7 +3144,7 @@ sub libsvn_new_tree { } my ($paths, $rev, $author, $date, $msg) = @_; open my $gui, '| git-update-index -z --index-info' or croak $!; - libsvn_traverse($gui, '', $SVN_PATH, $rev); + libsvn_traverse($gui, '', $SVN->{svn_path}, $rev); close $gui or croak $?; return libsvn_log_entry($rev, $author, $date, $msg); } @@ -3234,11 +3229,10 @@ sub libsvn_commit_cb { sub libsvn_ls_fullurl { my $fullurl = shift; - my ($repo, $path) = repo_path_split($fullurl); - $SVN ||= libsvn_connect($repo); + $SVN ||= libsvn_connect($fullurl); my @ret; my $pool = SVN::Pool->new; - my ($dirent, undef, undef) = $SVN->get_dir($path, + my ($dirent, undef, undef) = $SVN->get_dir($SVN->{svn_path}, $SVN->get_latest_revnum, $pool); foreach my $d (keys %$dirent) { if ($dirent->{$d}->kind == $SVN::Node::dir) { @@ -3260,8 +3254,9 @@ sub libsvn_skip_unknown_revs { # Wonderfully consistent library, eh? # 160013 - svn:// and file:// # 175002 - http(s):// + # 175007 - http(s):// (this repo required authorization, too...) # More codes may be discovered later... - if ($errno == 175002 || $errno == 160013) { + if ($errno == 175007 || $errno == 175002 || $errno == 160013) { return; } croak "Error from SVN, ($errno): ", $err->expanded_message,"\n"; @@ -3349,8 +3344,7 @@ sub split_path { } sub repo_path { - (defined $_[1] && length $_[1]) ? "$_[0]->{svn_path}/$_[1]" - : $_[0]->{svn_path} + (defined $_[1] && length $_[1]) ? $_[1] : '' } sub url_path { @@ -3382,10 +3376,9 @@ sub rmdirs { exec qw/git-ls-tree --name-only -r -z/, $self->{c} or croak $!; } local $/ = "\0"; - my @svn_path = split m#/#, $self->{svn_path}; while (<$fh>) { chomp; - my @dn = (@svn_path, (split m#/#, $_)); + my @dn = split m#/#, $_; while (pop @dn) { delete $rm->{join '/', @dn}; } From d25c26e771fdf771f264dc85be348719886d354f Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 24 Nov 2006 22:38:18 -0800 Subject: [PATCH 26/27] git-svn: exit with status 1 for test failures Some versions of the SVN libraries cause die() to exit with 255, and 40cf043389ef4cdf3e56e7c4268d6f302e387fa0 tightened up test_expect_failure to reject return values >128. Signed-off-by: Eric Wong Signed-off-by: Junio C Hamano --- git-svn.perl | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/git-svn.perl b/git-svn.perl index 5d67771c2..0a47b1f9f 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -21,6 +21,7 @@ $ENV{LC_ALL} = 'C'; $| = 1; # unbuffer STDOUT +sub fatal (@) { print STDERR $@; exit 1 } # If SVN:: library support is added, please make the dependencies # optional and preserve the capability to use the command-line client. # use eval { require SVN::... } to make it lazy load @@ -569,7 +570,7 @@ sub commit_lib { $no = 1; } } - close $fh or croak $?; + close $fh or exit 1; if (! defined $r_new && ! defined $cmt_new) { unless ($no) { die "Failed to parse revision information\n"; @@ -868,13 +869,16 @@ sub commit_diff { print "Committed $_[0]\n"; }, @lock) ); - my $mods = libsvn_checkout_tree($ta, $tb, $ed); - if (@$mods == 0) { - print "No changes\n$ta == $tb\n"; - $ed->abort_edit; - } else { - $ed->close_edit; - } + eval { + my $mods = libsvn_checkout_tree($ta, $tb, $ed); + if (@$mods == 0) { + print "No changes\n$ta == $tb\n"; + $ed->abort_edit; + } else { + $ed->close_edit; + } + }; + fatal "$@\n" if $@; $_message = $_file = undef; return $rev_committed; } From f64d7fd267c501f501e18a888e3e1e0c5b56458f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 25 Nov 2006 01:04:28 -0800 Subject: [PATCH 27/27] git-fetch: exit with non-zero status when fast-forward check fails When update_local_ref() refuses to update a branch head due to fast-forward check, it was not propagated properly in the call chain and the command did not exit with non-zero status as a result. Signed-off-by: Junio C Hamano --- git-fetch.sh | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/git-fetch.sh b/git-fetch.sh index eb32476bb..442556209 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -359,7 +359,7 @@ fetch_main () { esac append_fetch_head "$head" "$remote" \ - "$remote_name" "$remote_nick" "$local_name" "$not_for_merge" + "$remote_name" "$remote_nick" "$local_name" "$not_for_merge" || exit done @@ -413,15 +413,16 @@ fetch_main () { done local_name=$(expr "z$found" : 'z[^:]*:\(.*\)') append_fetch_head "$sha1" "$remote" \ - "$remote_name" "$remote_nick" "$local_name" "$not_for_merge" - done + "$remote_name" "$remote_nick" "$local_name" \ + "$not_for_merge" || exit + done && if [ "$pack_lockfile" ]; then rm -f "$pack_lockfile"; fi ) || exit ;; esac } -fetch_main "$reflist" +fetch_main "$reflist" || exit # automated tag following case "$no_tags$tags" in @@ -449,7 +450,7 @@ case "$no_tags$tags" in case "$taglist" in '') ;; ?*) - fetch_main "$taglist" ;; + fetch_main "$taglist" || exit ;; esac esac