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= 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 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= safe_pipe_capture('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) { 107# This should return only one value 108my@status=grep(m/^File/, safe_pipe_capture('cvs','-q','status',$f)); 109if(@status>1) {warn'Strange! cvs status returned more than one line?'}; 110unless($status[0] =~m/Status: Unknown$/) { 111$dirty=1; 112warn"File$fis already known in your CVS checkout!\n"; 113} 114} 115foreachmy$f(@mfiles,@dfiles) { 116# TODO:we need to handle removed in cvs 117my@status=grep(m/^File/, safe_pipe_capture('cvs','-q','status',$f)); 118if(@status>1) {warn'Strange! cvs status returned more than one line?'}; 119unless($status[0] =~m/Status: Up-to-date$/) { 120$dirty=1; 121warn"File$fnot up to date in your CVS checkout!\n"; 122} 123} 124if($dirty) { 125die"Exiting: your CVS tree is not clean for this merge."; 126} 127 128### 129### NOTE: if you are planning to die() past this point 130### you MUST call cleanupcvs(@files) before die() 131### 132 133 134print"'Patching' binary files\n"; 135 136my@bfiles=grep(m/^Binary/, safe_pipe_capture('git-diff-tree','-p',$parent,$commit)); 137@bfiles=map{chomp}@bfiles; 138foreachmy$f(@bfiles) { 139# check that the file in cvs matches the "old" file 140# extract the file to $tmpdir and comparre with cmp 141my$tree= safe_pipe_capture('git-rev-parse',"$parent^{tree}"); 142chomp$tree; 143my$blob=`git-ls-tree$tree"$f" | cut -f 1 | cut -d ' ' -f 3`; 144chomp$blob; 145`git-cat-file blob$blob>$tmpdir/blob`; 146 if (system('cmp', '-q',$f, "$tmpdir/blob")) { 147 warn "Binary file$fin CVS does not match parent.\n"; 148$dirty= 1; 149 next; 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); 160die"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); 172die"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 177foreachmy$f(@afiles) { 178system('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) { 186system('cvs','rm','-f',$f); 187if($?) { 188$dirty=1; 189warn"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) { 198print"NOTE: One or more hunks failed to apply cleanly.\n"; 199print"Resolve the conflicts and then commit using:\n"; 200print"\n$cmd\n\n"; 201exit(1); 202} 203 204 205if($opt_c) { 206print"Autocommit\n$cmd\n"; 207print safe_pipe_capture('cvs','commit','-F','.msg',@afiles,@mfiles,@dfiles); 208if($?) { 209 cleanupcvs(@files); 210die"Exiting: The commit did not succeed"; 211} 212print"Committed successfully to CVS\n"; 213}else{ 214print"Ready for you to commit, just run:\n\n$cmd\n"; 215} 216sub usage { 217print 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) { 227system('cvs','-q','update','-C',$f); 228if($?) { 229warn"Warning! Failed to cleanup state of$f\n"; 230} 231} 232} 233 234# An alterative to `command` that allows input to be passed as an array 235# to work around shell problems with weird characters in arguments 236# if the exec returns non-zero we die 237sub safe_pipe_capture { 238my@output; 239if(my$pid=open my$child,'-|') { 240@output= (<$child>); 241close$childor die join(' ',@_).":$!$?"; 242}else{ 243exec(@_)or die"$!$?";# exec() can fail the executable can't be found 244} 245returnwantarray?@output:join('',@output); 246}