{"thread":{"id":"43314","subject":"Re: [PATCH] Make cvsexportcommit work with filenames with spaces and non-ascii characters.","startedAt":"2006-12-09T23:29:19Z","lastAt":"2006-12-12T01:15:49Z","messageCount":8,"participants":["Junio C Hamano","Robin Rosenberg"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"297864","messageId":"20061209232919.31863.58547.stgit@lathund.dewire.com","threadId":"43314","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-12-09T23:29:19Z","receivedAt":"2006-12-09T23:29:19Z","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 tested.\n\nSigned-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>\n---\n\nThis is a cleaned up version of a previously submitted patch with same name. \nI'm using non-ascii characters in the \n\n git-cvsexportcommit.perl       |  166 +++++++++++++---------------------------\n t/t9200-git-cvsexportcommit.sh |  108 +++++++++++++++++++-------\n 2 files changed, 133 insertions(+), 141 deletions(-)\n\ndiff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl\nindex c9d1d88..f1a9c1b 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,16 +115,13 @@ if ($opt_a) {\n close MSG;\n \n my (@afiles, @dfiles, @mfiles, @dirs);\n-my %amodes;\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+    my @fields = ( $f =~ m/^(\\S+) (\\S+) (\\S+) (\\S+) (\\S+)\\000(.*)\\000/ );\n     if ($fields[4] eq 'A') {\n         my $path = $fields[5];\n-\t$amodes{$path} = $fields[1];\n \tpush @afiles, $path;\n         # add any needed parent directories\n \t$path = dirname $path;\n@@ -141,20 +137,27 @@ foreach my $f (@files) {\n \tpush @dfiles, $fields[5];\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 @bfiles, @abfiles;\n-push @bfiles, @dbfiles;\n-push @bfiles, @mbfiles;\n-push @mfiles, @mbfiles;\n \n+my (@diff,@binfiles, @abfiles, @dbfiles, @bfiles, @mbfiles);\n+@diff = safe_pipe_capture('git-diff-tree','--binary','-p',$parent,$commit);\n+# the --binary format is harder to grok for names of binary files so we execute a new diff\n+# if it looks like binary files exists to find out\n+if (grep /^GIT binary patch$/, @diff) {\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+    push @bfiles, @abfiles;\n+    push @bfiles, @dbfiles;\n+    push @bfiles, @mbfiles;\n+    map { s/\\\\([\\d]{3})/sprintf('%c',oct $1)/eg } @bfiles;\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@@ -197,87 +200,32 @@ 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-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-    }\n-    `cvs add $d`;\n-    if ($?) {\n-\t$dirty = 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+open APPLY, \"|GIT_DIR= git-apply -C$fuzz\" || die \"cannot patch\";\n+(print APPLY @diff) || die \"cannot patch\";\n+close APPLY || die \"cannot patch\";\n \n+print \"Patch applied successfully. Adding new files and directories to CVS\\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+foreach my $d (@dirs) {\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 foreach my $f (@afiles) {\n-    set_new_file_permissions($f, $amodes{$f});\n     if (grep { $_ eq $f } @bfiles) {\n       system('cvs', 'add','-kb',$f);\n     } else {\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@@ -285,29 +233,29 @@ 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 (first comment line): $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 \n-\n 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@@ -321,17 +269,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@@ -346,12 +283,15 @@ sub safe_pipe_capture {\n     return wantarray ? @output : join('',@output);\n }\n \n-# For any file we want to add to cvs, we must first set its permissions\n-# properly, *before* the \"cvs add ...\" command.  Otherwise, it is impossible\n-# to change the permission of the file in the CVS repository using only cvs\n-# commands.  This should be fixed in cvs-1.12.14.\n-sub set_new_file_permissions {\n-    my ($file, $perm) = @_;\n-    chmod oct($perm), $file\n-      or die \"failed to set permissions of \\\"$file\\\": $!\\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 c102479..63eafc8 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,20 +141,73 @@ test_expect_success \\\n      diff F/newfile6.png ../F/newfile6.png\n      )'\n \n-test_expect_success 'Retain execute bit' '\n-\tmkdir G &&\n-\techo executeon >G/on &&\n-\tchmod +x G/on &&\n-\techo executeoff >G/off &&\n-\tgit add G/on &&\n-\tgit add G/off &&\n-\tgit commit -a -m \"Execute test\" &&\n-\t(\n-\t\tcd \"$CVSWORK\" &&\n-\t\tgit-cvsexportcommit -c HEAD\n-\t\ttest -x G/on &&\n-\t\t! test -x G/off\n-\t)\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+test_expect_success \\\n+     'Retain execute bit' \\\n+     'mkdir G &&\n+      echo executeon >G/on &&\n+      chmod +x G/on &&\n+      echo executeoff >G/off &&\n+      git add G/on &&\n+      git add G/off &&\n+      git commit -a -m \"Execute test\" &&\n+      (cd \"$CVSWORK\" &&\n+      git-cvsexportcommit -c HEAD\n+      test -x G/on &&\n+      ! test -x G/off\n+      )'\n \n"},{"id":"294324","messageId":"7v3b7o8s5j.fsf@assigned-by-dhcp.cox.net","threadId":"43314","inReplyTo":"20061209232919.31863.58547.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-12-10T01:18:48Z","receivedAt":"2006-12-10T01:18:48Z","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> 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 tested.\n\nInteresting.\n\nI think you should be able to generate the patchfile once, and\nuse git-apply to figure out additions, deletions and binaryness,\nand then use the same patchfile to apply the changes.  Currently\nchecking for binaryness is not easy with git-apply, so we would\nwant to fix git-apply first, instead of forcing you to have a\nchange like this:\n\n   # the --binary format is harder to grok for names of binary\n   # files so we execute a new diff\n   # if it looks like binary files exists to find out\n   if (grep /^GIT binary patch$/, @diff) {\n       @binfiles = grep m/^Binary files/,\n       safe_pipe_capture('git-diff-tree', '-p', $parent, $commit);\n\nwhich is way too ugly.\n\n\t... goes to look and comes back, with a big grin ...\n\nWell, have you tried this?\n\n\tgit diff-tree -p --binary fe142b3a | git apply --summary --numstat\n\nThe numstat part would let you see the binaryness, so we do not\nhave to \"fix\" git-apply.\n\nAnother thing that _might_ be interesting is to use rename\ndetection when preparing the patch, and make the matching rename\non the CVS side, but I do not recall the details of how one\nwould make CVS pretend to support renamed paths ;-).  I think it\ninvolved copying the ,v file to a new name, and marking the\nolder revisions in that new ,v file as nonexistent or something\nlike that, but I did it only in my distant past and forgot the\ndetails.\n\nBy the way, I am not sure if giving fuzz by default is such a\ngood idea, though.\n"},{"id":"294716","messageId":"200612101639.22397.robin.rosenberg@dewire.com","threadId":"43314","inReplyTo":"7v3b7o8s5j.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH] Make cvsexportcommit work with filenames with spaces and non-ascii characters.","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg@dewire.com","sentAt":"2006-12-10T15:39:21Z","receivedAt":"2006-12-10T15:39:21Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"söndag 10 december 2006 02:18 skrev Junio C Hamano:\n> Robin Rosenberg <robin.rosenberg@dewire.com> writes:\n> > This patch uses git-apply to do the patching which simplifies the code a\n> > lot.\n> >\n> > Removed the test for checking for matching binary files when deleting\n> > them since git-apply happily deletes the file. This is matter of taste\n> > since we allow some fuzz for text patches also.\n> >\n> > Error handling was cleaned up, but not much tested.\n>\n> Interesting.\n>\n> I think you should be able to generate the patchfile once, and\n> use git-apply to figure out additions, deletions and binaryness,\n> and then use the same patchfile to apply the changes.  Currently\n> checking for binaryness is not easy with git-apply, so we would\n> want to fix git-apply first, instead of forcing you to have a\n> change like this:\n>\n>    # the --binary format is harder to grok for names of binary\n>    # files so we execute a new diff\n>    # if it looks like binary files exists to find out\n>    if (grep /^GIT binary patch$/, @diff) {\n>        @binfiles = grep m/^Binary files/,\n>        safe_pipe_capture('git-diff-tree', '-p', $parent, $commit);\n>\n> which is way too ugly.\n>\n> \t... goes to look and comes back, with a big grin ...\n<grin> apparently\n\n>\n> Well, have you tried this?\n>\n> \tgit diff-tree -p --binary fe142b3a | git apply --summary --numstat\nof course not. I didn't understand it. Why can't it tell me about removed\nbinary files, so I could remove the git-diff-tree invocation to find out about \nadded/removed files?\n\n> The numstat part would let you see the binaryness, so we do not\n> have to \"fix\" git-apply.\n>\n> Another thing that _might_ be interesting is to use rename\n> detection when preparing the patch, and make the matching rename\n> on the CVS side, but I do not recall the details of how one\n> would make CVS pretend to support renamed paths ;-).  I think it\n> involved copying the ,v file to a new name, and marking the\n> older revisions in that new ,v file as nonexistent or something\n> like that, but I did it only in my distant past and forgot the\n> details.\nIn server mode, which is the normal way of using CVS you cannot\ndo this with the CVS most of us are used with. CvsNT does support \na rename command, I think, but I don't use it, partly due to rumors of it \nbeing somewhat unstable. If there's any truth in that, I don't know.\n\nAnyway, I don't practice the rename trick in CVS myself. I'm not  sure how \nthat would work with the roundtripping via CVS that I do.  cvsimport \ndetects \"renames\" with the current approach so I'm happy as is, not that I \nrename files much.  I also think there than a copy is needed to play nice, \nlike removing old tags from the copy, how about rename back and forth etc. \nThere's a reason people want to migrate from CVS, as well as there are \nreasons not to hurry. CVS doesn't support rename, it's that simple.\n\n> By the way, I am not sure if giving fuzz by default is such a\n> good idea, though.\n\nIt was in the original. I don't know why. Maybe the original author can tell \nus why it was important. It may be problematic to stay fully in sync with a \nCVS repo because you have to git-cvsimport it first and that takes some time. \nThis fuzz gives some, but not much slack. Reverting the option could be a \ngood idea.\n\nUpdate follows.\n\n"},{"id":"298669","messageId":"20061210154542.3909.289.stgit@lathund.dewire.com","threadId":"43314","inReplyTo":"200612101639.22397.robin.rosenberg@dewire.com","subject":"[PATCH] Make cvsexportcommit work with filenames with spaces and non-ascii characters.","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg@dewire.com","sentAt":"2006-12-10T15:45:42Z","receivedAt":"2006-12-10T15:45:42Z","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 tested.\n\nSigned-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>\n---\n\nUpdated patch using --numstat to figure which files are binary.\n\n git-cvsexportcommit.perl       |  158 +++++++++++-----------------------------\n t/t9200-git-cvsexportcommit.sh |  108 ++++++++++++++++++++-------\n 2 files changed, 125 insertions(+), 141 deletions(-)\n\ndiff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl\nindex c9d1d88..159af66 100755\n--- a/git-cvsexportcommit.perl\n+++ b/git-cvsexportcommit.perl\n@@ -2,9 +2,8 @@\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,16 +115,13 @@ if ($opt_a) {\n close MSG;\n \n my (@afiles, @dfiles, @mfiles, @dirs);\n-my %amodes;\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+    my @fields = ( $f =~ m/^(\\S+) (\\S+) (\\S+) (\\S+) (\\S+)\\000(.*)\\000/ );\n     if ($fields[4] eq 'A') {\n         my $path = $fields[5];\n-\t$amodes{$path} = $fields[1];\n \tpush @afiles, $path;\n         # add any needed parent directories\n \t$path = dirname $path;\n@@ -141,20 +137,10 @@ foreach my $f (@files) {\n \tpush @dfiles, $fields[5];\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 @bfiles, @abfiles;\n-push @bfiles, @dbfiles;\n-push @bfiles, @mbfiles;\n-push @mfiles, @mbfiles;\n \n+`git-diff-tree --binary -p $parent $commit >.cvsexportcommit.diff`;# || die \"Cannot diff\";\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@@ -197,87 +183,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-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-    }\n-    `cvs add $d`;\n-    if ($?) {\n-\t$dirty = 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+my @stat;\n+open APPLY, \"GIT_DIR= git-apply -C$fuzz --binary --numstat --apply <.cvsexportcommit.diff|\" || die \"cannot patch\";\n+@stat=<APPLY>;\n+close APPLY || die \"Cannot patch\";\n+my @bfiles=grep { chomp && s/^\\-\\t\\-\\t(.*)$/$1/; } @stat;\n+map { s/^\"(.*)\"$/$1/g } @bfiles;\n+map { s/\\\\([\\d]{3})/sprintf('%c',oct $1)/eg } @bfiles;\n \n+print \"Patch applied successfully. Adding new files and directories to CVS\\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+foreach my $d (@dirs) {\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 foreach my $f (@afiles) {\n-    set_new_file_permissions($f, $amodes{$f});\n     if (grep { $_ eq $f } @bfiles) {\n       system('cvs', 'add','-kb',$f);\n     } else {\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@@ -285,35 +220,40 @@ 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 (first comment line): $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 \n-\n 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 } else {\n     print \"Ready for you to commit, just run:\\n\\n   $cmd\\n\";\n }\n+\n+# clean up\n+unlink(\".cvsexportcommit.diff\");\n+unlink(\".msg\");\n+\n sub usage {\n \tprint STDERR <<END;\n Usage: GIT_DIR=/path/to/.git ${\\basename $0} [-h] [-p] [-v] [-c] [-f] [-m msgprefix] [ parent ] commit\n@@ -321,17 +261,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@@ -346,12 +275,15 @@ sub safe_pipe_capture {\n     return wantarray ? @output : join('',@output);\n }\n \n-# For any file we want to add to cvs, we must first set its permissions\n-# properly, *before* the \"cvs add ...\" command.  Otherwise, it is impossible\n-# to change the permission of the file in the CVS repository using only cvs\n-# commands.  This should be fixed in cvs-1.12.14.\n-sub set_new_file_permissions {\n-    my ($file, $perm) = @_;\n-    chmod oct($perm), $file\n-      or die \"failed to set permissions of \\\"$file\\\": $!\\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 c102479..63eafc8 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 @@ test_expect_success \\\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,20 +141,73 @@ test_expect_success \\\n      diff F/newfile6.png ../F/newfile6.png\n      )'\n \n-test_expect_success 'Retain execute bit' '\n-\tmkdir G &&\n-\techo executeon >G/on &&\n-\tchmod +x G/on &&\n-\techo executeoff >G/off &&\n-\tgit add G/on &&\n-\tgit add G/off &&\n-\tgit commit -a -m \"Execute test\" &&\n-\t(\n-\t\tcd \"$CVSWORK\" &&\n-\t\tgit-cvsexportcommit -c HEAD\n-\t\ttest -x G/on &&\n-\t\t! test -x G/off\n-\t)\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+test_expect_success \\\n+     'Retain execute bit' \\\n+     'mkdir G &&\n+      echo executeon >G/on &&\n+      chmod +x G/on &&\n+      echo executeoff >G/off &&\n+      git add G/on &&\n+      git add G/off &&\n+      git commit -a -m \"Execute test\" &&\n+      (cd \"$CVSWORK\" &&\n+      git-cvsexportcommit -c HEAD\n+      test -x G/on &&\n+      ! test -x G/off\n+      )'\n \n"},{"id":"295140","messageId":"7vlklf4f1s.fsf@assigned-by-dhcp.cox.net","threadId":"43314","inReplyTo":"200612101639.22397.robin.rosenberg@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-12-10T21:27:43Z","receivedAt":"2006-12-10T21:27:43Z","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>> Well, have you tried this?\n>>\n>> \tgit diff-tree -p --binary fe142b3a | git apply --summary --numstat\n> of course not. I didn't understand it. Why can't it tell me about removed\n> binary files, so I could remove the git-diff-tree invocation to find out about \n> added/removed files?\n\nMaybe I am missing something.  It tells you about added or\nremoved files (either binary or non-binary).\n\nI'd prepare a pair of practice patch files.  \"forward\" has\ncreation, \"reverse\" has addition.\n\n$ git diff-tree    -p --binary fe142b3a >forward.patch\n$ git diff-tree -R -p --binary fe142b3a >reverse.patch\n\n\n$ git apply --summary --numstat forward.patch\n50      20      git-cvsexportcommit.perl\n145     0       t/t9200-git-cvsexportcommit.sh\n-       -       t/test9200a.png\n-       -       t/test9200b.png\n create mode 100755 t/t9200-git-cvsexportcommit.sh\n create mode 100644 t/test9200a.png\n create mode 100644 t/test9200b.png\n\n$ git apply --summary --numstat reverse.patch\n20      50      git-cvsexportcommit.perl\n0       145     t/t9200-git-cvsexportcommit.sh\n-       -       t/test9200a.png\n-       -       t/test9200b.png\n delete mode 100755 t/t9200-git-cvsexportcommit.sh\n delete mode 100644 t/test9200a.png\n delete mode 100644 t/test9200b.png\n\n> This fuzz gives some, but not much slack. Reverting the option could be a \n> good idea.\n>\n> Update follows.\n\nOk.\n"},{"id":"297617","messageId":"20061210233006.21227.18064.stgit@lathund.dewire.com","threadId":"43314","inReplyTo":"7v3b7o8s5j.fsf@assigned-by-dhcp.cox.net","subject":"[PATCH] Make cvsexportcommit work with filenames with spaces and non-ascii characters.","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg@dewire.com","sentAt":"2006-12-10T23:30:06Z","receivedAt":"2006-12-10T23:30:06Z","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\nand also uses one pass to git-diff. git-apply gives information on added,\nremoved files as well as which files are binary.\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 tested.\n\nSigned-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>\n---\n\nYet another version (maybe I'm learning perl now....). This one uses one pass\nto git-diff, but compensates that with two to git-apply.\n\n-- robin\n\n git-cvsexportcommit.perl       |  203 +++++++++++++---------------------------\n t/t9200-git-cvsexportcommit.sh |  108 ++++++++++++++++-----\n 2 files changed, 145 insertions(+), 166 deletions(-)\n\ndiff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl\nindex c9d1d88..8f37123 100755\n--- a/git-cvsexportcommit.perl\n+++ b/git-cvsexportcommit.perl\n@@ -2,9 +2,8 @@\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@@ -115,49 +114,40 @@ if ($opt_a) {\n }\n close MSG;\n \n-my (@afiles, @dfiles, @mfiles, @dirs);\n-my %amodes;\n-my @files = safe_pipe_capture('git-diff-tree', '-r', $parent, $commit);\n-#print @files;\n-$? && die \"Error in git-diff-tree\";\n-foreach my $f (@files) {\n-    chomp $f;\n-    my @fields = split(m!\\s+!, $f);\n-    if ($fields[4] eq 'A') {\n-        my $path = $fields[5];\n-\t$amodes{$path} = $fields[1];\n-\tpush @afiles, $path;\n-        # add any needed parent directories\n-\t$path = dirname $path;\n-\twhile (!-d $path and ! grep { $_ eq $path } @dirs) {\n-\t    unshift @dirs, $path;\n-\t    $path = dirname $path;\n-\t}\n-    }\n-    if ($fields[4] eq 'M') {\n-\tpush @mfiles, $fields[5];\n-    }\n-    if ($fields[4] eq 'D') {\n-\tpush @dfiles, $fields[5];\n-    }\n+`git-diff-tree --binary -p $parent $commit >.cvsexportcommit.diff`;# || die \"Cannot diff\";\n+\n+## apply non-binary changes\n+my $fuzz = $opt_p ? 0 : 2;\n+\n+print \"Checking if patch will apply\\n\";\n+\n+my @stat;\n+open APPLY, \"GIT_DIR= git-apply -C$fuzz --binary --summary --numstat<.cvsexportcommit.diff|\" || die \"cannot patch\";\n+@stat=<APPLY>;\n+close APPLY || die \"Cannot patch\";\n+my (@bfiles,@files,@afiles,@dfiles);\n+chomp @stat;\n+foreach (@stat) {\n+\tpush (@bfiles,$1) if m/^\\-\\t\\-\\t(.*)$/;\n+\tpush (@files,$1)  if m/^\\-\\t\\-\\t(.*)$/;\n+\tpush (@files, $1) if m/^\\d+\\t\\d+\\t(.*)$/;\n+\tpush (@afiles,$1) if m/^ +create mode \\d+ (.*)$/;\n+\tpush (@dfiles,$1) if m/^ +delete mode \\d{6} (.*)$/;\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 @bfiles, @abfiles;\n-push @bfiles, @dbfiles;\n-push @bfiles, @mbfiles;\n-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+map { s/^\"(.*)\"$/$1/g } @bfiles,@files;\n+map { s/\\\\([\\d]{3})/sprintf('%c',oct $1)/eg } @bfiles,@files;\n \n # check that the files are clean and up to date according to cvs\n my $dirty;\n+my @dirs;\n+foreach my $p (@afiles) {\n+    my $path = dirname $p;\n+    while (!-d $path and ! grep { $_ eq $path } @dirs) {\n+    \tunshift @dirs, $path;\n+        $path = dirname $path;\n+    }\n+}\n+\n foreach my $d (@dirs) {\n     if (-e $d) {\n \t$dirty = 1;\n@@ -180,7 +170,8 @@ foreach my $f (@afiles) {\n     }\n }\n \n-foreach my $f (@mfiles, @dfiles) {\n+foreach my $f (@files) {\n+    next if grep { $_ eq $f } @afiles;\n     # TODO:we need to handle removed in cvs\n     my @status = grep(m/^File/,  safe_pipe_capture('cvs', '-q', 'status' ,$f));\n     if (@status > 1) { warn 'Strange! cvs status returned more than one line?'};\n@@ -197,87 +188,26 @@ 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+print \"Applying\\n\";\n+`GIT_DIR= git-apply -C$fuzz --binary --summary --numstat --apply <.cvsexportcommit.diff` || die \"cannot patch\";\n \n-\n-print \"Creating new directories\\n\";\n+print \"Patch applied successfully. Adding new files and directories to CVS\\n\";\n+my $dirtypatch = 0;\n foreach my $d (@dirs) {\n-    unless (mkdir $d) {\n-        warn \"Could not mkdir $d: $!\";\n-\t$dirty = 1;\n-    }\n-    `cvs add $d`;\n-    if ($?) {\n-\t$dirty = 1;\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-\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-}\n-\n foreach my $f (@afiles) {\n-    set_new_file_permissions($f, $amodes{$f});\n     if (grep { $_ eq $f } @bfiles) {\n       system('cvs', 'add','-kb',$f);\n     } else {\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@@ -285,35 +215,40 @@ 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 (first comment line): $title\\n\";\n+my @commitfiles = map { unless (m/\\s/) { '\\''.$_.'\\''; } else { $_; }; } (@files);\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 \n-\n if ($opt_c) {\n     print \"Autocommit\\n  $cmd\\n\";\n-    print safe_pipe_capture('cvs', 'commit', '-F', '.msg', @afiles, @mfiles, @dfiles);\n+    print safe_pipe_capture('cvs', 'commit', '-F', '.msg', @files);\n     if ($?) {\n-\tcleanupcvs(@files);\n \tdie \"Exiting: The commit did not succeed\";\n     }\n     print \"Committed successfully to CVS\\n\";\n } else {\n     print \"Ready for you to commit, just run:\\n\\n   $cmd\\n\";\n }\n+\n+# clean up\n+unlink(\".cvsexportcommit.diff\");\n+unlink(\".msg\");\n+\n sub usage {\n \tprint STDERR <<END;\n Usage: GIT_DIR=/path/to/.git ${\\basename $0} [-h] [-p] [-v] [-c] [-f] [-m msgprefix] [ parent ] commit\n@@ -321,17 +256,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@@ -346,12 +270,15 @@ sub safe_pipe_capture {\n     return wantarray ? @output : join('',@output);\n }\n \n-# For any file we want to add to cvs, we must first set its permissions\n-# properly, *before* the \"cvs add ...\" command.  Otherwise, it is impossible\n-# to change the permission of the file in the CVS repository using only cvs\n-# commands.  This should be fixed in cvs-1.12.14.\n-sub set_new_file_permissions {\n-    my ($file, $perm) = @_;\n-    chmod oct($perm), $file\n-      or die \"failed to set permissions of \\\"$file\\\": $!\\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 c102479..ca0513b 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 @@ test_expect_success \\\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,20 +141,73 @@ test_expect_success \\\n      diff F/newfile6.png ../F/newfile6.png\n      )'\n \n-test_expect_success 'Retain execute bit' '\n-\tmkdir G &&\n-\techo executeon >G/on &&\n-\tchmod +x G/on &&\n-\techo executeoff >G/off &&\n-\tgit add G/on &&\n-\tgit add G/off &&\n-\tgit commit -a -m \"Execute test\" &&\n-\t(\n-\t\tcd \"$CVSWORK\" &&\n-\t\tgit-cvsexportcommit -c HEAD\n-\t\ttest -x G/on &&\n-\t\t! test -x G/off\n-\t)\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 -p �/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/�/�/� &&\n+      echo Foo >�/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/�/�/�/g�rdets�g�rdet.txt &&\n+      git add �/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/�/�/�/g�rdets�g�rdet.txt &&\n+      cp ../test9200a.png �/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/�/�/�/g�rdets�g�rdet.png &&\n+      git add �/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/�/�/�/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 �/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/�/�/�/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+test_expect_success \\\n+     'Retain execute bit' \\\n+     'mkdir G &&\n+      echo executeon >G/on &&\n+      chmod +x G/on &&\n+      echo executeoff >G/off &&\n+      git add G/on &&\n+      git add G/off &&\n+      git commit -a -m \"Execute test\" &&\n+      (cd \"$CVSWORK\" &&\n+      git-cvsexportcommit -c HEAD\n+      test -x G/on &&\n+      ! test -x G/off\n+      )'\n \n"},{"id":"294851","messageId":"200612110048.47435.robin.rosenberg@dewire.com","threadId":"43314","inReplyTo":"7vlklf4f1s.fsf@assigned-by-dhcp.cox.net","subject":"Re: [PATCH] Make cvsexportcommit work with filenames with spaces and non-ascii characters.","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg@dewire.com","sentAt":"2006-12-10T23:48:46Z","receivedAt":"2006-12-10T23:48:46Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"söndag 10 december 2006 22:27 skrev Junio C Hamano:\n> Maybe I am missing something.  It tells you about added or\n> removed files (either binary or non-binary).\n\nno, I was missing --summary. I thought --numstat did that (man page \nsays \"similar to stat\".\n\n> I'd prepare a pair of practice patch files.  \"forward\" has\n> creation, \"reverse\" has addition.\nt9200 serves that purpose.\n\n"},{"id":"294826","messageId":"7vodq9zzga.fsf@assigned-by-dhcp.cox.net","threadId":"43314","inReplyTo":"200612110048.47435.robin.rosenberg@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-12-12T01:15:49Z","receivedAt":"2006-12-12T01:15:49Z","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> söndag 10 december 2006 22:27 skrev Junio C Hamano:\n>> Maybe I am missing something.  It tells you about added or\n>> removed files (either binary or non-binary).\n>\n> no, I was missing --summary. I thought --numstat did that (man page \n> says \"similar to stat\".\n\nAh, figures.\n\nYour patch removes more lines than it adds, which is quite nice.\n"}]}