Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 1 | #!/usr/bin/perl -w |
| 2 | |
Yann Dirson | 0d71b31 | 2006-05-27 18:39:33 +0200 | [diff] [blame] | 3 | # Known limitations: |
| 4 | # - cannot add or remove binary files |
Yann Dirson | 0d71b31 | 2006-05-27 18:39:33 +0200 | [diff] [blame] | 5 | # - does not propagate permissions |
| 6 | # - tells "ready for commit" even when things could not be completed |
| 7 | # (eg addition of a binary file) |
| 8 | |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 9 | use strict; |
| 10 | use Getopt::Std; |
| 11 | use File::Temp qw(tempdir); |
| 12 | use Data::Dumper; |
Yann Dirson | 3f0f756 | 2006-05-27 18:39:35 +0200 | [diff] [blame] | 13 | use File::Basename qw(basename dirname); |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 14 | |
| 15 | unless ($ENV{GIT_DIR} && -r $ENV{GIT_DIR}){ |
| 16 | die "GIT_DIR is not defined or is unreadable"; |
| 17 | } |
| 18 | |
Martin Langhoff | 1b91abe | 2006-07-18 14:22:49 +1200 | [diff] [blame] | 19 | our ($opt_h, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m ); |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 20 | |
Martin Langhoff | 1b91abe | 2006-07-18 14:22:49 +1200 | [diff] [blame] | 21 | getopts('hpvcfam:'); |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 22 | |
| 23 | $opt_h && usage(); |
| 24 | |
| 25 | die "Need at least one commit identifier!" unless @ARGV; |
| 26 | |
| 27 | # setup a tempdir |
| 28 | our ($tmpdir, $tmpdirname) = tempdir('git-cvsapplycommit-XXXXXX', |
| 29 | TMPDIR => 1, |
| 30 | CLEANUP => 1); |
| 31 | |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 32 | # resolve target commit |
| 33 | my $commit; |
| 34 | $commit = pop @ARGV; |
Martin Langhoff | d41df15 | 2006-01-30 19:12:12 +1300 | [diff] [blame] | 35 | $commit = safe_pipe_capture('git-rev-parse', '--verify', "$commit^0"); |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 36 | chomp $commit; |
| 37 | if ($?) { |
| 38 | die "The commit reference $commit did not resolve!"; |
| 39 | } |
| 40 | |
| 41 | # resolve what parent we want |
| 42 | my $parent; |
| 43 | if (@ARGV) { |
| 44 | $parent = pop @ARGV; |
Martin Langhoff | d41df15 | 2006-01-30 19:12:12 +1300 | [diff] [blame] | 45 | $parent = safe_pipe_capture('git-rev-parse', '--verify', "$parent^0"); |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 46 | chomp $parent; |
| 47 | if ($?) { |
| 48 | die "The parent reference did not resolve!"; |
| 49 | } |
| 50 | } |
| 51 | |
| 52 | # find parents from the commit itself |
Martin Langhoff | d41df15 | 2006-01-30 19:12:12 +1300 | [diff] [blame] | 53 | my @commit = safe_pipe_capture('git-cat-file', 'commit', $commit); |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 54 | my @parents; |
Martin Langhoff | 1b91abe | 2006-07-18 14:22:49 +1200 | [diff] [blame] | 55 | my $committer; |
| 56 | my $author; |
| 57 | my $stage = 'headers'; # headers, msg |
| 58 | my $title; |
| 59 | my $msg = ''; |
| 60 | |
| 61 | foreach my $line (@commit) { |
| 62 | chomp $line; |
| 63 | if ($stage eq 'headers' && $line eq '') { |
| 64 | $stage = 'msg'; |
| 65 | next; |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 66 | } |
Martin Langhoff | 1b91abe | 2006-07-18 14:22:49 +1200 | [diff] [blame] | 67 | |
| 68 | if ($stage eq 'headers') { |
| 69 | if ($line =~ m/^parent (\w{40})$/) { # found a parent |
| 70 | push @parents, $1; |
| 71 | } elsif ($line =~ m/^author (.+) \d+ \+\d+$/) { |
| 72 | $author = $1; |
| 73 | } elsif ($line =~ m/^committer (.+) \d+ \+\d+$/) { |
| 74 | $committer = $1; |
| 75 | } |
| 76 | } else { |
| 77 | $msg .= $line . "\n"; |
| 78 | unless ($title) { |
| 79 | $title = $line; |
| 80 | } |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 81 | } |
| 82 | } |
| 83 | |
| 84 | if ($parent) { |
Peter Baumann | 135a522 | 2006-07-07 12:55:41 +0200 | [diff] [blame] | 85 | my $found; |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 86 | # double check that it's a valid parent |
| 87 | foreach my $p (@parents) { |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 88 | if ($p eq $parent) { |
| 89 | $found = 1; |
| 90 | last; |
| 91 | }; # found it |
Alexander Litvinov | e09f5d7 | 2005-11-09 13:02:58 +0600 | [diff] [blame] | 92 | } |
Peter Baumann | 135a522 | 2006-07-07 12:55:41 +0200 | [diff] [blame] | 93 | die "Did not find $parent in the parents for this commit!" if !$found; |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 94 | } else { # we don't have a parent from the cmdline... |
| 95 | if (@parents == 1) { # it's safe to get it from the commit |
| 96 | $parent = $parents[0]; |
| 97 | } else { # or perhaps not! |
| 98 | die "This commit has more than one parent -- please name the parent you want to use explicitly"; |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | $opt_v && print "Applying to CVS commit $commit from parent $parent\n"; |
| 103 | |
| 104 | # grab the commit message |
Martin Langhoff | 992793c | 2006-04-26 12:26:16 +1200 | [diff] [blame] | 105 | open(MSG, ">.msg") or die "Cannot open .msg for writing"; |
Martin Langhoff | 1b91abe | 2006-07-18 14:22:49 +1200 | [diff] [blame] | 106 | if ($opt_m) { |
| 107 | print MSG $opt_m; |
| 108 | } |
| 109 | print MSG $msg; |
| 110 | if ($opt_a) { |
| 111 | print MSG "\n\nAuthor: $author\n"; |
| 112 | if ($author ne $committer) { |
| 113 | print MSG "Committer: $committer\n"; |
| 114 | } |
| 115 | } |
Martin Langhoff | 992793c | 2006-04-26 12:26:16 +1200 | [diff] [blame] | 116 | close MSG; |
| 117 | |
Yann Dirson | 3f0f756 | 2006-05-27 18:39:35 +0200 | [diff] [blame] | 118 | my (@afiles, @dfiles, @mfiles, @dirs); |
Martin Langhoff | d41df15 | 2006-01-30 19:12:12 +1300 | [diff] [blame] | 119 | my @files = safe_pipe_capture('git-diff-tree', '-r', $parent, $commit); |
Martin Langhoff | 992793c | 2006-04-26 12:26:16 +1200 | [diff] [blame] | 120 | #print @files; |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 121 | $? && die "Error in git-diff-tree"; |
| 122 | foreach my $f (@files) { |
| 123 | chomp $f; |
Martin Langhoff | d41df15 | 2006-01-30 19:12:12 +1300 | [diff] [blame] | 124 | my @fields = split(m!\s+!, $f); |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 125 | if ($fields[4] eq 'A') { |
Yann Dirson | 3f0f756 | 2006-05-27 18:39:35 +0200 | [diff] [blame] | 126 | my $path = $fields[5]; |
| 127 | push @afiles, $path; |
| 128 | # add any needed parent directories |
| 129 | $path = dirname $path; |
| 130 | while (!-d $path and ! grep { $_ eq $path } @dirs) { |
| 131 | unshift @dirs, $path; |
| 132 | $path = dirname $path; |
| 133 | } |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 134 | } |
| 135 | if ($fields[4] eq 'M') { |
| 136 | push @mfiles, $fields[5]; |
| 137 | } |
| 138 | if ($fields[4] eq 'R') { |
| 139 | push @dfiles, $fields[5]; |
| 140 | } |
| 141 | } |
| 142 | $opt_v && print "The commit affects:\n "; |
| 143 | $opt_v && print join ("\n ", @afiles,@mfiles,@dfiles) . "\n\n"; |
| 144 | undef @files; # don't need it anymore |
| 145 | |
| 146 | # check that the files are clean and up to date according to cvs |
| 147 | my $dirty; |
Yann Dirson | 3f0f756 | 2006-05-27 18:39:35 +0200 | [diff] [blame] | 148 | foreach my $d (@dirs) { |
| 149 | if (-e $d) { |
| 150 | $dirty = 1; |
| 151 | warn "$d exists and is not a directory!\n"; |
| 152 | } |
| 153 | } |
Yann Dirson | 576cfc8 | 2006-01-06 21:54:41 +0100 | [diff] [blame] | 154 | foreach my $f (@afiles) { |
Martin Langhoff | d41df15 | 2006-01-30 19:12:12 +1300 | [diff] [blame] | 155 | # This should return only one value |
| 156 | my @status = grep(m/^File/, safe_pipe_capture('cvs', '-q', 'status' ,$f)); |
| 157 | if (@status > 1) { warn 'Strange! cvs status returned more than one line?'}; |
Yann Dirson | 3f0f756 | 2006-05-27 18:39:35 +0200 | [diff] [blame] | 158 | if (-d dirname $f and $status[0] !~ m/Status: Unknown$/ |
| 159 | and $status[0] !~ m/^File: no file /) { |
Martin Langhoff | d41df15 | 2006-01-30 19:12:12 +1300 | [diff] [blame] | 160 | $dirty = 1; |
Martin Langhoff | 992793c | 2006-04-26 12:26:16 +1200 | [diff] [blame] | 161 | warn "File $f is 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"; |
Sven Verdoolaege | e968751 | 2006-06-17 19:46:40 +0200 | [diff] [blame] | 162 | warn "Status was: $status[0]\n"; |
Yann Dirson | 576cfc8 | 2006-01-06 21:54:41 +0100 | [diff] [blame] | 163 | } |
| 164 | } |
| 165 | foreach my $f (@mfiles, @dfiles) { |
| 166 | # TODO:we need to handle removed in cvs |
Martin Langhoff | d41df15 | 2006-01-30 19:12:12 +1300 | [diff] [blame] | 167 | my @status = grep(m/^File/, safe_pipe_capture('cvs', '-q', 'status' ,$f)); |
| 168 | if (@status > 1) { warn 'Strange! cvs status returned more than one line?'}; |
| 169 | unless ($status[0] =~ m/Status: Up-to-date$/) { |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 170 | $dirty = 1; |
| 171 | warn "File $f not up to date in your CVS checkout!\n"; |
| 172 | } |
| 173 | } |
| 174 | if ($dirty) { |
Martin Langhoff | 992793c | 2006-04-26 12:26:16 +1200 | [diff] [blame] | 175 | if ($opt_f) { warn "The tree is not clean -- forced merge\n"; |
| 176 | $dirty = 0; |
| 177 | } else { |
| 178 | die "Exiting: your CVS tree is not clean for this merge."; |
| 179 | } |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 180 | } |
| 181 | |
| 182 | ### |
| 183 | ### NOTE: if you are planning to die() past this point |
| 184 | ### you MUST call cleanupcvs(@files) before die() |
| 185 | ### |
| 186 | |
| 187 | |
Yann Dirson | 3f0f756 | 2006-05-27 18:39:35 +0200 | [diff] [blame] | 188 | print "Creating new directories\n"; |
| 189 | foreach my $d (@dirs) { |
| 190 | unless (mkdir $d) { |
| 191 | warn "Could not mkdir $d: $!"; |
| 192 | $dirty = 1; |
| 193 | } |
| 194 | `cvs add $d`; |
| 195 | if ($?) { |
| 196 | $dirty = 1; |
| 197 | warn "Failed to cvs add directory $d -- you may need to do it manually"; |
| 198 | } |
| 199 | } |
| 200 | |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 201 | print "'Patching' binary files\n"; |
| 202 | |
Martin Langhoff | d41df15 | 2006-01-30 19:12:12 +1300 | [diff] [blame] | 203 | my @bfiles = grep(m/^Binary/, safe_pipe_capture('git-diff-tree', '-p', $parent, $commit)); |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 204 | @bfiles = map { chomp } @bfiles; |
| 205 | foreach my $f (@bfiles) { |
| 206 | # check that the file in cvs matches the "old" file |
Pavel Roskin | 82e5a82 | 2006-07-10 01:50:18 -0400 | [diff] [blame] | 207 | # extract the file to $tmpdir and compare with cmp |
Martin Langhoff | d41df15 | 2006-01-30 19:12:12 +1300 | [diff] [blame] | 208 | my $tree = safe_pipe_capture('git-rev-parse', "$parent^{tree}"); |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 209 | chomp $tree; |
| 210 | my $blob = `git-ls-tree $tree "$f" | cut -f 1 | cut -d ' ' -f 3`; |
| 211 | chomp $blob; |
| 212 | `git-cat-file blob $blob > $tmpdir/blob`; |
Yann Dirson | ff4a9ce | 2006-05-27 18:39:31 +0200 | [diff] [blame] | 213 | if (system('cmp', '-s', $f, "$tmpdir/blob")) { |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 214 | warn "Binary file $f in CVS does not match parent.\n"; |
| 215 | $dirty = 1; |
| 216 | next; |
| 217 | } |
| 218 | |
| 219 | # replace with the new file |
| 220 | `git-cat-file blob $blob > $f`; |
| 221 | |
| 222 | # TODO: something smart with file modes |
| 223 | |
| 224 | } |
| 225 | if ($dirty) { |
| 226 | cleanupcvs(@files); |
| 227 | die "Exiting: Binary files in CVS do not match parent"; |
| 228 | } |
| 229 | |
| 230 | ## apply non-binary changes |
| 231 | my $fuzz = $opt_p ? 0 : 2; |
| 232 | |
| 233 | print "Patching non-binary files\n"; |
| 234 | print `(git-diff-tree -p $parent -p $commit | patch -p1 -F $fuzz ) 2>&1`; |
| 235 | |
| 236 | my $dirtypatch = 0; |
| 237 | if (($? >> 8) == 2) { |
| 238 | cleanupcvs(@files); |
| 239 | die "Exiting: Patch reported serious trouble -- you will have to apply this patch manually"; |
| 240 | } elsif (($? >> 8) == 1) { # some hunks failed to apply |
| 241 | $dirtypatch = 1; |
| 242 | } |
| 243 | |
| 244 | foreach my $f (@afiles) { |
Martin Langhoff | d41df15 | 2006-01-30 19:12:12 +1300 | [diff] [blame] | 245 | system('cvs', 'add', $f); |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 246 | if ($?) { |
| 247 | $dirty = 1; |
| 248 | warn "Failed to cvs add $f -- you may need to do it manually"; |
| 249 | } |
| 250 | } |
| 251 | |
| 252 | foreach my $f (@dfiles) { |
Martin Langhoff | d41df15 | 2006-01-30 19:12:12 +1300 | [diff] [blame] | 253 | system('cvs', 'rm', '-f', $f); |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 254 | if ($?) { |
| 255 | $dirty = 1; |
| 256 | warn "Failed to cvs rm -f $f -- you may need to do it manually"; |
| 257 | } |
| 258 | } |
| 259 | |
| 260 | print "Commit to CVS\n"; |
Martin Langhoff | 1b91abe | 2006-07-18 14:22:49 +1200 | [diff] [blame] | 261 | print "Patch: $title\n"; |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 262 | my $commitfiles = join(' ', @afiles, @mfiles, @dfiles); |
| 263 | my $cmd = "cvs commit -F .msg $commitfiles"; |
| 264 | |
| 265 | if ($dirtypatch) { |
| 266 | print "NOTE: One or more hunks failed to apply cleanly.\n"; |
Junio C Hamano | 27dedf0 | 2005-11-16 21:32:44 -0800 | [diff] [blame] | 267 | print "Resolve the conflicts and then commit using:\n"; |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 268 | print "\n $cmd\n\n"; |
Junio C Hamano | 27dedf0 | 2005-11-16 21:32:44 -0800 | [diff] [blame] | 269 | exit(1); |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 270 | } |
| 271 | |
| 272 | |
| 273 | if ($opt_c) { |
| 274 | print "Autocommit\n $cmd\n"; |
Martin Langhoff | d41df15 | 2006-01-30 19:12:12 +1300 | [diff] [blame] | 275 | print safe_pipe_capture('cvs', 'commit', '-F', '.msg', @afiles, @mfiles, @dfiles); |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 276 | if ($?) { |
| 277 | cleanupcvs(@files); |
| 278 | die "Exiting: The commit did not succeed"; |
| 279 | } |
| 280 | print "Committed successfully to CVS\n"; |
| 281 | } else { |
| 282 | print "Ready for you to commit, just run:\n\n $cmd\n"; |
| 283 | } |
| 284 | sub usage { |
| 285 | print STDERR <<END; |
Martin Langhoff | 992793c | 2006-04-26 12:26:16 +1200 | [diff] [blame] | 286 | Usage: GIT_DIR=/path/to/.git ${\basename $0} [-h] [-p] [-v] [-c] [-f] [-m msgprefix] [ parent ] commit |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 287 | END |
| 288 | exit(1); |
| 289 | } |
| 290 | |
| 291 | # ensure cvs is clean before we die |
| 292 | sub cleanupcvs { |
| 293 | my @files = @_; |
| 294 | foreach my $f (@files) { |
Martin Langhoff | d41df15 | 2006-01-30 19:12:12 +1300 | [diff] [blame] | 295 | system('cvs', '-q', 'update', '-C', $f); |
Martin Langhoff | 5e0306a | 2005-11-07 17:57:08 +1300 | [diff] [blame] | 296 | if ($?) { |
| 297 | warn "Warning! Failed to cleanup state of $f\n"; |
| 298 | } |
| 299 | } |
| 300 | } |
| 301 | |
Pavel Roskin | 82e5a82 | 2006-07-10 01:50:18 -0400 | [diff] [blame] | 302 | # An alternative to `command` that allows input to be passed as an array |
Martin Langhoff | d41df15 | 2006-01-30 19:12:12 +1300 | [diff] [blame] | 303 | # to work around shell problems with weird characters in arguments |
| 304 | # if the exec returns non-zero we die |
| 305 | sub safe_pipe_capture { |
| 306 | my @output; |
| 307 | if (my $pid = open my $child, '-|') { |
| 308 | @output = (<$child>); |
| 309 | close $child or die join(' ',@_).": $! $?"; |
| 310 | } else { |
| 311 | exec(@_) or die "$! $?"; # exec() can fail the executable can't be found |
| 312 | } |
| 313 | return wantarray ? @output : join('',@output); |
| 314 | } |