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,$opt_f,$opt_m); 14 15getopts('hpvcfm:'); 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= safe_pipe_capture('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= safe_pipe_capture('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= safe_pipe_capture('git-cat-file','commit',$commit); 49my@parents; 50foreachmy$p(@commit) { 51if($p=~m/^$/) {# end of commit headers, we're done 52last; 53} 54if($p=~m/^parent (\w{40})$/) {# found a parent 55push@parents,$1; 56} 57} 58 59if($parent) { 60# double check that it's a valid parent 61foreachmy$p(@parents) { 62my$found; 63if($peq$parent) { 64$found=1; 65last; 66};# found it 67die"Did not find$parentin the parents for this commit!"; 68} 69}else{# we don't have a parent from the cmdline... 70if(@parents==1) {# it's safe to get it from the commit 71$parent=$parents[0]; 72}else{# or perhaps not! 73die"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 80open(MSG,">.msg")or die"Cannot open .msg for writing"; 81print MSG $opt_m; 82close MSG; 83 84`git-cat-file commit$commit| sed -e '1,/^\$/d' >> .msg`; 85$?&&die"Error extracting the commit message"; 86 87my(@afiles,@dfiles,@mfiles); 88my@files= safe_pipe_capture('git-diff-tree','-r',$parent,$commit); 89#print @files; 90$?&&die"Error in git-diff-tree"; 91foreachmy$f(@files) { 92chomp$f; 93my@fields=split(m!\s+!,$f); 94if($fields[4]eq'A') { 95push@afiles,$fields[5]; 96} 97if($fields[4]eq'M') { 98push@mfiles,$fields[5]; 99} 100if($fields[4]eq'R') { 101push@dfiles,$fields[5]; 102} 103} 104$opt_v&&print"The commit affects:\n"; 105$opt_v&&print join("\n",@afiles,@mfiles,@dfiles) ."\n\n"; 106undef@files;# don't need it anymore 107 108# check that the files are clean and up to date according to cvs 109my$dirty; 110foreachmy$f(@afiles) { 111# This should return only one value 112my@status=grep(m/^File/, safe_pipe_capture('cvs','-q','status',$f)); 113if(@status>1) {warn'Strange! cvs status returned more than one line?'}; 114unless($status[0] =~m/Status: Unknown$/) { 115$dirty=1; 116warn"File$fis 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"; 117} 118} 119foreachmy$f(@mfiles,@dfiles) { 120# TODO:we need to handle removed in cvs 121my@status=grep(m/^File/, safe_pipe_capture('cvs','-q','status',$f)); 122if(@status>1) {warn'Strange! cvs status returned more than one line?'}; 123unless($status[0] =~m/Status: Up-to-date$/) { 124$dirty=1; 125warn"File$fnot up to date in your CVS checkout!\n"; 126} 127} 128if($dirty) { 129if($opt_f) {warn"The tree is not clean -- forced merge\n"; 130$dirty=0; 131}else{ 132die"Exiting: your CVS tree is not clean for this merge."; 133} 134} 135 136### 137### NOTE: if you are planning to die() past this point 138### you MUST call cleanupcvs(@files) before die() 139### 140 141 142print"'Patching' binary files\n"; 143 144my@bfiles=grep(m/^Binary/, safe_pipe_capture('git-diff-tree','-p',$parent,$commit)); 145@bfiles=map{chomp}@bfiles; 146foreachmy$f(@bfiles) { 147# check that the file in cvs matches the "old" file 148# extract the file to $tmpdir and comparre with cmp 149my$tree= safe_pipe_capture('git-rev-parse',"$parent^{tree}"); 150chomp$tree; 151my$blob=`git-ls-tree$tree"$f" | cut -f 1 | cut -d ' ' -f 3`; 152chomp$blob; 153`git-cat-file blob$blob>$tmpdir/blob`; 154 if (system('cmp', '-q',$f, "$tmpdir/blob")) { 155 warn "Binary file$fin CVS does not match parent.\n"; 156$dirty= 1; 157 next; 158 } 159 160 # replace with the new file 161 `git-cat-file blob $blob>$f`; 162 163# TODO: something smart with file modes 164 165} 166if($dirty) { 167 cleanupcvs(@files); 168die"Exiting: Binary files in CVS do not match parent"; 169} 170 171## apply non-binary changes 172my$fuzz=$opt_p?0:2; 173 174print"Patching non-binary files\n"; 175print`(git-diff-tree -p$parent-p$commit| patch -p1 -F$fuzz) 2>&1`; 176 177my$dirtypatch=0; 178if(($?>>8) ==2) { 179 cleanupcvs(@files); 180die"Exiting: Patch reported serious trouble -- you will have to apply this patch manually"; 181}elsif(($?>>8) ==1) {# some hunks failed to apply 182$dirtypatch=1; 183} 184 185foreachmy$f(@afiles) { 186system('cvs','add',$f); 187if($?) { 188$dirty=1; 189warn"Failed to cvs add$f-- you may need to do it manually"; 190} 191} 192 193foreachmy$f(@dfiles) { 194system('cvs','rm','-f',$f); 195if($?) { 196$dirty=1; 197warn"Failed to cvs rm -f$f-- you may need to do it manually"; 198} 199} 200 201print"Commit to CVS\n"; 202my$commitfiles=join(' ',@afiles,@mfiles,@dfiles); 203my$cmd="cvs commit -F .msg$commitfiles"; 204 205if($dirtypatch) { 206print"NOTE: One or more hunks failed to apply cleanly.\n"; 207print"Resolve the conflicts and then commit using:\n"; 208print"\n$cmd\n\n"; 209exit(1); 210} 211 212 213if($opt_c) { 214print"Autocommit\n$cmd\n"; 215print safe_pipe_capture('cvs','commit','-F','.msg',@afiles,@mfiles,@dfiles); 216if($?) { 217 cleanupcvs(@files); 218die"Exiting: The commit did not succeed"; 219} 220print"Committed successfully to CVS\n"; 221}else{ 222print"Ready for you to commit, just run:\n\n$cmd\n"; 223} 224sub usage { 225print STDERR <<END; 226Usage: GIT_DIR=/path/to/.git ${\basename$0} [-h] [-p] [-v] [-c] [-f] [-m msgprefix] [ parent ] commit 227END 228exit(1); 229} 230 231# ensure cvs is clean before we die 232sub cleanupcvs { 233my@files=@_; 234foreachmy$f(@files) { 235system('cvs','-q','update','-C',$f); 236if($?) { 237warn"Warning! Failed to cleanup state of$f\n"; 238} 239} 240} 241 242# An alterative to `command` that allows input to be passed as an array 243# to work around shell problems with weird characters in arguments 244# if the exec returns non-zero we die 245sub safe_pipe_capture { 246my@output; 247if(my$pid=open my$child,'-|') { 248@output= (<$child>); 249close$childor die join(' ',@_).":$!$?"; 250}else{ 251exec(@_)or die"$!$?";# exec() can fail the executable can't be found 252} 253returnwantarray?@output:join('',@output); 254}