{"thread":{"id":"43647","subject":"[PATCH] Make cvsexportcommit work with filenames with spaces and non-ascii characters.","startedAt":"2006-11-15T00:55:30Z","lastAt":"2006-11-15T06:27:43Z","messageCount":3,"participants":["Robin Rosenberg","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"296288","messageId":"20061115005530.26560.18222.stgit@lathund.dewire.com","threadId":"43647","inReplyTo":null,"subject":"[PATCH] Make cvsexportcommit work with filenames with spaces and non-ascii characters.","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg@dewire.com","sentAt":"2006-11-15T00:55:30Z","receivedAt":"2006-11-15T00:55:30Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"From: Robin Rosenberg <robin.rosenberg@dewire.com>\n\nThis patch uses git-apply to do the patching which simplifies the code a lot.\n\nRemoved the test for checking for matching binary files when deleting them\nsince git-apply happily deletes the file. This is matter of taste since we\nallow some fuzz for text patches also.\n\nError handling was cleaned up, but not much testd (it did not work before).\n\nSigned-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>\n---\n\n git-cvsexportcommit.perl       |  141 ++++++++++++++++------------------------\n t/t9200-git-cvsexportcommit.sh |   79 +++++++++++++++++++---\n 2 files changed, 124 insertions(+), 96 deletions(-)\n\ndiff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl\nindex 7bac16e..feff681 100755\n--- a/git-cvsexportcommit.perl\n+++ b/git-cvsexportcommit.perl\n@@ -2,9 +2,8 @@ #!/usr/bin/perl -w\n \n # Known limitations:\n # - does not propagate permissions\n-# - tells \"ready for commit\" even when things could not be completed\n-#   (not sure this is true anymore, more testing is needed)\n-# - does not handle whitespace in pathnames at all.\n+# - error handling has not been extensively tested\n+#\n \n use strict;\n use Getopt::Std;\n@@ -116,12 +115,18 @@ if ($opt_a) {\n close MSG;\n \n my (@afiles, @dfiles, @mfiles, @dirs);\n-my @files = safe_pipe_capture('git-diff-tree', '-r', $parent, $commit);\n-#print @files;\n+my $files = safe_pipe_capture_blob('git-diff-tree', '-z', '-r', $parent, $commit);\n $? && die \"Error in git-diff-tree\";\n-foreach my $f (@files) {\n-    chomp $f;\n-    my @fields = split(m!\\s+!, $f);\n+while ($files =~ m/(.*?\\000.*?\\000)/g) {\n+    my $f=$1;\n+    $f =~ m/^(\\S+) (\\S+) (\\S+) (\\S+) (\\S+)\\000(.*)\\000/;\n+    my @fields = ();\n+    $fields[++$#fields] = $1;\n+    $fields[++$#fields] = $2;\n+    $fields[++$#fields] = $3;\n+    $fields[++$#fields] = $4;\n+    $fields[++$#fields] = $5;\n+    $fields[++$#fields] = $6;\n     if ($fields[4] eq 'A') {\n         my $path = $fields[5];\n \tpush @afiles, $path;\n@@ -139,12 +144,20 @@ foreach my $f (@files) {\n \tpush @dfiles, $fields[5];\n     }\n }\n+\n my (@binfiles, @abfiles, @dbfiles, @bfiles, @mbfiles);\n @binfiles = grep m/^Binary files/, safe_pipe_capture('git-diff-tree', '-p', $parent, $commit);\n map { chomp } @binfiles;\n @abfiles = grep s/^Binary files \\/dev\\/null and b\\/(.*) differ$/$1/, @binfiles;\n @dbfiles = grep s/^Binary files a\\/(.*) and \\/dev\\/null differ$/$1/, @binfiles;\n @mbfiles = grep s/^Binary files a\\/(.*) and b\\/(.*) differ$/$1/, @binfiles;\n+push @abfiles, grep s/^Binary files \\/dev\\/null and \"b\\/(.*)\" differ$/$1/, @binfiles;\n+push @dbfiles, grep s/^Binary files \"a\\/(.*)\" and \\/dev\\/null differ$/$1/, @binfiles;\n+push @mbfiles, grep s/^Binary files \"a\\/(.*)\" and \\\"b\\/(.*)\\\" differ$/$1/, @binfiles;\n+map { s/\\\\([\\d]{3})/sprintf('%c',oct $1)/eg; } @abfiles;\n+map { s/\\\\([\\d]{3})/sprintf('%c',oct $1)/eg; } @dbfiles;\n+map { s/\\\\([\\d]{3})/sprintf('%c',oct $1)/eg; } @mbfiles;\n+\n push @bfiles, @abfiles;\n push @bfiles, @dbfiles;\n push @bfiles, @mbfiles;\n@@ -152,7 +165,6 @@ push @mfiles, @mbfiles;\n \n $opt_v && print \"The commit affects:\\n \";\n $opt_v && print join (\"\\n \", @afiles,@mfiles,@dfiles) . \"\\n\\n\";\n-undef @files; # don't need it anymore\n \n # check that the files are clean and up to date according to cvs\n my $dirty;\n@@ -195,76 +207,36 @@ if ($dirty) {\n     }\n }\n \n-###\n-### NOTE: if you are planning to die() past this point\n-###       you MUST call cleanupcvs(@files) before die()\n-###\n-\n-\n+my $dirtypatch = 0;\n print \"Creating new directories\\n\";\n foreach my $d (@dirs) {\n     unless (mkdir $d) {\n         warn \"Could not mkdir $d: $!\";\n-\t$dirty = 1;\n+\t$dirtypatch = 1;\n     }\n-    `cvs add $d`;\n-    if ($?) {\n-\t$dirty = 1;\n+    print \"Adding directory $d\";\n+    if (system('cvs','add',$d)) {\n+\t$dirtypatch = 1;\n \twarn \"Failed to cvs add directory $d -- you may need to do it manually\";\n     }\n }\n \n-print \"'Patching' binary files\\n\";\n-\n-foreach my $f (@bfiles) {\n-    # check that the file in cvs matches the \"old\" file\n-    # extract the file to $tmpdir and compare with cmp\n-    if (not(grep { $_ eq $f } @afiles)) {\n-        my $tree = safe_pipe_capture('git-rev-parse', \"$parent^{tree}\");\n-\tchomp $tree;\n-\tmy $blob = `git-ls-tree $tree \"$f\" | cut -f 1 | cut -d ' ' -f 3`;\n-\tchomp $blob;\n-        `git-cat-file blob $blob > $tmpdir/blob`;\n-        if (system('cmp', '-s', $f, \"$tmpdir/blob\")) {\n-\t    warn \"Binary file $f in CVS does not match parent.\\n\";\n-\t    if (not $opt_f) {\n-\t        $dirty = 1;\n-\t\tnext;\n-\t    }\n-        }\n-    }\n-    if (not(grep { $_ eq $f } @dfiles)) {\n-\tmy $tree = safe_pipe_capture('git-rev-parse', \"$commit^{tree}\");\n-\tchomp $tree;\n-\tmy $blob = `git-ls-tree $tree \"$f\" | cut -f 1 | cut -d ' ' -f 3`;\n-\tchomp $blob;\n-\t# replace with the new file\n-\t`git-cat-file blob $blob > $f`;\n-    }\n-\n-    # TODO: something smart with file modes\n-\n-}\n-if ($dirty) {\n-    cleanupcvs(@files);\n-    die \"Exiting: Binary files in CVS do not match parent\";\n-}\n-\n ## apply non-binary changes\n my $fuzz = $opt_p ? 0 : 2;\n \n-print \"Patching non-binary files\\n\";\n+print \"Patching\\n\";\n \n if (scalar(@afiles)+scalar(@dfiles)+scalar(@mfiles) != scalar(@bfiles)) {\n-    print `(git-diff-tree -p $parent -p $commit | patch -p1 -F $fuzz ) 2>&1`;\n-}\n-\n-my $dirtypatch = 0;\n-if (($? >> 8) == 2) {\n-    cleanupcvs(@files);\n-    die \"Exiting: Patch reported serious trouble -- you will have to apply this patch manually\";\n-} elsif (($? >> 8) == 1) { # some hunks failed to apply\n-    $dirtypatch = 1;\n+    `git-diff-tree --binary -z -p $parent -p $commit >.cvsexportcommit.diff`;\n+    if ($?) {\n+        die(\"cannot diff\");\n+    }\n+    `GIT_DIR= git-apply -z -C$fuzz .cvsexportcommit.diff`;\n+    if ($?) {\n+        $dirtypatch = 1;\n+    } else {\n+        $opt_v || unlink(\".cvsexportcommit.diff\");\n+    }\n }\n \n foreach my $f (@afiles) {\n@@ -274,7 +246,7 @@ foreach my $f (@afiles) {\n       system('cvs', 'add', $f);\n     }\n     if ($?) {\n-\t$dirty = 1;\n+\t$dirtypatch = 1;\n \twarn \"Failed to cvs add $f -- you may need to do it manually\";\n     }\n }\n@@ -282,19 +254,21 @@ foreach my $f (@afiles) {\n foreach my $f (@dfiles) {\n     system('cvs', 'rm', '-f', $f);\n     if ($?) {\n-\t$dirty = 1;\n+\t$dirtypatch = 1;\n \twarn \"Failed to cvs rm -f $f -- you may need to do it manually\";\n     }\n }\n \n print \"Commit to CVS\\n\";\n-print \"Patch: $title\\n\";\n-my $commitfiles = join(' ', @afiles, @mfiles, @dfiles);\n-my $cmd = \"cvs commit -F .msg $commitfiles\";\n+print \"Patch title: $title\\n\";\n+my @commitfiles = map { unless (m/\\s/) { '\\''.$_.'\\''; } else { $_; }; } (@afiles, @mfiles, @dfiles);\n+my $cmd = \"cvs commit -F .msg @commitfiles\";\n \n if ($dirtypatch) {\n     print \"NOTE: One or more hunks failed to apply cleanly.\\n\";\n-    print \"Resolve the conflicts and then commit using:\\n\";\n+    print \"You'll need to apply the patch in .cvsexportcommit.diff manually\\n\";\n+    print \"using a patch program. After applying the patch and resolving the\\n\";\n+    print \"problems you may commit using:\";\n     print \"\\n    $cmd\\n\\n\";\n     exit(1);\n }\n@@ -304,7 +278,6 @@ if ($opt_c) {\n     print \"Autocommit\\n  $cmd\\n\";\n     print safe_pipe_capture('cvs', 'commit', '-F', '.msg', @afiles, @mfiles, @dfiles);\n     if ($?) {\n-\tcleanupcvs(@files);\n \tdie \"Exiting: The commit did not succeed\";\n     }\n     print \"Committed successfully to CVS\\n\";\n@@ -318,17 +291,6 @@ END\n \texit(1);\n }\n \n-# ensure cvs is clean before we die\n-sub cleanupcvs {\n-    my @files = @_;\n-    foreach my $f (@files) {\n-\tsystem('cvs', '-q', 'update', '-C', $f);\n-\tif ($?) {\n-\t    warn \"Warning! Failed to cleanup state of $f\\n\";\n-\t}\n-    }\n-}\n-\n # An alternative to `command` that allows input to be passed as an array\n # to work around shell problems with weird characters in arguments\n # if the exec returns non-zero we die\n@@ -342,3 +304,16 @@ sub safe_pipe_capture {\n     }\n     return wantarray ? @output : join('',@output);\n }\n+\n+sub safe_pipe_capture_blob {\n+    my $output;\n+    if (my $pid = open my $child, '-|') {\n+        local $/;\n+\tundef $/;\n+\t$output = (<$child>);\n+\tclose $child or die join(' ',@_).\": $! $?\";\n+    } else {\n+\texec(@_) or die \"$! $?\"; # exec() can fail the executable can't be found\n+    }\n+    return $output;\n+}\ndiff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh\nindex 6e566d4..75b9f38 100755\n--- a/t/t9200-git-cvsexportcommit.sh\n+++ b/t/t9200-git-cvsexportcommit.sh\n@@ -89,18 +89,17 @@ test_expect_success \\\n      ! git cvsexportcommit -c $id\n      )'\n \n-# Should fail, but only on the git-cvsexportcommit stage\n-test_expect_success \\\n-    'Fail to remove binary file more than one generation old' \\\n-    'git reset --hard HEAD^ &&\n-     cat F/newfile6.png >>D/newfile4.png &&\n-     git commit -a -m \"generation 2 (again)\" &&\n-     rm -f D/newfile4.png &&\n-     git commit -a -m \"generation 3\" &&\n-     id=$(git rev-list --max-count=1 HEAD) &&\n-     (cd \"$CVSWORK\" &&\n-     ! git cvsexportcommit -c $id\n-     )'\n+#test_expect_success \\\n+#    'Fail to remove binary file more than one generation old' \\\n+#    'git reset --hard HEAD^ &&\n+#     cat F/newfile6.png >>D/newfile4.png &&\n+#     git commit -a -m \"generation 2 (again)\" &&\n+#     rm -f D/newfile4.png &&\n+#     git commit -a -m \"generation 3\" &&\n+#     id=$(git rev-list --max-count=1 HEAD) &&\n+#     (cd \"$CVSWORK\" &&\n+#     ! git cvsexportcommit -c $id\n+#     )'\n \n # We reuse the state from two tests back here\n \n@@ -108,7 +107,7 @@ # This test is here because a patch for \n # fail with gnu patch, so cvsexportcommit must handle that.\n test_expect_success \\\n     'Remove only binary files' \\\n-    'git reset --hard HEAD^^^ &&\n+    'git reset --hard HEAD^^ &&\n      rm -f D/newfile4.png &&\n      git commit -a -m \"test: remove only a binary file\" &&\n      id=$(git rev-list --max-count=1 HEAD) &&\n@@ -142,4 +141,58 @@ test_expect_success \\\n      diff F/newfile6.png ../F/newfile6.png\n      )'\n \n+test_expect_success \\\n+     'New file with spaces in file name' \\\n+     'mkdir \"G g\" &&\n+      echo ok then >\"G g/with spaces.txt\" &&\n+      git add \"G g/with spaces.txt\" && \\\n+      cp ../test9200a.png \"G g/with spaces.png\" && \\\n+      git add \"G g/with spaces.png\" &&\n+      git commit -a -m \"With spaces\" &&\n+      id=$(git rev-list --max-count=1 HEAD) &&\n+      (cd \"$CVSWORK\" &&\n+      git-cvsexportcommit -c $id &&\n+      test \"$(echo $(sort \"G g/CVS/Entries\"|cut -d/ -f2,3,5))\" = \"with spaces.png/1.1/-kb with spaces.txt/1.1/\"\n+      )'\n+\n+test_expect_success \\\n+     'Update file with spaces in file name' \\\n+     'echo Ok then >>\"G g/with spaces.txt\" &&\n+      cat ../test9200a.png >>\"G g/with spaces.png\" && \\\n+      git add \"G g/with spaces.png\" &&\n+      git commit -a -m \"Update with spaces\" &&\n+      id=$(git rev-list --max-count=1 HEAD) &&\n+      (cd \"$CVSWORK\" &&\n+      git-cvsexportcommit -c $id\n+      test \"$(echo $(sort \"G g/CVS/Entries\"|cut -d/ -f2,3,5))\" = \"with spaces.png/1.2/-kb with spaces.txt/1.2/\"\n+      )'\n+\n+# This test contains ISO-8859-1 characters\n+test_expect_success \\\n+     'File with non-ascii file name' \\\n+     'mkdir Å &&\n+      echo Foo >Å/gårdetsågårdet.txt &&\n+      git add Å/gårdetsågårdet.txt &&\n+      cp ../test9200a.png Å/gårdetsågårdet.png &&\n+      git add Å/gårdetsågårdet.png &&\n+      git commit -a -m \"Går det så går det\" && \\\n+      id=$(git rev-list --max-count=1 HEAD) &&\n+      (cd \"$CVSWORK\" &&\n+      git-cvsexportcommit -v -c $id &&\n+      test \"$(echo $(sort Å/CVS/Entries|cut -d/ -f2,3,5))\" = \"gårdetsågårdet.png/1.1/-kb gårdetsågårdet.txt/1.1/\"\n+      )'\n+\n+test_expect_success \\\n+     'Mismatching patch should fail' \\\n+     'date >>\"E/newfile5.txt\" &&\n+      git add \"E/newfile5.txt\" &&\n+      git commit -a -m \"Update one\" &&\n+      date >>\"E/newfile5.txt\" &&\n+      git add \"E/newfile5.txt\" &&\n+      git commit -a -m \"Update two\" &&\n+      id=$(git rev-list --max-count=1 HEAD) &&\n+      (cd \"$CVSWORK\" &&\n+      ! git-cvsexportcommit -c $id\n+      )'\n+\n"},{"id":"298352","messageId":"7v8xidqm1e.fsf@assigned-by-dhcp.cox.net","threadId":"43647","inReplyTo":"20061115005530.26560.18222.stgit@lathund.dewire.com","subject":"Re: [PATCH] Make cvsexportcommit work with filenames with spaces and non-ascii characters.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-11-15T06:08:45Z","receivedAt":"2006-11-15T06:08:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Robin Rosenberg <robin.rosenberg@dewire.com> writes:\n\n> From: Robin Rosenberg <robin.rosenberg@dewire.com>\n>\n> This patch uses git-apply to do the patching which simplifies the code a lot.\n>\n> Removed the test for checking for matching binary files when deleting them\n> since git-apply happily deletes the file. This is matter of taste since we\n> allow some fuzz for text patches also.\n>\n> Error handling was cleaned up, but not much testd (it did not work before).\n>\n> Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>\n\nThanks.  Allow me to put this in freezer for a few days while\nreleasing v1.4.4.\n\nSome comments below.\n\n> @@ -116,12 +115,18 @@ if ($opt_a) {\n>  close MSG;\n>  \n>  my (@afiles, @dfiles, @mfiles, @dirs);\n> -my @files = safe_pipe_capture('git-diff-tree', '-r', $parent, $commit);\n> -#print @files;\n> +my $files = safe_pipe_capture_blob('git-diff-tree', '-z', '-r', $parent, $commit);\n>  $? && die \"Error in git-diff-tree\";\n> +while ($files =~ m/(.*?\\000.*?\\000)/g) {\n> +    my $f=$1;\n> +    $f =~ m/^(\\S+) (\\S+) (\\S+) (\\S+) (\\S+)\\000(.*)\\000/;\n> +    my @fields = ();\n> +    $fields[++$#fields] = $1;\n> +    $fields[++$#fields] = $2;\n> +    $fields[++$#fields] = $3;\n> +    $fields[++$#fields] = $4;\n> +    $fields[++$#fields] = $5;\n> +    $fields[++$#fields] = $6;\n\nmy @fields = ($f =~ m/^.../);\n\n>  my (@binfiles, @abfiles, @dbfiles, @bfiles, @mbfiles);\n>  @binfiles = grep m/^Binary files/, safe_pipe_capture('git-diff-tree', '-p', $parent, $commit);\n>  map { chomp } @binfiles;\n>  @abfiles = grep s/^Binary files \\/dev\\/null and b\\/(.*) differ$/$1/, @binfiles;\n>  @dbfiles = grep s/^Binary files a\\/(.*) and \\/dev\\/null differ$/$1/, @binfiles;\n>  @mbfiles = grep s/^Binary files a\\/(.*) and b\\/(.*) differ$/$1/, @binfiles;\n> +push @abfiles, grep s/^Binary files \\/dev\\/null and \"b\\/(.*)\" differ$/$1/, @binfiles;\n> +push @dbfiles, grep s/^Binary files \"a\\/(.*)\" and \\/dev\\/null differ$/$1/, @binfiles;\n> +push @mbfiles, grep s/^Binary files \"a\\/(.*)\" and \\\"b\\/(.*)\\\" differ$/$1/, @binfiles;\n> +map { s/\\\\([\\d]{3})/sprintf('%c',oct $1)/eg; } @abfiles;\n> +map { s/\\\\([\\d]{3})/sprintf('%c',oct $1)/eg; } @dbfiles;\n> +map { s/\\\\([\\d]{3})/sprintf('%c',oct $1)/eg; } @mbfiles;\n> +\n\nLogically these map {} should be done only on the c-quoted\nnames, but it is Ok because the names that have backslash with\noctal are quoted.  However, what's inside the map is not correct\nc dequoting (see how Jakub does it in gitweb -- you need to\nworry about \\\\, \\\" and such).\n\nI think it is perhaps safer to parse \"diff --git\" and remember\nthe filenames of the \"current patch\" and then notice only\n/^Binary files / part of the message.\n\nBut all of the above shows deficiency in the current set of\ntools -- they are not helping Porcelain writers enough.  I think\nwe should enhance 'apply --numstat' to let it show binary diffs\ndifferently:\n\n\tgit diff-tree -p $parent $commit >.tmpfile\n        git apply --numstat -z <.tmpfile\n\nwould currently say \"0 0\" for binary files (the primary benefit\nof using \"--numstat -z\" here is that it would give Perl scripts\npathnames parsable without C dequoting).  We should somehow have\na way to show it differently from text files without any\nadded/deleted lines (e.g. only the mode change), and that would\nmake the life of Porcelain writers who needs to write something\nlike the above code much more pleasant.  Perhaps show \"- -\"\ninstead of \"0 0\", since there is no notion of lines in \"binary\nfiles differ\" case?\n\n> -    `cvs add $d`;\n> -    if ($?) {\n> -\t$dirty = 1;\n> +    print \"Adding directory $d\";\n> +    if (system('cvs','add',$d)) {\n> +\t$dirtypatch = 1;\n\nGood.\n\n>  ## apply non-binary changes\n>  my $fuzz = $opt_p ? 0 : 2;\n>  \n> -print \"Patching non-binary files\\n\";\n> +print \"Patching\\n\";\n\nLeftover comment that does not apply anymore.\n\n>  if (scalar(@afiles)+scalar(@dfiles)+scalar(@mfiles) != scalar(@bfiles)) {\n> +    `git-diff-tree --binary -z -p $parent -p $commit >.cvsexportcommit.diff`;\n\nHaven't you run this diff before to grep for \"Binary files...\"\nMaybe doing a temporary file upfront once and using it would\nmake sense.\n\nAlso why multiple -p?  I do not think -z is wanted here, as -z\naffects only output side of git apply and not input side (see\nthe above comment on --numstat -z).\n"},{"id":"296727","messageId":"7vu011p6lc.fsf@assigned-by-dhcp.cox.net","threadId":"43647","inReplyTo":"7v8xidqm1e.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH] Make cvsexportcommit work with filenames with spaces and non-ascii characters.","fromName":"Junio C Hamano","fromEmail":"junkio@cox.net","sentAt":"2006-11-15T06:27:43Z","receivedAt":"2006-11-15T06:27:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <junkio@cox.net> writes:\n\n> But all of the above shows deficiency in the current set of\n> tools -- they are not helping Porcelain writers enough.  I think\n> we should enhance 'apply --numstat' to let it show binary diffs\n> differently:\n>\n> \tgit diff-tree -p $parent $commit >.tmpfile\n>         git apply --numstat -z <.tmpfile\n>\n> would currently say \"0 0\" for binary files (the primary benefit\n> of using \"--numstat -z\" here is that it would give Perl scripts\n> pathnames parsable without C dequoting).  We should somehow have\n> a way to show it differently from text files without any\n> added/deleted lines (e.g. only the mode change), and that would\n> make the life of Porcelain writers who needs to write something\n> like the above code much more pleasant.  Perhaps show \"- -\"\n> instead of \"0 0\", since there is no notion of lines in \"binary\n> files differ\" case?\n\nThat is, something like this...\n\n-- >8 --\n[PATCH] apply --numstat: mark binary diffstat with - -, not 0 0\n\nWe do not even know number of lines so showing it as 0 0 is\nlying.  This would also help Porcelains like cvsexportcommit.\n\nSigned-off-by: Junio C Hamano <junkio@cox.net>\n---\n builtin-apply.c |    6 +++++-\n 1 files changed, 5 insertions(+), 1 deletions(-)\n\ndiff --git a/builtin-apply.c b/builtin-apply.c\nindex aad5526..b80ad2c 100644\n--- a/builtin-apply.c\n+++ b/builtin-apply.c\n@@ -2116,7 +2116,11 @@ static void numstat_patch_list(struct pa\n \tfor ( ; patch; patch = patch->next) {\n \t\tconst char *name;\n \t\tname = patch->new_name ? patch->new_name : patch->old_name;\n-\t\tprintf(\"%d\\t%d\\t\", patch->lines_added, patch->lines_deleted);\n+\t\tif (patch->is_binary)\n+\t\t\tprintf(\"-\\t-\\t\");\n+\t\telse\n+\t\t\tprintf(\"%d\\t%d\\t\",\n+\t\t\t       patch->lines_added, patch->lines_deleted);\n \t\tif (line_termination && quote_c_style(name, NULL, NULL, 0))\n \t\t\tquote_c_style(name, NULL, stdout, 0);\n \t\telse\n-- \n1.4.4.rc2.g2a54\n\n"}]}