Merge branch 'maint'
authorJunio C Hamano <gitster@pobox.com>
Fri, 19 Sep 2008 03:30:12 +0000 (20:30 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 19 Sep 2008 03:30:12 +0000 (20:30 -0700)
* maint:
sha1_file: link() returns -1 on failure, not errno
Make git archive respect core.autocrlf when creating zip format archives
Add new test to demonstrate git archive core.autocrlf inconsistency
gitweb: avoid warnings for commits without body
Clarified gitattributes documentation regarding custom hunk header.
git-svn: fix handling of even funkier branch names
git-svn: Always create a new RA when calling do_switch for svn://
git-svn: factor out svnserve test code for later use
diff/diff-files: do not use --cc too aggressively

1  2 
Documentation/gitattributes.txt
builtin-diff.c
git-svn.perl
gitweb/gitweb.perl
sha1_file.c
t/lib-git-svn.sh
t/t9113-git-svn-dcommit-new-file.sh
index 6f3551dc825a04628e7bcd133421093d89f01956,89627688b81761771f17865a67d10ad85a830ea2..e848c94397dedc93652e8027df1ad081f08d2d03
@@@ -270,27 -270,27 +270,27 @@@ See linkgit:git[1] for details
  Defining a custom hunk-header
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  
- Each group of changes (called "hunk") in the textual diff output
+ Each group of changes (called "hunk") in the textual diff output
  is prefixed with a line of the form:
  
        @@ -k,l +n,m @@ TEXT
  
- The text is called 'hunk header', and by default a line that
- begins with an alphabet, an underscore or a dollar sign is used,
- which matches what GNU 'diff -p' output uses.  This default
selection however is not suited for some contents, and you can
use customized pattern to make a selection.
+ This is called a 'hunk header'.  The "TEXT" portion is by default a line
+ that begins with an alphabet, an underscore or a dollar sign; this
+ matches what GNU 'diff -p' output uses.  This default selection however
is not suited for some contents, and you can use a customized pattern
+ to make a selection.
  
- First in .gitattributes, you would assign the `diff` attribute
+ First, in .gitattributes, you would assign the `diff` attribute
  for paths.
  
  ------------------------
  *.tex diff=tex
  ------------------------
  
- Then, you would define "diff.tex.funcname" configuration to
+ Then, you would define "diff.tex.funcname" configuration to
  specify a regular expression that matches a line that you would
- want to appear as the hunk header, like this:
+ want to appear as the hunk header "TEXT", like this:
  
  ------------------------
  [diff "tex"]
@@@ -311,16 -311,10 +311,16 @@@ patterns are available
  
  - `bibtex` suitable for files with BibTeX coded references.
  
 +- `html` suitable for HTML/XHTML documents.
 +
  - `java` suitable for source code in the Java language.
  
  - `pascal` suitable for source code in the Pascal/Delphi language.
  
 +- `php` suitable for source code in the PHP language.
 +
 +- `python` suitable for source code in the Python language.
 +
  - `ruby` suitable for source code in the Ruby language.
  
  - `tex` suitable for source code for LaTeX documents.
diff --combined builtin-diff.c
index 52470c7f41b914932d404a5813e8ae156346beca,d5fe775fc135c0905cabec6731104009ca3bfef3..35da366f46009eab8eab693f7f56403d91d5d677
@@@ -74,8 -74,6 +74,8 @@@ static int builtin_diff_b_f(struct rev_
        if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)))
                die("'%s': not a regular file or symlink", path);
  
 +      diff_set_mnemonic_prefix(&revs->diffopt, "o/", "w/");
 +
        if (blob[0].mode == S_IFINVALID)
                blob[0].mode = canon_mode(st.st_mode);
  
@@@ -225,7 -223,13 +225,13 @@@ static int builtin_diff_files(struct re
                argv++; argc--;
        }
  
