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