3 # git2svn, converts a git branch to a svn ditto
4 # Copyright (C) 2008 Love Hörnquist Åstrand
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 use POSIX
qw(strftime);
21 use Getopt
::Long qw
/:config gnu_getopt no_ignore_case auto_abbrev/;
30 my $svntree = "repro";
31 my $basedir = "trunk";
35 my $branch = "master";
47 my ($IN, $next, $length, $data, $l) = (shift, shift);
48 unless($next =~ m/^data (\d+)/) { die "missing data: $next" ; }
51 $l = read(IN
, $data, $length);
52 unless ($l == $length) { die "failed to read data $l != $length"; }
53 $data = "" if ($length == 0);
59 my ($key, $value) = (shift, shift);
60 "K " . length($key) . "\n$key\nV " . length($value) . "\n$value\n";
66 my ($SVN, $type, $name);
68 open(SVN
, "svn ls -R $url|") or die "failed to open svn ls -R $url";
72 $name = "$basedir/$1";
75 $name = "$basedir/$_";
77 $paths{$name} = $type;
81 open(SVN
, "svn info $url|") or die "failed to open svn info $url";
83 if (/^Revision: (\d+)/) {
93 $masterrev= `cat ${gittree}/.git/refs/heads/${branch}`;
96 die "No head found for ${branch}" if ($masterrev eq "");
98 my $syncpoint="${gittree}/.git/refs/tags/${syncname}";
100 if (-f
${syncpoint
}) {
101 my $oldmasterrev=`cat ${syncpoint}`;
102 chomp($oldmasterrev);
104 if (${oldmasterrev
} eq ${masterrev
}) {
105 print STDERR
"nothing to sync\n" if ($verbose);
109 die "no $svntree, but incremental (have synctag) ?\n".
110 "(\"cd $gittree && git tag -d $syncname\" to remove)"
111 unless ( -d
$svntree);
113 $fexport = "$oldmasterrev..$masterrev";
115 $fexport="${masterrev}";
117 system("svnadmin create ./$svntree") unless (-d
$svntree);
127 # pick first dir, create, take next dir, continue until we reached basename
128 while ($path =~ m@
^([^/]+)/(.*)$@
) {
129 my $first = $base . $1;
131 $base = $first . "/";
132 next if ($paths{$first});
136 printf OUT
"Node-path: $first\n";
137 printf OUT
"Node-kind: dir\n";
138 printf OUT
"Node-action: add\n";
139 printf OUT
"Prop-content-length: 0\n";
140 printf OUT
"Content-length: 0\n";
156 $result = GetOptions
("git-branch=s" => \
$branch,
157 "svn-prefix=s" => \
$basedir,
158 "verbose+" => \
$verbose,
159 "help" => \
$help) or pod2usage
(2);
161 pod2usage
(0) if ($help);
163 die "to few arguments" if ($#ARGV < 1);
165 mkdir ".data" unless (-d
".data");
167 $syncname = "git2svn-syncpoint-${branch}";
169 my $gitdump = ".data/git.dump-${branch}";
170 my $svndump = ".data/svn.dump-${branch}";
171 my $log = ".data/log-${branch}";
176 parse_git_tree
($gittree);
180 parse_svn_tree
("file://" . $cwd ."/". $svntree);
184 print STDERR
"git fast-export $branch ($fexport)\n" if ($verbose);
186 system("(cd $gittree && git fast-export $fexport) > $gitdump 2>$log") == 0 or
187 die "git fast-export: $!";
189 open IN
, "$gitdump" or
190 die "failed to open $gitdump";
192 open OUT
, ">$svndump" or
193 die "failed to open $svndump";
195 print STDERR
"creating svn dump from revision $revision...\n" if ($verbose);
197 print OUT
"SVN-fs-dump-format-version: 3\n";
199 my $next = next_line
();
200 COMMAND
: while (!eof(IN
)) {
203 $next = next_line
($IN);
205 } elsif ($next =~ /^commit (.*)/) {
209 $next = next_line
($IN);
210 if ($next =~ m/mark (.*)/) {
212 $next = next_line
($IN);
214 if ($next =~ m/author (.*)/) {
215 $commit{author
} = $1;
216 $next = next_line
($IN);
218 unless ($next =~ m/committer (.+) <([^>]+)> (\d+) \+(\d+)$/) {
219 die "missing comitter"
222 $commit{CommitterName
} = $1;
223 $commit{CommitterEmail
} = $2;
224 $commit{CommitterWhen
} = $3;
225 $commit{CommitterTZ
} = $4;
227 $next = next_line
($IN);
228 my $log = read_data
($IN, $next);
230 $next = next_line
($IN);
231 if ($next =~ m/from (.*)/) {
232 $next = next_line
($IN);
234 if ($next =~ m/merge (.*)/) {
235 $next = next_line
($IN);
239 strftime
("%Y-%m-%dT%H:%M:%S.000000Z",
240 gmtime($commit{CommitterWhen
}));
242 my $author = "(no author)";
243 if ($commit{CommitterEmail
} =~ m/([^@]+)/) {
246 $author = "git2svn-dump" if ($author eq "(no author)");
249 $props .= prop
("svn:author", $author);
250 $props .= prop
("svn:log", $log);
251 $props .= prop
("svn:date", $date);
252 $props .= "PROPS-END";
256 printf OUT
"Revision-number: $revision\n"; $revision++;
257 printf OUT
"Prop-content-length: ". length($props) . "\n";
258 printf OUT
"Content-length: " . length($props) . "\n";
260 print OUT
"$props\n";
263 if ($next =~ m/M (\d+) (\S+) (.*)$/) {
264 my ($mode, $dataref, $path) = (oct $1, $2, $3);
266 if ($dataref eq "inline") {
267 $next = next_line
($IN);
268 $content = read_data
($IN, $next);
270 die "Revision missing content ref $dataref"
271 unless(defined $blob{$dataref});
273 $content = $blob{$dataref};
274 # here we really want to delete $blob{$dataref},
275 # but it might be referenced in the future. To
276 # avoid keepig everything in memory for larger
277 # repositories this must be written out to disk
278 # and removed when done.
281 checkdirs
("$basedir/$path");
286 die "file was a dir" if ($paths{$path} != 2);
293 my $type = $mode & 0777000;
295 $kind = "file" if ($type == 0100000);
296 $kind = "symlink" if ($type == 0120000);
297 die "$type unknown" if ($kind eq "");
300 $props .= prop
("svn:executable", "on") if ($mode & 0111);
301 $props .= "PROPS-END" if ($props ne "");
303 my $plen = length($props);
304 my $clen = length($content);
306 printf OUT
"Node-path: $basedir/$path\n";
307 printf OUT
"Node-kind: $kind\n";
308 printf OUT
"Node-action: $action\n";
309 printf OUT
"Text-content-length: $clen\n";
310 printf OUT
"Content-length: " . ($clen + $plen) . "\n";
311 printf OUT
"Prop-content-length: $plen\n" if ($plen);
314 print OUT
"$props\n" if ($plen);
318 } elsif ($next =~ m/D (.*)/) {
321 die "deleting non existing object" unless ($paths{$path});
323 delete $paths{$path};
325 printf OUT
"Node-path: $basedir/$path\n";
326 printf OUT
"Node-action: delete\n";
329 } elsif ($next =~ m/^C (.*)/) {
331 } elsif ($next =~ m/^R (.*)/) {
333 } elsif ($next =~ m/^filedeleteall$/) {
334 die "file delete all ?";
338 $next = next_line
($IN);
341 } elsif ($next =~ /^tag .*/) {
342 } elsif ($next =~ /^reset .*/) {
343 } elsif ($next =~ /^blob/) {
344 $next = next_line
($IN);
345 if ($next =~ m/mark (.*)/) {
347 $next = next_line
($IN);
349 my $data = read_data
($IN, $next);
350 $blob{$mark} = $data if (defined $mark);
351 } elsif ($next =~ /^checkpoint .*/) {
352 } elsif ($next =~ /^progress (.*)/) {
353 print STDERR
"progress: $1\n" if ($verbose);
355 die "unknown command $next";
357 $next = next_line
($IN);
363 print STDERR
"(re-)setting sync-tag to new master\n" if ($verbose);
365 system("cd $gittree && ".
366 "git tag -m \"sync $(date)\" -a -f ${syncname} ${masterrev}");
368 print STDERR
"loading dump into svn\n" if ($verbose);
370 system("svnadmin load $svntree < $svndump >>$log 2>&1") == 0 or
373 unlink $svndump, $gitdump, $log;
382 git2svn - converts a git branch to a svn ditto
386 git2svn [options] git-repro svn-repro
394 The git branch to export. The default is branch is master.
398 The svn prefix where the branch is import. The default is trunk to
399 match the default GIT branch (master).
403 More verbose output, can be give more then once to increase the verbosity.
407 Print a brief help message and exits.
413 B<This program> will convert a git branch to a svn ditto, it also
414 support incremantal updates.