diff options
-rw-r--r-- | Documentation/git-merge-file.txt | 4 | ||||
-rw-r--r-- | Documentation/git-svnimport.txt | 13 | ||||
-rw-r--r-- | builtin-show-ref.c | 77 | ||||
-rw-r--r-- | cache.h | 1 | ||||
-rwxr-xr-x | git-fetch.sh | 12 | ||||
-rwxr-xr-x | gitweb/gitweb.perl | 216 | ||||
-rw-r--r-- | ident.c | 15 | ||||
-rw-r--r-- | read-cache.c | 6 | ||||
-rw-r--r-- | receive-pack.c | 2 | ||||
-rwxr-xr-x | t/t0000-basic.sh | 9 |
10 files changed, 305 insertions, 50 deletions
diff --git a/Documentation/git-merge-file.txt b/Documentation/git-merge-file.txt index 0b41d66a70..29d3faa556 100644 --- a/Documentation/git-merge-file.txt +++ b/Documentation/git-merge-file.txt @@ -1,9 +1,9 @@ git-merge-file(1) -============ +================= NAME ---- -git-merge-file - threeway file merge +git-merge-file - three-way file merge SYNOPSIS diff --git a/Documentation/git-svnimport.txt b/Documentation/git-svnimport.txt index b1b87c2fcd..2c7c7dad54 100644 --- a/Documentation/git-svnimport.txt +++ b/Documentation/git-svnimport.txt @@ -15,6 +15,7 @@ SYNOPSIS [ -b branch_subdir ] [ -T trunk_subdir ] [ -t tag_subdir ] [ -s start_chg ] [ -m ] [ -r ] [ -M regex ] [ -I <ignorefile_name> ] [ -A <author_file> ] + [ -P <path_from_trunk> ] <SVN_repository_URL> [ <path> ] @@ -103,9 +104,17 @@ repository without -A. -l <max_rev>:: Specify a maximum revision number to pull. ++ +Formerly, this option controlled how many revisions to pull, +due to SVN memory leaks. (These have been worked around.) - Formerly, this option controlled how many revisions to pull, - due to SVN memory leaks. (These have been worked around.) +-P <path_from_trunk>:: + Partial import of the SVN tree. ++ +By default, the whole tree on the SVN trunk (/trunk) is imported. +'-P my/proj' will import starting only from '/trunk/my/proj'. +This option is useful when you want to import one project from a +svn repo which hosts multiple projects under the same trunk. -v:: Verbosity: let 'svnimport' report what it is doing. diff --git a/builtin-show-ref.c b/builtin-show-ref.c index 073979855b..23e0ff8fbf 100644 --- a/builtin-show-ref.c +++ b/builtin-show-ref.c @@ -2,8 +2,9 @@ #include "refs.h" #include "object.h" #include "tag.h" +#include "path-list.h" -static const char show_ref_usage[] = "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] [-s|--hash[=<length>]] [--abbrev[=<length>]] [--tags] [--heads] [--] [pattern*]"; +static const char show_ref_usage[] = "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] [-s|--hash[=<length>]] [--abbrev[=<length>]] [--tags] [--heads] [--] [pattern*] | --filter-invalid < ref-list"; static int deref_tags = 0, show_head = 0, tags_only = 0, heads_only = 0, found_match = 0, verify = 0, quiet = 0, hash_only = 0, abbrev = 0; @@ -86,6 +87,59 @@ match: return 0; } +static int add_existing(const char *refname, const unsigned char *sha1, int flag, void *cbdata) +{ + struct path_list *list = (struct path_list *)cbdata; + path_list_insert(refname, list); + return 0; +} + +/* + * read "^(?:<anything>\s)?<refname>(?:\^\{\})?$" from the standard input, + * and + * (1) strip "^{}" at the end of line if any; + * (2) ignore if match is provided and does not head-match refname; + * (3) warn if refname is not a well-formed refname and skip; + * (4) ignore if refname is a ref that exists in the local repository; + * (5) otherwise output the line. + */ +static int exclude_existing(const char *match) +{ + static struct path_list existing_refs = { NULL, 0, 0, 0 }; + char buf[1024]; + int matchlen = match ? strlen(match) : 0; + + for_each_ref(add_existing, &existing_refs); + while (fgets(buf, sizeof(buf), stdin)) { + int len = strlen(buf); + char *ref; + if (len > 0 && buf[len - 1] == '\n') + buf[--len] = '\0'; + if (!strcmp(buf + len - 3, "^{}")) { + len -= 3; + buf[len] = '\0'; + } + for (ref = buf + len; buf < ref; ref--) + if (isspace(ref[-1])) + break; + if (match) { + int reflen = buf + len - ref; + if (reflen < matchlen) + continue; + if (strncmp(ref, match, matchlen)) + continue; + } + if (check_ref_format(ref)) { + fprintf(stderr, "warning: ref '%s' ignored\n", ref); + continue; + } + if (!path_list_has_path(&existing_refs, ref)) { + printf("%s\n", buf); + } + } + return 0; +} + int cmd_show_ref(int argc, const char **argv, const char *prefix) { int i; @@ -153,8 +207,29 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix) heads_only = 1; continue; } + if (!strcmp(arg, "--exclude-existing")) + return exclude_existing(NULL); + if (!strncmp(arg, "--exclude-existing=", 19)) + return exclude_existing(arg + 19); usage(show_ref_usage); } + + if (verify) { + unsigned char sha1[20]; + + while (*pattern) { + if (resolve_ref(*pattern, sha1, 1, NULL)) + printf("%s %s\n", sha1_to_hex(sha1), + *pattern); + else if (!quiet) + die("'%s' - not a valid ref", *pattern); + else + return 1; + pattern++; + } + return 0; + } + if (show_head) head_ref(show_ref, NULL); for_each_ref(show_ref, NULL); @@ -309,6 +309,7 @@ void datestamp(char *buf, int bufsize); unsigned long approxidate(const char *); extern int setup_ident(void); +extern void ignore_missing_committer_name(); extern const char *git_author_info(int); extern const char *git_committer_info(int); diff --git a/git-fetch.sh b/git-fetch.sh index fb35815a5f..38101a6ace 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -242,7 +242,7 @@ esac reflist=$(get_remote_refs_for_fetch "$@") if test "$tags" then - taglist=`IFS=" " && + taglist=`IFS=' ' && echo "$ls_remote_result" | while read sha1 name do @@ -438,17 +438,11 @@ case "$no_tags$tags" in *:refs/*) # effective only when we are following remote branch # using local tracking branch. - taglist=$(IFS=" " && + taglist=$(IFS=' ' && echo "$ls_remote_result" | - sed -n -e 's|^\('"$_x40"'\) \(refs/tags/.*\)^{}$|\1 \2|p' \ - -e 's|^\('"$_x40"'\) \(refs/tags/.*\)$|\1 \2|p' | + git-show-ref --exclude-existing=refs/tags/ | while read sha1 name do - git-show-ref --verify --quiet -- "$name" && continue - git-check-ref-format "$name" || { - echo >&2 "warning: tag ${name} ignored" - continue - } git-cat-file -t "$sha1" >/dev/null 2>&1 || continue echo >&2 "Auto-following $name" echo ".${name}:${name}" diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 5ea3fda540..4059894e0b 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -434,6 +434,7 @@ my %actions = ( "tags" => \&git_tags, "tree" => \&git_tree, "snapshot" => \&git_snapshot, + "object" => \&git_object, # those below don't need $project "opml" => \&git_opml, "project_list" => \&git_project_list, @@ -827,14 +828,12 @@ sub format_log_line_html { my $line = shift; $line = esc_html($line, -nbsp=>1); - if ($line =~ m/([0-9a-fA-F]{40})/) { + if ($line =~ m/([0-9a-fA-F]{8,40})/) { my $hash_text = $1; - if (git_get_type($hash_text) eq "commit") { - my $link = - $cgi->a({-href => href(action=>"commit", hash=>$hash_text), - -class => "text"}, $hash_text); - $line =~ s/$hash_text/$link/; - } + my $link = + $cgi->a({-href => href(action=>"object", hash=>$hash_text), + -class => "text"}, $hash_text); + $line =~ s/$hash_text/$link/; } return $line; } @@ -856,7 +855,8 @@ sub format_ref_marker { $name = $ref; } - $markers .= " <span class=\"$type\">" . esc_html($name) . "</span>"; + $markers .= " <span class=\"$type\" title=\"$ref\">" . + esc_html($name) . "</span>"; } } @@ -1989,12 +1989,73 @@ sub git_print_log ($;%) { } } +# return link target (what link points to) +sub git_get_link_target { + my $hash = shift; + my $link_target; + + # read link + open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash + or return; + { + local $/; + $link_target = <$fd>; + } + close $fd + or return; + + return $link_target; +} + +# given link target, and the directory (basedir) the link is in, +# return target of link relative to top directory (top tree); +# return undef if it is not possible (including absolute links). +sub normalize_link_target { + my ($link_target, $basedir, $hash_base) = @_; + + # we can normalize symlink target only if $hash_base is provided + return unless $hash_base; + + # absolute symlinks (beginning with '/') cannot be normalized + return if (substr($link_target, 0, 1) eq '/'); + + # normalize link target to path from top (root) tree (dir) + my $path; + if ($basedir) { + $path = $basedir . '/' . $link_target; + } else { + # we are in top (root) tree (dir) + $path = $link_target; + } + + # remove //, /./, and /../ + my @path_parts; + foreach my $part (split('/', $path)) { + # discard '.' and '' + next if (!$part || $part eq '.'); + # handle '..' + if ($part eq '..') { + if (@path_parts) { + pop @path_parts; + } else { + # link leads outside repository (outside top dir) + return; + } + } else { + push @path_parts, $part; + } + } + $path = join('/', @path_parts); + + return $path; +} + # print tree entry (row of git_tree), but without encompassing <tr> element sub git_print_tree_entry { my ($t, $basedir, $hash_base, $have_blame) = @_; my %base_key = (); - $base_key{hash_base} = $hash_base if defined $hash_base; + $base_key{'hash_base'} = $hash_base if defined $hash_base; # The format of a table row is: mode list link. Where mode is # the mode of the entry, list is the name of the entry, an href, @@ -2005,16 +2066,31 @@ sub git_print_tree_entry { print "<td class=\"list\">" . $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}", %base_key), - -class => "list"}, esc_path($t->{'name'})) . "</td>\n"; + -class => "list"}, esc_path($t->{'name'})); + if (S_ISLNK(oct $t->{'mode'})) { + my $link_target = git_get_link_target($t->{'hash'}); + if ($link_target) { + my $norm_target = normalize_link_target($link_target, $basedir, $hash_base); + if (defined $norm_target) { + print " -> " . + $cgi->a({-href => href(action=>"object", hash_base=>$hash_base, + file_name=>$norm_target), + -title => $norm_target}, esc_path($link_target)); + } else { + print " -> " . esc_path($link_target); + } + } + } + print "</td>\n"; print "<td class=\"link\">"; print $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'}, - file_name=>"$basedir$t->{'name'}", %base_key)}, - "blob"); + file_name=>"$basedir$t->{'name'}", %base_key)}, + "blob"); if ($have_blame) { print " | " . $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'}, - file_name=>"$basedir$t->{'name'}", %base_key)}, - "blame"); + file_name=>"$basedir$t->{'name'}", %base_key)}, + "blame"); } if (defined $hash_base) { print " | " . @@ -2036,8 +2112,8 @@ sub git_print_tree_entry { print "</td>\n"; print "<td class=\"link\">"; print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'}, - file_name=>"$basedir$t->{'name'}", %base_key)}, - "tree"); + file_name=>"$basedir$t->{'name'}", %base_key)}, + "tree"); if (defined $hash_base) { print " | " . $cgi->a({-href => href(action=>"history", hash_base=>$hash_base, @@ -3414,8 +3490,7 @@ sub git_snapshot { my $filename = basename($project) . "-$hash.tar.$suffix"; print $cgi->header( - -type => 'application/x-tar', - -content_encoding => $ctype, + -type => "application/$ctype", -content_disposition => 'inline; filename="' . "$filename" . '"', -status => '200 OK'); @@ -3497,15 +3572,46 @@ sub git_commit { my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'}); my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'}); - my $parent = $co{'parent'}; + my $parent = $co{'parent'}; + my $parents = $co{'parents'}; # listref + + # we need to prepare $formats_nav before any parameter munging + my $formats_nav; + if (!defined $parent) { + # --root commitdiff + $formats_nav .= '(initial)'; + } elsif (@$parents == 1) { + # single parent commit + $formats_nav .= + '(parent: ' . + $cgi->a({-href => href(action=>"commit", + hash=>$parent)}, + esc_html(substr($parent, 0, 7))) . + ')'; + } else { + # merge commit + $formats_nav .= + '(merge: ' . + join(' ', map { + $cgi->a({-href => href(action=>"commitdiff", + hash=>$_)}, + esc_html(substr($_, 0, 7))); + } @$parents ) . + ')'; + } + if (!defined $parent) { $parent = "--root"; } - open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id", - @diff_opts, $parent, $hash, "--" - or die_error(undef, "Open git-diff-tree failed"); - my @difftree = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading git-diff-tree failed"); + my @difftree; + if (@$parents <= 1) { + # difftree output is not printed for merges + open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id", + @diff_opts, $parent, $hash, "--" + or die_error(undef, "Open git-diff-tree failed"); + @difftree = map { chomp; $_ } <$fd>; + close $fd or die_error(undef, "Reading git-diff-tree failed"); + } # non-textual hash id's can be cached my $expires; @@ -3517,16 +3623,10 @@ sub git_commit { my $have_snapshot = gitweb_have_snapshot(); - my @views_nav = (); - if (defined $file_name && defined $co{'parent'}) { - push @views_nav, - $cgi->a({-href => href(action=>"blame", hash_parent=>$parent, file_name=>$file_name)}, - "blame"); - } git_header_html(undef, $expires); git_print_page_nav('commit', '', $hash, $co{'tree'}, $hash, - join (' | ', @views_nav)); + $formats_nav); if (defined $co{'parent'}) { git_print_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash); @@ -3567,7 +3667,7 @@ sub git_commit { } print "</td>" . "</tr>\n"; - my $parents = $co{'parents'}; + foreach my $par (@$parents) { print "<tr>" . "<td>parent</td>" . @@ -3589,11 +3689,61 @@ sub git_commit { git_print_log($co{'comment'}); print "</div>\n"; - git_difftree_body(\@difftree, $hash, $parent); + if (@$parents <= 1) { + # do not output difftree/whatchanged for merges + git_difftree_body(\@difftree, $hash, $parent); + } git_footer_html(); } +sub git_object { + # object is defined by: + # - hash or hash_base alone + # - hash_base and file_name + my $type; + + # - hash or hash_base alone + if ($hash || ($hash_base && !defined $file_name)) { + my $object_id = $hash || $hash_base; + + my $git_command = git_cmd_str(); + open my $fd, "-|", "$git_command cat-file -t $object_id 2>/dev/null" + or die_error('404 Not Found', "Object does not exist"); + $type = <$fd>; + chomp $type; + close $fd + or die_error('404 Not Found', "Object does not exist"); + + # - hash_base and file_name + } elsif ($hash_base && defined $file_name) { + $file_name =~ s,/+$,,; + + system(git_cmd(), "cat-file", '-e', $hash_base) == 0 + or die_error('404 Not Found', "Base object does not exist"); + + # here errors should not hapen + open my $fd, "-|", git_cmd(), "ls-tree", $hash_base, "--", $file_name + or die_error(undef, "Open git-ls-tree failed"); + my $line = <$fd>; + close $fd; + + #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' + unless ($line && $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/) { + die_error('404 Not Found', "File or directory for given base does not exist"); + } + $type = $2; + $hash = $3; + } else { + die_error('404 Not Found', "Not enough information to find object"); + } + + print $cgi->redirect(-uri => href(action=>$type, -full=>1, + hash=>$hash, hash_base=>$hash_base, + file_name=>$file_name), + -status => '302 Found'); +} + sub git_blobdiff { my $format = shift || 'html'; @@ -221,3 +221,18 @@ const char *git_committer_info(int error_on_no_name) getenv("GIT_COMMITTER_DATE"), error_on_no_name); } + +void ignore_missing_committer_name() +{ + /* If we did not get a name from the user's gecos entry then + * git_default_name is empty; so instead load the username + * into it as a 'good enough for now' approximation of who + * this user is. + */ + if (!*git_default_name) { + struct passwd *pw = getpwuid(getuid()); + if (!pw) + die("You don't exist. Go away!"); + strlcpy(git_default_name, pw->pw_name, sizeof(git_default_name)); + } +} diff --git a/read-cache.c b/read-cache.c index eae4745d28..b8d83ccd9f 100644 --- a/read-cache.c +++ b/read-cache.c @@ -358,7 +358,7 @@ int add_file_to_index(const char *path, int verbose) if (index_path(ce->sha1, path, &st, 1)) die("unable to index file %s", path); - if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD)) + if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE)) die("unable to add %s to index",path); if (verbose) printf("add '%s'\n", path); @@ -517,7 +517,7 @@ static int has_dir_name(const struct cache_entry *ce, int pos, int ok_to_replace pos = cache_name_pos(name, ntohs(create_ce_flags(len, stage))); if (pos >= 0) { retval = -1; - if (ok_to_replace) + if (!ok_to_replace) break; remove_cache_entry_at(pos); continue; @@ -609,7 +609,7 @@ int add_cache_entry(struct cache_entry *ce, int option) if (!skip_df_check && check_file_directory_conflict(ce, pos, ok_to_replace)) { if (!ok_to_replace) - return -1; + return error("'%s' appears as both a file and as a directory", ce->name); pos = cache_name_pos(ce->name, ntohs(ce->ce_flags)); pos = -pos-1; } diff --git a/receive-pack.c b/receive-pack.c index e76d9aea31..5e5510bc3d 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -420,6 +420,8 @@ int main(int argc, char **argv) die("'%s': unable to chdir or not a git archive", dir); setup_ident(); + /* don't die if gecos is empty */ + ignore_missing_committer_name(); git_config(receive_pack_config); write_head_info(); diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index 3260d1d7a7..0cd1c41866 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -272,4 +272,13 @@ test_expect_success \ wc -l) && test $numparent = 1' +test_expect_success 'update-index D/F conflict' ' + mv path0 tmp && + mv path2 path0 && + mv tmp path2 && + git update-index --add --replace path2 path0/file2 && + numpath0=$(git ls-files path0 | wc -l) && + test $numpath0 = 1 +' + test_done |