-       if (revs->max_count == -1 &&
+       /*
+        * "diff --base" should not combine merges because it was not
+        * asked to.  "diff -c" should not densify (if the user wants
+        * dense one, --cc can be explicitly asked for, or just rely
+        * on the default).
+        */
+       if (revs->max_count == -1 && !revs->combine_merges &&
            (revs->diffopt.output_format & DIFF_FORMAT_PATCH))
                revs->combine_merges = revs->dense_combined_merges = 1;
  
diff --combined git-svn.perl
index 88066c9a7561c45e59d8fc425382d5d86fc3281f,7c7fc39483e2713674a8cf3526651e34bdb9e9f7..af8279acafd8a0e1701f3547da3011c585051e6d
@@@ -421,15 -421,15 +421,15 @@@ sub cmd_dcommit 
        $head ||= 'HEAD';
        my @refs;
        my ($url, $rev, $uuid, $gs) = working_head_info($head, \@refs);
 +      unless ($gs) {
 +              die "Unable to determine upstream SVN information from ",
 +                  "$head history.\nPerhaps the repository is empty.";
 +      }
        $url = defined $_commit_url ? $_commit_url : $gs->full_url;
        my $last_rev = $_revision if defined $_revision;
        if ($url) {
                print "Committing to $url ...\n";
        }
 -      unless ($gs) {
 -              die "Unable to determine upstream SVN information from ",
 -                  "$head history.\nPerhaps the repository is empty.";
 -      }
        my ($linear_refs, $parents) = linearize_history($gs, \@refs);
        if ($_no_rebase && scalar(@$linear_refs) > 1) {
                warn "Attempting to commit more than one change while ",
@@@ -803,28 -803,8 +803,28 @@@ sub cmd_commit_diff 
        }
  }
  
 +sub escape_uri_only {
 +      my ($uri) = @_;
 +      my @tmp;
 +      foreach (split m{/}, $uri) {
 +              s/([^\w.%+-]|%(?![a-fA-F0-9]{2}))/sprintf("%%%02X",ord($1))/eg;
 +              push @tmp, $_;
 +      }
 +      join('/', @tmp);
 +}
 +
 +sub escape_url {
 +      my ($url) = @_;
 +      if ($url =~ m#^([^:]+)://([^/]*)(.*)$#) {
 +              my ($scheme, $domain, $uri) = ($1, $2, escape_uri_only($3));
 +              $url = "$scheme://$domain$uri";
 +      }
 +      $url;
 +}
 +
  sub cmd_info {
        my $path = canonicalize_path(defined($_[0]) ? $_[0] : ".");
 +      my $fullpath = canonicalize_path($cmd_dir_prefix . $path);
        if (exists $_[1]) {
                die "Too many arguments specified\n";
        }
        my ($file_type, $diff_status) = find_file_type_and_diff_status($path);
  
        if (!$file_type && !$diff_status) {
 -              print STDERR "$path:  (Not a versioned resource)\n\n";
 -              return;
 +              print STDERR "svn: '$path' is not under version control\n";
 +              exit 1;
        }
  
        my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
        # canonicalize_path() will return "" to make libsvn 1.5.x happy,
        $path = "." if $path eq "";
  
 -      my $full_url = $url . ($path eq "." ? "" : "/$path");
 +      my $full_url = $url . ($fullpath eq "" ? "" : "/$fullpath");
  
        if ($_url) {
 -              print $full_url, "\n";
 +              print escape_url($full_url), "\n";
                return;
        }
  
        my $result = "Path: $path\n";
        $result .= "Name: " . basename($path) . "\n" if $file_type ne "dir";
 -      $result .= "URL: " . $full_url . "\n";
 +      $result .= "URL: " . escape_url($full_url) . "\n";
  
        eval {
                my $repos_root = $gs->repos_root;
                Git::SVN::remove_username($repos_root);
 -              $result .= "Repository Root: $repos_root\n";
 +              $result .= "Repository Root: " . escape_url($repos_root) . "\n";
        };
        if ($@) {
                $result .= "Repository Root: (offline)\n";
        }
  
        my ($lc_author, $lc_rev, $lc_date_utc);
 -      my @args = Git::SVN::Log::git_svn_log_cmd($rev, $rev, "--", $path);
 +      my @args = Git::SVN::Log::git_svn_log_cmd($rev, $rev, "--", $fullpath);
        my $log = command_output_pipe(@args);
        my $esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/;
        while (<$log>) {
@@@ -3400,12 -3380,11 +3400,12 @@@ sub generate_diff 
        while (<$diff_fh>) {
                chomp $_; # this gets rid of the trailing "\0"
                if ($state eq 'meta' && /^:(\d{6})\s(\d{6})\s
 -                                      $::sha1\s($::sha1)\s
 +                                      ($::sha1)\s($::sha1)\s
                                        ([MTCRAD])\d*$/xo) {
                        push @mods, {   mode_a => $1, mode_b => $2,
 -                                      sha1_b => $3, chg => $4 };
 -                      if ($4 =~ /^(?:C|R)$/) {
 +                                      sha1_a => $3, sha1_b => $4,
 +                                      chg => $5 };
 +                      if ($5 =~ /^(?:C|R)$/) {
                                $state = 'file_a';
                        } else {
                                $state = 'file_b';
@@@ -3657,7 -3636,6 +3657,7 @@@ sub R 
        my $fbat = $self->add_file($self->repo_path($m->{file_b}), $pbat,
                                $self->url_path($m->{file_a}), $self->{r});
        print "\tR\t$m->{file_a} => $m->{file_b}\n" unless $::_q;
 +      $self->apply_autoprops($file, $fbat);
        $self->chg_file($fbat, $m);
        $self->close_file($fbat,undef,$self->{pool});
  
@@@ -3684,52 -3662,33 +3684,52 @@@ sub change_file_prop 
        $self->SUPER::change_file_prop($fbat, $pname, $pval, $self->{pool});
  }
  
 -sub chg_file {
 -      my ($self, $fbat, $m) = @_;
 -      if ($m->{mode_b} =~ /755$/ && $m->{mode_a} !~ /755$/) {
 -              $self->change_file_prop($fbat,'svn:executable','*');
 -      } elsif ($m->{mode_b} !~ /755$/ && $m->{mode_a} =~ /755$/) {
 -              $self->change_file_prop($fbat,'svn:executable',undef);
 -      }
 -      my $fh = Git::temp_acquire('git_blob');
 -      if ($m->{mode_b} =~ /^120/) {
 +sub _chg_file_get_blob ($$$$) {
 +      my ($self, $fbat, $m, $which) = @_;
 +      my $fh = Git::temp_acquire("git_blob_$which");
 +      if ($m->{"mode_$which"} =~ /^120/) {
                print $fh 'link ' or croak $!;
                $self->change_file_prop($fbat,'svn:special','*');
 -      } elsif ($m->{mode_a} =~ /^120/ && $m->{mode_b} !~ /^120/) {
 +      } elsif ($m->{mode_a} =~ /^120/ && $m->{"mode_$which"} !~ /^120/) {
                $self->change_file_prop($fbat,'svn:special',undef);
        }
 -      my $size = $::_repository->cat_blob($m->{sha1_b}, $fh);
 -      croak "Failed to read object $m->{sha1_b}" if ($size < 0);
 +      my $blob = $m->{"sha1_$which"};
 +      return ($fh,) if ($blob =~ /^0{40}$/);
 +      my $size = $::_repository->cat_blob($blob, $fh);
 +      croak "Failed to read object $blob" if ($size < 0);
        $fh->flush == 0 or croak $!;
        seek $fh, 0, 0 or croak $!;
  
        my $exp = ::md5sum($fh);
        seek $fh, 0, 0 or croak $!;
 +      return ($fh, $exp);
 +}
  
 +sub chg_file {
 +      my ($self, $fbat, $m) = @_;
 +      if ($m->{mode_b} =~ /755$/ && $m->{mode_a} !~ /755$/) {
 +              $self->change_file_prop($fbat,'svn:executable','*');
 +      } elsif ($m->{mode_b} !~ /755$/ && $m->{mode_a} =~ /755$/) {
 +              $self->change_file_prop($fbat,'svn:executable',undef);
 +      }
 +      my ($fh_a, $exp_a) = _chg_file_get_blob $self, $fbat, $m, 'a';
 +      my ($fh_b, $exp_b) = _chg_file_get_blob $self, $fbat, $m, 'b';
        my $pool = SVN::Pool->new;
 -      my $atd = $self->apply_textdelta($fbat, undef, $pool);
 -      my $got = SVN::TxDelta::send_stream($fh, @$atd, $pool);
 -      die "Checksum mismatch\nexpected: $exp\ngot: $got\n" if ($got ne $exp);
 -      Git::temp_release($fh, 1);
 +      my $atd = $self->apply_textdelta($fbat, $exp_a, $pool);
 +      if (-s $fh_a) {
 +              my $txstream = SVN::TxDelta::new ($fh_a, $fh_b, $pool);
 +              my $res = SVN::TxDelta::send_txstream($txstream, @$atd, $pool);
 +              if (defined $res) {
 +                      die "Unexpected result from send_txstream: $res\n",
 +                          "(SVN::Core::VERSION: $SVN::Core::VERSION)\n";
 +              }
 +      } else {
 +              my $got = SVN::TxDelta::send_stream($fh_b, @$atd, $pool);
 +              die "Checksum mismatch\nexpected: $exp_b\ngot: $got\n"
 +                  if ($got ne $exp_b);
 +      }
 +      Git::temp_release($fh_b, 1);
 +      Git::temp_release($fh_a, 1);
        $pool->clear;
  }
  
@@@ -4010,20 -3969,19 +4010,19 @@@ sub gs_do_switch 
        my $old_url = $full_url;
        $full_url .= '/' . escape_uri_only($path) if length $path;
        my ($ra, $reparented);
-       if ($old_url ne $full_url) {
-               if ($old_url !~ m#^svn(\+ssh)?://#) {
-                       SVN::_Ra::svn_ra_reparent($self->{session}, $full_url,
-                                                 $pool);
-                       $self->{url} = $full_url;
-                       $reparented = 1;
-               } else {
-                       $_[0] = undef;
-                       $self = undef;
-                       $RA = undef;
-                       $ra = Git::SVN::Ra->new($full_url);
-                       $ra_invalid = 1;
-               }
+       if ($old_url =~ m#^svn(\+ssh)?://#) {
+               $_[0] = undef;
+               $self = undef;
+               $RA = undef;
+               $ra = Git::SVN::Ra->new($full_url);
+               $ra_invalid = 1;
+       } elsif ($old_url ne $full_url) {
+               SVN::_Ra::svn_ra_reparent($self->{session}, $full_url, $pool);
+               $self->{url} = $full_url;
+               $reparented = 1;
        }
        $ra ||= $self;
        $url_b = escape_url($url_b);
        my $reporter = $ra->do_switch($rev_b, '', 1, $url_b, $editor, $pool);
diff --combined gitweb/gitweb.perl
index 29e21564c8e7b2dd711af2e96e03b811b2c1a268,269f1125d9442cb4e0eaecb9069af1d32b4d947b..da474d082c4422ea2def6a7090ac79ed39eb9d47
@@@ -1090,23 -1090,13 +1090,23 @@@ sub format_log_line_html 
  }
  
  # format marker of refs pointing to given object
 +
 +# the destination action is chosen based on object type and current context:
 +# - for annotated tags, we choose the tag view unless it's the current view
 +#   already, in which case we go to shortlog view
 +# - for other refs, we keep the current view if we're in history, shortlog or
 +#   log view, and select shortlog otherwise
  sub format_ref_marker {
        my ($refs, $id) = @_;
        my $markers = '';
  
        if (defined $refs->{$id}) {
                foreach my $ref (@{$refs->{$id}}) {
 +                      # this code exploits the fact that non-lightweight tags are the
 +                      # only indirect objects, and that they are the only objects for which
 +                      # we want to use tag instead of shortlog as action
                        my ($type, $name) = qw();
 +                      my $indirect = ($ref =~ s/\^\{\}$//);
                        # e.g. tags/v2.6.11 or heads/next
                        if ($ref =~ m!^(.*?)s?/(.*)$!) {
                                $type = $1;
                                $name = $ref;
                        }
  
 -                      $markers .= " <span class=\"$type\" title=\"$ref\">" .
 -                                  esc_html($name) . "</span>";
 +                      my $class = $type;
 +                      $class .= " indirect" if $indirect;
 +
 +                      my $dest_action = "shortlog";
 +
 +                      if ($indirect) {
 +                              $dest_action = "tag" unless $action eq "tag";
 +                      } elsif ($action =~ /^(history|(short)?log)$/) {
 +                              $dest_action = $action;
 +                      }
 +
 +                      my $dest = "";
 +                      $dest .= "refs/" unless $ref =~ m!^refs/!;
 +                      $dest .= $ref;
 +
 +                      my $link = $cgi->a({
 +                              -href => href(
 +                                      action=>$dest_action,
 +                                      hash=>$dest
 +                              )}, $name);
 +
 +                      $markers .= " <span class=\"$class\" title=\"$ref\">" .
 +                              $link . "</span>";
                }
        }
  
@@@ -1949,7 -1918,7 +1949,7 @@@ sub git_get_references 
  
        while (my $line = <$fd>) {
                chomp $line;
 -              if ($line =~ m!^([0-9a-fA-F]{40})\srefs/($type/?[^^]+)!) {
 +              if ($line =~ m!^([0-9a-fA-F]{40})\srefs/($type.*)$!) {
                        if (defined $refs{$1}) {
                                push @{$refs{$1}}, $2;
                        } else {
@@@ -2123,7 -2092,7 +2123,7 @@@ sub parse_commit_text 
                        last;
                }
        }
-       if ($co{'title'} eq "") {
+       if (! defined $co{'title'} || $co{'title'} eq "") {
                $co{'title'} = $co{'title_short'} = '(no commit message)';
        }
        # remove added spaces
diff --combined sha1_file.c
index 9ee1ed16ad2df8847bd5008a5d61c807c145357a,e2cb342a32f31be2b9ffc1867fbfd671fe63cef1..aec81bbae7628574e997fc3a6c9f5ae3dda2476d
@@@ -2136,7 -2136,9 +2136,9 @@@ static void write_sha1_file_prepare(con
   */
  int move_temp_to_file(const char *tmpfile, const char *filename)
  {
-       int ret = link(tmpfile, filename);
+       int ret = 0;
+       if (link(tmpfile, filename))
+               ret = errno;
  
        /*
         * Coda hack - coda doesn't like cross-directory links,
@@@ -2361,22 -2363,51 +2363,22 @@@ int has_sha1_file(const unsigned char *
        return has_loose_object(sha1);
  }
  
 -int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object)
 +static int index_mem(unsigned char *sha1, void *buf, size_t size,
 +                   int write_object, enum object_type type, const char *path)
  {
 -      struct strbuf buf;
 -      int ret;
 -
 -      strbuf_init(&buf, 0);
 -      if (strbuf_read(&buf, fd, 4096) < 0) {
 -              strbuf_release(&buf);
 -              return -1;
 -      }
 -
 -      if (!type)
 -              type = blob_type;
 -      if (write_object)
 -              ret = write_sha1_file(buf.buf, buf.len, type, sha1);
 -      else
 -              ret = hash_sha1_file(buf.buf, buf.len, type, sha1);
 -      strbuf_release(&buf);
 -
 -      return ret;
 -}
 -
 -int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object,
 -           enum object_type type, const char *path)
 -{
 -      size_t size = xsize_t(st->st_size);
 -      void *buf = NULL;
        int ret, re_allocated = 0;
  
 -      if (size)
 -              buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
 -      close(fd);
 -
        if (!type)
                type = OBJ_BLOB;
  
        /*
         * Convert blobs to git internal format
         */
 -      if ((type == OBJ_BLOB) && S_ISREG(st->st_mode)) {
 +      if ((type == OBJ_BLOB) && path) {
                struct strbuf nbuf;
                strbuf_init(&nbuf, 0);
                if (convert_to_git(path, buf, size, &nbuf,
                                   write_object ? safe_crlf : 0)) {
 -                      munmap(buf, size);
                        buf = strbuf_detach(&nbuf, &size);
                        re_allocated = 1;
                }
                ret = write_sha1_file(buf, size, typename(type), sha1);
        else
                ret = hash_sha1_file(buf, size, typename(type), sha1);
 -      if (re_allocated) {
 +      if (re_allocated)
                free(buf);
 -              return ret;
 -      }
 -      if (size)
 +      return ret;
 +}
 +
 +int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object,
 +           enum object_type type, const char *path)
 +{
 +      int ret;
 +      size_t size = xsize_t(st->st_size);
 +
 +      if (!S_ISREG(st->st_mode)) {
 +              struct strbuf sbuf;
 +              strbuf_init(&sbuf, 0);
 +              if (strbuf_read(&sbuf, fd, 4096) >= 0)
 +                      ret = index_mem(sha1, sbuf.buf, sbuf.len, write_object,
 +                                      type, path);
 +              else
 +                      ret = -1;
 +              strbuf_release(&sbuf);
 +      } else if (size) {
 +              void *buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
 +              ret = index_mem(sha1, buf, size, write_object, type, path);
                munmap(buf, size);
 +      } else
 +              ret = index_mem(sha1, NULL, size, write_object, type, path);
 +      close(fd);
        return ret;
  }
  
diff --combined t/lib-git-svn.sh
index c526eedd622558275b48fee35fb8382b2c815b9b,5b5f288809bd019662cf8af45c277bf61cd6df5c..67c431d4ebbb32fe8d88a83104485b38d746fa62
@@@ -1,11 -1,8 +1,11 @@@
  . ./test-lib.sh
  
 +remotes_git_svn=remotes/git""-svn
 +git_svn_id=git""-svn-id
 +
  if test -n "$NO_SVN_TESTS"
  then
 -      test_expect_success 'skipping git-svn tests, NO_SVN_TESTS defined' :
 +      test_expect_success 'skipping git svn tests, NO_SVN_TESTS defined' :
        test_done
        exit
  fi
@@@ -17,7 -14,7 +17,7 @@@ SVN_TREE=$GIT_SVN_DIR/svn-tre
  svn >/dev/null 2>&1
  if test $? -ne 1
  then
 -    test_expect_success 'skipping git-svn tests, svn not found' :
 +    test_expect_success 'skipping git svn tests, svn not found' :
      test_done
      exit
  fi
@@@ -91,7 -88,7 +91,7 @@@ start_httpd () 
        mkdir "$GIT_DIR"/logs
  
        cat > "$GIT_DIR/httpd.conf" <<EOF
 -ServerName "git-svn test"
 +ServerName "git svn test"
  ServerRoot "$GIT_DIR"
  DocumentRoot "$GIT_DIR"
  PidFile "$GIT_DIR/httpd.pid"
@@@ -138,3 -135,20 +138,20 @@@ close $wr or die $!
  close $rd or die $!;
  EOF
  }
+ require_svnserve () {
+     if test -z "$SVNSERVE_PORT"
+     then
+         say 'skipping svnserve test. (set $SVNSERVE_PORT to enable)'
+         test_done
+         exit
+     fi
+ }
+ start_svnserve () {
+     svnserve --listen-port $SVNSERVE_PORT \
+              --root "$rawsvnrepo" \
+              --listen-once \
+              --listen-host 127.0.0.1 &
+ }
index 250c53022b6a217890593ecb95c86e81ac61c669,c2b24a439d8f7aee5431a40dfe1fdb5ec61a53f6..e9b6128b3fa5c3d0cbbe5abb7f3e55267263449d
@@@ -8,23 -8,11 +8,11 @@@
  # daemon running on a users system if the test fails.
  # Not all git users will need to interact with SVN.
  
 -test_description='git-svn dcommit new files over svn:// test'
 +test_description='git svn dcommit new files over svn:// test'
  
  . ./lib-git-svn.sh
  
- if test -z "$SVNSERVE_PORT"
- then
-       say 'skipping svnserve test. (set $SVNSERVE_PORT to enable)'
-       test_done
-       exit
- fi
- start_svnserve () {
-       svnserve --listen-port $SVNSERVE_PORT \
-                --root "$rawsvnrepo" \
-                --listen-once \
-                --listen-host 127.0.0.1 &
- }
+ require_svnserve
  
  test_expect_success 'start tracking an empty repo' '
        svn mkdir -m "empty dir" "$svnrepo"/empty-dir &&