1#!/usr/bin/perl -w 2 3use strict; 4use Getopt::Std; 5use File::Temp qw(tempdir); 6use Data::Dumper; 7use File::Basename qw(basename); 8 9unless($ENV{GIT_DIR} && -r $ENV{GIT_DIR}){ 10die"GIT_DIR is not defined or is unreadable"; 11} 12 13our($opt_h,$opt_p,$opt_v,$opt_c); 14 15getopts('hpvc'); 16 17$opt_h&& usage(); 18 19die"Need at least one commit identifier!"unless@ARGV; 20 21# setup a tempdir 22our($tmpdir,$tmpdirname) = tempdir('git-cvsapplycommit-XXXXXX', 23 TMPDIR =>1, 24 CLEANUP =>1); 25 26print Dumper(@ARGV); 27# resolve target commit 28my$commit; 29$commit=pop@ARGV; 30$commit=`git-rev-parse --verify "$commit"^0`; 31chomp$commit; 32if($?) { 33die"The commit reference$commitdid not resolve!"; 34} 35 36# resolve what parent we want 37my$parent; 38if(@ARGV) { 39$parent=pop@ARGV; 40$parent=`git-rev-parse --verify "$parent"^0"`; 41chomp$parent; 42if($?) { 43die"The parent reference did not resolve!"; 44} 45} 46 47# find parents from the commit itself 48my@commit=`git-cat-file commit$commit`; 49my@parents; 50foreach my$p(@commit) { 51 if ($p=~ m/^$/) { # end of commit headers, we're done 52 last; 53 } 54 if ($p=~ m/^parent (\w{40})$/) { # found a parent 55 push@parents,$1; 56 } 57} 58 59if ($parent) { 60 # double check that it's a valid parent 61 foreach my$p(@parents) { 62 my$found; 63 if ($peq$parent) { 64$found= 1; 65 last; 66 }; # found it 67 die "Did not find$parentin the parents for this commit!"; 68 } 69} else { # we don't have a parent from the cmdline... 70 if (@parents== 1) { # it's safe to get it from the commit 71$parent=$parents[0]; 72 } else { # or perhaps not! 73 die "This commit has more than one parent -- please name the parent you want to use explicitly"; 74 } 75} 76 77$opt_v&& print "Applying to CVS commit$commitfrom parent$parent\n"; 78 79# grab the commit message 80`git-cat-file commit $commit| sed -e '1,/^\$/d'> .msg`; 81$?&& die "Error extracting the commit message"; 82 83my (@afiles,@dfiles,@mfiles); 84my@files= `git-diff-tree -r $parent $commit`; 85print@files; 86$?&&die"Error in git-diff-tree"; 87foreachmy$f(@files) { 88chomp$f; 89my@fields=split(m/\s+/,$f); 90if($fields[4]eq'A') { 91push@afiles,$fields[5]; 92} 93if($fields[4]eq'M') { 94push@mfiles,$fields[5]; 95} 96if($fields[4]eq'R') { 97push@dfiles,$fields[5]; 98} 99} 100$opt_v&&print"The commit affects:\n"; 101$opt_v&&print join("\n",@afiles,@mfiles,@dfiles) ."\n\n"; 102undef@files;# don't need it anymore 103 104# check that the files are clean and up to date according to cvs 105my$dirty; 106foreachmy$f(@afiles,@mfiles,@dfiles) { 107# TODO:we need to handle removed in cvs and/or new (from git) 108my$status=`cvs -q status "$f" | grep '^File: '`; 109 110unless($status=~m/Status: Up-to-date$/) { 111$dirty=1; 112warn"File$fnot up to date in your CVS checkout!\n"; 113} 114} 115if($dirty) { 116die"Exiting: your CVS tree is not clean for this merge."; 117} 118 119### 120### NOTE: if you are planning to die() past this point 121### you MUST call cleanupcvs(@files) before die() 122### 123 124 125print"'Patching' binary files\n"; 126 127my@bfiles=`git-diff-tree -p$parent$commit| grep '^Binary'`; 128@bfiles=map{chomp}@bfiles; 129foreachmy$f(@bfiles) { 130# check that the file in cvs matches the "old" file 131# extract the file to $tmpdir and comparre with cmp 132my$tree=`git-rev-parse$parent^{tree} `; 133chomp$tree; 134my$blob=`git-ls-tree$tree"$f" | cut -f 1 | cut -d ' ' -f 3`; 135chomp$blob; 136`git-cat-file blob$blob>$tmpdir/blob`; 137 `cmp-q $f $tmpdir/blob`; 138if($?) { 139warn"Binary file$fin CVS does not match parent.\n"; 140$dirty=1; 141next; 142} 143 144# replace with the new file 145`git-cat-file blob$blob>$f`; 146 147 # TODO: something smart with file modes 148 149} 150if ($dirty) { 151 cleanupcvs(@files); 152 die "Exiting: Binary files in CVS do not match parent"; 153} 154 155## apply non-binary changes 156my$fuzz=$opt_p? 0 : 2; 157 158print "Patching non-binary files\n"; 159print `(git-diff-tree -p $parent-p $commit| patch -p1 -F $fuzz)2>&1`; 160 161my$dirtypatch= 0; 162if (($?>> 8) == 2) { 163 cleanupcvs(@files); 164 die "Exiting: Patch reported serious trouble -- you will have to apply this patch manually"; 165} elsif (($?>> 8) == 1) { # some hunks failed to apply 166$dirtypatch= 1; 167} 168 169foreach my$f(@afiles) { 170 `cvs add $f`; 171if($?) { 172$dirty=1; 173warn"Failed to cvs add$f-- you may need to do it manually"; 174} 175} 176 177foreachmy$f(@dfiles) { 178`cvs rm -f$f`; 179 if ($?) { 180$dirty= 1; 181 warn "Failed to cvs rm -f$f-- you may need to do it manually"; 182 } 183} 184 185print "Commit to CVS\n"; 186my$commitfiles= join(' ',@afiles,@mfiles,@dfiles); 187my$cmd= "cvs commit -F .msg$commitfiles"; 188 189if ($dirtypatch) { 190 print "NOTE: One or more hunks failed to apply cleanly.\n"; 191 print "Resolve the conflicts and then commit using:\n"; 192 print "\n$cmd\n\n"; 193 exit(1); 194} 195 196 197if ($opt_c) { 198 print "Autocommit\n$cmd\n"; 199 print `cvs commit -F .msg $commitfiles2>&1`; 200 if ($?) { 201 cleanupcvs(@files); 202 die "Exiting: The commit did not succeed"; 203 } 204 print "Committed successfully to CVS\n"; 205} else { 206 print "Ready for you to commit, just run:\n\n$cmd\n"; 207} 208sub usage { 209 print STDERR <<END; 210Usage: GIT_DIR=/path/to/.git ${\basename$0} [-h] [-p] [-v] [-c] [ parent ] commit 211END 212exit(1); 213} 214 215# ensure cvs is clean before we die 216sub cleanupcvs { 217my@files=@_; 218foreachmy$f(@files) { 219`cvs -q update -C "$f"`; 220if($?) { 221warn"Warning! Failed to cleanup state of$f\n"; 222} 223} 224} 225