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) { 107my$status=`cvs -q status "$f" | grep '^File: '`; 108 109unless($status=~m/Status: Unknown$/) { 110$dirty=1; 111warn"File$fis already known in your CVS checkout!\n"; 112} 113} 114foreachmy$f(@mfiles,@dfiles) { 115# TODO:we need to handle removed in cvs 116my$status=`cvs -q status "$f" | grep '^File: '`; 117 118unless($status=~m/Status: Up-to-date$/) { 119$dirty=1; 120warn"File$fnot up to date in your CVS checkout!\n"; 121} 122} 123if($dirty) { 124die"Exiting: your CVS tree is not clean for this merge."; 125} 126 127### 128### NOTE: if you are planning to die() past this point 129### you MUST call cleanupcvs(@files) before die() 130### 131 132 133print"'Patching' binary files\n"; 134 135my@bfiles=`git-diff-tree -p$parent$commit| grep '^Binary'`; 136@bfiles=map{chomp}@bfiles; 137foreachmy$f(@bfiles) { 138# check that the file in cvs matches the "old" file 139# extract the file to $tmpdir and comparre with cmp 140my$tree=`git-rev-parse$parent^{tree} `; 141chomp$tree; 142my$blob=`git-ls-tree$tree"$f" | cut -f 1 | cut -d ' ' -f 3`; 143chomp$blob; 144`git-cat-file blob$blob>$tmpdir/blob`; 145 `cmp-q $f $tmpdir/blob`; 146if($?) { 147warn"Binary file$fin CVS does not match parent.\n"; 148$dirty=1; 149next; 150} 151 152# replace with the new file 153`git-cat-file blob$blob>$f`; 154 155 # TODO: something smart with file modes 156 157} 158if ($dirty) { 159 cleanupcvs(@files); 160 die "Exiting: Binary files in CVS do not match parent"; 161} 162 163## apply non-binary changes 164my$fuzz=$opt_p? 0 : 2; 165 166print "Patching non-binary files\n"; 167print `(git-diff-tree -p $parent-p $commit| patch -p1 -F $fuzz)2>&1`; 168 169my$dirtypatch= 0; 170if (($?>> 8) == 2) { 171 cleanupcvs(@files); 172 die "Exiting: Patch reported serious trouble -- you will have to apply this patch manually"; 173} elsif (($?>> 8) == 1) { # some hunks failed to apply 174$dirtypatch= 1; 175} 176 177foreach my$f(@afiles) { 178 `cvs add $f`; 179if($?) { 180$dirty=1; 181warn"Failed to cvs add$f-- you may need to do it manually"; 182} 183} 184 185foreachmy$f(@dfiles) { 186`cvs rm -f$f`; 187 if ($?) { 188$dirty= 1; 189 warn "Failed to cvs rm -f$f-- you may need to do it manually"; 190 } 191} 192 193print "Commit to CVS\n"; 194my$commitfiles= join(' ',@afiles,@mfiles,@dfiles); 195my$cmd= "cvs commit -F .msg$commitfiles"; 196 197if ($dirtypatch) { 198 print "NOTE: One or more hunks failed to apply cleanly.\n"; 199 print "Resolve the conflicts and then commit using:\n"; 200 print "\n$cmd\n\n"; 201 exit(1); 202} 203 204 205if ($opt_c) { 206 print "Autocommit\n$cmd\n"; 207 print `cvs commit -F .msg $commitfiles2>&1`; 208 if ($?) { 209 cleanupcvs(@files); 210 die "Exiting: The commit did not succeed"; 211 } 212 print "Committed successfully to CVS\n"; 213} else { 214 print "Ready for you to commit, just run:\n\n$cmd\n"; 215} 216sub usage { 217 print STDERR <<END; 218Usage: GIT_DIR=/path/to/.git ${\basename$0} [-h] [-p] [-v] [-c] [ parent ] commit 219END 220exit(1); 221} 222 223# ensure cvs is clean before we die 224sub cleanupcvs { 225my@files=@_; 226foreachmy$f(@files) { 227`cvs -q update -C "$f"`; 228if($?) { 229warn"Warning! Failed to cleanup state of$f\n"; 230} 231} 232} 233