Subject: [PATCH] git-cvsexportcommit can't commit files which have been removed from CVS

From: Nick Woolley <[email-masked]>
Date: Fri, 29 May 2009 00:23:33 +0100

If a file X is removed from CVS, it goes into the Attic directory, and
CVS reports it as 'no file X' but with status 'Up-to-date'.  This is
misinterpreted an existing file when git-cvsexportcommit tries to commit a file
with the same name as one of these.  This patch attempts to correctly identify
these files, so that new files with the same name can be committed.

Added a test to t9200-git-cvsexportcommit.sh, which tests that we can
re-commit a removed filename which remains in CVS's attic. This adds a
file 'attic_gremlin' in CVS, then "removes" it, then tries to commit a
file with the same name from git.

Signed-off-by: Nick Woolley <[email-masked]>
---
 git-cvsexportcommit.perl       |   49 ++++++++++++++++++++++++++++-----------
 t/t9200-git-cvsexportcommit.sh |   18 ++++++++++++++
 2 files changed, 53 insertions(+), 14 deletions(-)

diff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl
index 6d9f0ef..e5e8ca9 100755
--- a/git-cvsexportcommit.perl
+++ b/git-cvsexportcommit.perl
@@ -225,7 +225,14 @@ if (@canstatusfiles) {
       foreach my $name (keys %todo) {
 	my $basename = basename($name);

-	$basename = "no file " . $basename if (exists($added{$basename}));
+	# CVS reports files which are "status"ed which don't exist
+	# in the current revision as "no file $basename", so we
+	# should anticipate that.  Totally unknown files will have
+	# a status "Unknown". However, if they exist in the attic
+	# their status will be "Up-to-date" (this means they were
+	# added once but have been removed).
+	$basename = "no file $basename" if $added{$basename};
+
 	$basename =~ s/^\s+//;
 	$basename =~ s/\s+$//;

@@ -233,31 +240,45 @@ if (@canstatusfiles) {
 	  $fullname{$basename} = $name;
 	  push (@canstatusfiles2, $name);
 	  delete($todo{$name});
-        }
+	}
       }
       my @cvsoutput;
       @cvsoutput = xargs_safe_pipe_capture([@cvs, 'status'], @canstatusfiles2);
       foreach my $l (@cvsoutput) {
-        chomp $l;
-        if ($l =~ /^File:\s+(.*\S)\s+Status: (.*)$/) {
-	  if (!exists($fullname{$1})) {
-	    print STDERR "Huh? Status reported for unexpected file '$1'\n";
-	  } else {
-	    $cvsstat{$fullname{$1}} = $2;
-	  }
-	}
+	chomp $l;
+	next unless
+	    my ($file, $status) = $l =~ /^File:\s+(.*\S)\s+Status: (.*)$/;
+
+	my $fullname = $fullname{$file};
+	print STDERR "Huh? Status '$status' reported for unexpected file '$file'\n"
+	    unless defined $fullname;
+
+	# This response means the file does not exist except in
+	# CVS's attic, so set the status accordingly
+	$status = "In-attic"
+	    if $file =~ /^no file /
+		&& $status eq 'Up-to-date';
+
+	$cvsstat{$fullname{$file}} = $status;
       }
     }
 }

-# ... validate new files,
+# ... Validate new files have the correct status
 foreach my $f (@afiles) {
-    if (defined ($cvsstat{$f}) and $cvsstat{$f} ne "Unknown") {
-	$dirty = 1;
+    next unless defined(my $stat = $cvsstat{$f});
+
+    # This means the file has never been seen before
+    next if $stat eq 'Unknown';
+
+    # This means the file has been seen before but was removed
+    next if $stat eq 'In-attic';
+
+    $dirty = 1;
 	warn "File $f is already known in your CVS checkout -- perhaps it has been
added by another user. Or this may indicate that it exists on a different
branch. If this is the case, use -f to force the merge.\n";
 	warn "Status was: $cvsstat{$f}\n";
-    }
 }
+
 # ... validate known files.
 foreach my $f (@files) {
     next if grep { $_ eq $f } @afiles;
diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh
index 56b7c06..ef1f8d2 100755
--- a/t/t9200-git-cvsexportcommit.sh
+++ b/t/t9200-git-cvsexportcommit.sh
@@ -317,4 +317,22 @@ test_expect_success 'use the same checkout for Git and CVS' '

 '

+test_expect_success 're-commit a removed filename which remains in CVS attic' '
+
+    (cd "$CVSWORK" &&
+     echo >attic_gremlin &&
+     cvs -Q add attic_gremlin &&
+     cvs -Q ci -m "added attic_gremlin" &&
+     rm attic_gremlin &&
+     cvs -Q rm attic_gremlin &&
+     cvs -Q ci -m "removed attic_gremlin") &&
+
+    echo > attic_gremlin &&
+    git add attic_gremlin &&
+    git commit -m "Added attic_gremlin" &&
+	git cvsexportcommit -w "$CVSWORK" -c HEAD &&
+    (cd "$CVSWORK"; cvs -Q update -d) &&
+    test -f "$CVSWORK/attic_gremlin"
+'
+
 test_done