Skip to content

Commit 5e0306a

Browse files
Martin LanghoffJunio C Hamano
authored andcommitted
Introducing: git-cvsexportcommit
A script that can replay commits git into a CVS checkout. Tries to ensure the sanity of the operation and supports mainly manual usage. If you are reckless enough, you can ask it to autocommit when everything has applied cleanly. Combined with a couple more scripts could become part of a git2cvs gateway. Should support adds/removes and binary files. Signed-off-by: Martin Langhoff <martin@catalyst.net.nz> Signed-off-by: Junio C Hamano <junkio@cox.net>
1 parent e66ab03 commit 5e0306a

File tree

3 files changed

+282
-1
lines changed

3 files changed

+282
-1
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
git-cvsexportcommit(1)
2+
================
3+
4+
NAME
5+
----
6+
git-cvsexportcommit - Export a commit to a CVS checkout
7+
8+
9+
SYNOPSIS
10+
--------
11+
git-cvsapplycommmit.perl
12+
[ -h ] [ -v ] [ -c ] [ -p ] [PARENTCOMMIT] COMMITID
13+
14+
15+
DESCRIPTION
16+
-----------
17+
Exports a commit from GIT to a CVS checkout, making it easier
18+
to merge patches from a git repository into a CVS repository.
19+
20+
Execute it from the root of the CVS working copy. GIT_DIR must be defined.
21+
22+
It does its best to do the safe thing, it will check that the files are
23+
unchanged and up to date in the CVS checkout, and it will not autocommit
24+
by default.
25+
26+
Supports file additions, removals, and commits that affect binary files.
27+
28+
If the commit is a merge commit, you must tell git-cvsapplycommit what parent
29+
should the changeset be done against.
30+
31+
OPTIONS
32+
-------
33+
34+
-c::
35+
Commit automatically if the patch applied cleanly. It will not
36+
commit if any hunks fail to apply or there were other problems.
37+
38+
-p::
39+
Be pedantic (paranoid) when applying patches. Invokes patch with
40+
--fuzz=0
41+
42+
-v::
43+
Verbose.
44+
45+
Author
46+
------
47+
Written by Martin Langhoff <martin@catalyst.net.nz>
48+
49+
Documentation
50+
--------------
51+
Documentation by Martin Langhoff <martin@catalyst.net.nz>
52+
53+
GIT
54+
---
55+
Part of the gitlink:git[7] suite
56+

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ SCRIPT_SH = \
9494
SCRIPT_PERL = \
9595
git-archimport.perl git-cvsimport.perl git-relink.perl \
9696
git-rename.perl git-shortlog.perl git-fmt-merge-msg.perl \
97-
git-svnimport.perl git-mv.perl
97+
git-svnimport.perl git-mv.perl git-cvsexportcommit.perl
9898

9999
SCRIPT_PYTHON = \
100100
git-merge-recursive.py

git-cvsexportcommit.perl

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
#!/usr/bin/perl -w
2+
3+
use strict;
4+
use Getopt::Std;
5+
use File::Temp qw(tempdir);
6+
use Data::Dumper;
7+
8+
unless ($ENV{GIT_DIR} && -r $ENV{GIT_DIR}){
9+
die "GIT_DIR is not defined or is unreadable";
10+
}
11+
12+
our ($opt_h, $opt_p, $opt_v, $opt_c );
13+
14+
getopt('hpvc');
15+
16+
$opt_h && usage();
17+
18+
die "Need at least one commit identifier!" unless @ARGV;
19+
20+
# setup a tempdir
21+
our ($tmpdir, $tmpdirname) = tempdir('git-cvsapplycommit-XXXXXX',
22+
TMPDIR => 1,
23+
CLEANUP => 1);
24+
25+
print Dumper(@ARGV);
26+
# resolve target commit
27+
my $commit;
28+
$commit = pop @ARGV;
29+
$commit = `git-rev-parse --verify "$commit"^0`;
30+
chomp $commit;
31+
if ($?) {
32+
die "The commit reference $commit did not resolve!";
33+
}
34+
35+
# resolve what parent we want
36+
my $parent;
37+
if (@ARGV) {
38+
$parent = pop @ARGV;
39+
$parent = `git-rev-parse --verify "$parent"^0"`;
40+
chomp $parent;
41+
if ($?) {
42+
die "The parent reference did not resolve!";
43+
}
44+
}
45+
46+
# find parents from the commit itself
47+
my @commit = `git-cat-file commit $commit`;
48+
my @parents;
49+
foreach 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+
58+
if ($parent) {
59+
# double check that it's a valid parent
60+
foreach my $p (@parents) {
61+
my $found;
62+
if ($p eq $parent) {
63+
$found = 1;
64+
last;
65+
}; # found it
66+
die "Did not find $parent in the parents for this commit!";
67+
s }
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 $commit from 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+
82+
my (@afiles, @dfiles, @mfiles);
83+
my @files = `git-diff-tree -r $parent $commit`;
84+
print @files;
85+
$? && die "Error in git-diff-tree";
86+
foreach my $f (@files) {
87+
chomp $f;
88+
my @fields = split(m/\s+/, $f);
89+
if ($fields[4] eq 'A') {
90+
push @afiles, $fields[5];
91+
}
92+
if ($fields[4] eq 'M') {
93+
push @mfiles, $fields[5];
94+
}
95+
if ($fields[4] eq 'R') {
96+
push @dfiles, $fields[5];
97+
}
98+
}
99+
$opt_v && print "The commit affects:\n ";
100+
$opt_v && print join ("\n ", @afiles,@mfiles,@dfiles) . "\n\n";
101+
undef @files; # don't need it anymore
102+
103+
# check that the files are clean and up to date according to cvs
104+
my $dirty;
105+
foreach my $f (@afiles, @mfiles, @dfiles) {
106+
# TODO:we need to handle removed in cvs and/or new (from git)
107+
my $status = `cvs -q status "$f" | grep '^File: '`;
108+
109+
unless ($status =~ m/Status: Up-to-date$/) {
110+
$dirty = 1;
111+
warn "File $f not up to date in your CVS checkout!\n";
112+
}
113+
}
114+
if ($dirty) {
115+
die "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+
124+
print "'Patching' binary files\n";
125+
126+
my @bfiles = `git-diff-tree -p $parent $commit | grep '^Binary'`;
127+
@bfiles = map { chomp } @bfiles;
128+
foreach my $f (@bfiles) {
129+
# check that the file in cvs matches the "old" file
130+
# extract the file to $tmpdir and comparre with cmp
131+
my $tree = `git-rev-parse $parent^{tree} `;
132+
chomp $tree;
133+
my $blob = `git-ls-tree $tree "$f" | cut -f 1 | cut -d ' ' -f 3`;
134+
chomp $blob;
135+
`git-cat-file blob $blob > $tmpdir/blob`;
136+
`cmp -q $f $tmpdir/blob`;
137+
if ($?) {
138+
warn "Binary file $f in CVS does not match parent.\n";
139+
$dirty = 1;
140+
next;
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+
}
149+
if ($dirty) {
150+
cleanupcvs(@files);
151+
die "Exiting: Binary files in CVS do not match parent";
152+
}
153+
154+
## apply non-binary changes
155+
my $fuzz = $opt_p ? 0 : 2;
156+
157+
print "Patching non-binary files\n";
158+
print `(git-diff-tree -p $parent -p $commit | patch -p1 -F $fuzz ) 2>&1`;
159+
160+
my $dirtypatch = 0;
161+
if (($? >> 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+
168+
foreach my $f (@afiles) {
169+
`cvs add $f`;
170+
if ($?) {
171+
$dirty = 1;
172+
warn "Failed to cvs add $f -- you may need to do it manually";
173+
}
174+
}
175+
176+
foreach my $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+
184+
print "Commit to CVS\n";
185+
my $commitfiles = join(' ', @afiles, @mfiles, @dfiles);
186+
my $cmd = "cvs commit -F .msg $commitfiles";
187+
188+
if ($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+
196+
if ($opt_c) {
197+
print "Autocommit\n $cmd\n";
198+
print `cvs commit -F .msg $commitfiles 2>&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+
}
207+
sub usage {
208+
print STDERR <<END;
209+
Usage: GIT_DIR=/path/to/.gi ${\basename $0} # fetch/update GIT from CVS
210+
[-h] [-p] [ parent ] commit
211+
END
212+
exit(1);
213+
}
214+
215+
# ensure cvs is clean before we die
216+
sub cleanupcvs {
217+
my @files = @_;
218+
foreach my $f (@files) {
219+
`cvs -q update -C "$f"`;
220+
if ($?) {
221+
warn "Warning! Failed to cleanup state of $f\n";
222+
}
223+
}
224+
}
225+

0 commit comments

Comments
 (0)