source: openpam/trunk/misc/p42svn.pl

Last change on this file was 648, checked in by des, 22 months ago

prop sweep

  • Property svn:eol-style set to native
File size: 25.2 KB
Line 
1#!/usr/bin/perl
2
3=pod
4
5=head1 NAME
6
7B<p42svn> - dump Perforce repository in Subversion portable dump/load format.
8
9=head1 SYNOPSIS
10
11B<p42svn> [I<options>] [B<--branch> I<p4_branch_spec=svn_path>] ...
12
13=head1 OPTIONS
14
15=over 8
16
17=item B<--help>
18
19Print detailed help message and exit.
20
21=item B<--usage>
22
23Print brief usage message and exit.
24
25=item B<--debug>
26
27Print debug messages to STDERR.
28
29=item B<--branch> I<p4_depot_spec=svn_path>
30
31Specify mapping of Perforce branch to repository path.  Takes an
32argument of the form p4_depot_spec=svn_path.  Multiple branch mappings
33may be specified, but at least one is required.
34
35=item B<--munge-keywords|--nomunge-keywords>
36
37Do/don't convert Perforce keywords to their Subversion equivalent.
38The default is to perform keyword conversion.
39
40=item B<--parse-mime-types|--noparse-mime-types>
41
42Do/don't attempt to parse content MIME type and add svn:mime-type
43property.  Default is not to parse MIME types.
44
45=item B<--mime-magic-path> I<path>
46
47Specify path of MIME magic file, overriding the default
48F</usr/share/misc/magic.mime>.  Ignored unless B<--parse-mime-types>
49is true.
50
51=item B<--delete-empty-dirs|--nodelete-empty-dirs>
52
53Do/don't delete the parent directory when the last file/directory it
54contains is deleted.  Default is to delete empty directories.
55
56=item B<--user> I<name>
57
58Specify Perforce username; this overrides $P4USER, $USER, and
59$USERNAME in the environment.
60
61=item B<--client> I<name>
62
63Specify Perforce client; this overrides $P4CLIENT in the environment
64and the default, the hostname.
65
66=item B<--port> I<[host:]port>
67
68Specify Perforce server and port; this overrides $P4PORT in the
69environment and the default, perforce:1666.
70
71=item B<--password> I<token>
72
73Specify Perforce password; this overrides $P4PASSWD in the
74environment.
75
76=back
77
78=head1 DESCRIPTION
79
80B<p42svn> connects to a Perforce server and examines changes affecting
81the specified repository branch(es).  Records reflecting each change
82are written to STDOUT in Subversion portable dump/load format.  Each
83Perforce change corresponds to a single Subversion revision.  Changes
84to files outside the specified Perforce branch(es) are ignored.
85
86Migration of a Perforce depot to Subversion can thus be achieved in
87two easy steps:
88
89=over 4
90
91=item C<svnadmin create /path/to/repository>
92
93=item C<p42svn --branch //depot/projectA=trunk/projectA | svnadmin load /path/to/repository>
94
95=back
96
97It is also possible to specify multiple branch mappings to change the
98repository layout when migrating, for example:
99
100=over 4
101
102=item C<p42svn --branch //depot/projectA/devel=projectA/trunk --branch
103//depot/projectA/release-1.0=projectA/tags/release1.0>
104
105=back
106
107=head1 REQUIREMENTS
108
109This program requires the Perforce Perl API, which is available for
110download from
111E<lt>http://public.perforce.com/guest/tony_smith/perforce/API/Perl/index.htmlE<gt>.
112It has been tested against version 1.2587 of the P4 module built
113against r02.2 of the Perforce API.
114
115Also tested by Dimitri Papadopoulos-Orfanos against P4 version 3.4804 built
116against r05.2 of the Perforce API.
117
118=head1 VERSION
119
120This is version 0.16.
121
122=head1 AUTHOR
123
124Ray Miller E<lt>ray@sysdev.oucs.ox.ac.ukE<gt>.
125
126=head1 BUGS
127
128Please report any bugs to the author.
129
130Accuracy of deretmined MIME types is dependent on your systems's MIME
131magic data.  This program defaults to using data in
132F</usr/share/misc/magic.mime> which, on a Debian woody system, does
133not give very good results.
134
135=head1 COPYRIGHT
136
137Copyright (C) 2003 University of Oxford
138
139This program is free software; you can redistribute it and/or modify
140it under the terms of the GNU General Public License as published by
141the Free Software Foundation; either version 2 of the License, or
142(at your option) any later version.
143
144This program is distributed in the hope that it will be useful,
145but WITHOUT ANY WARRANTY; without even the implied warranty of
146MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
147GNU General Public License for more details.
148
149You should have received a copy of the GNU General Public License
150along with this program; if not, write to the Free Software
151Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
152
153=cut
154
155use strict;
156use warnings;
157
158use P4;
159use Data::Dumper;
160use Date::Format;
161use Digest::MD5 qw(md5_hex);
162use File::MMagic;
163use Getopt::Long;
164use Pod::Usage;
165
166use constant MIME_MAGIC_PATH => "/usr/share/misc/magic.mime";
167use constant SVN_FS_DUMP_FORMAT_VERSION => 1;
168use constant SVN_DATE_TEMPLATE => "%Y-%m-%dT%T.000000Z";
169
170our (%rev_map, %dir_seen, %dir_usage, @deleted_files);
171
172our %KEYWORD_MAP = ("Author"   => "LastChangedBy",
173                    "Date"     => "LastChangedDate",
174                    "Revision" => "LastChangedRevision",
175                    "File"     => "HeadURL",
176                    "Id"       => "Id");
177
178use constant OPT_SPEC => qw(help usage debug  branch=s%
179                            delete-empty-dirs!
180                            munge-keywords! parse-mime-types! mime-magic-path=s
181                            user=s client=s port=s password=s);
182
183our %options = ("help"              => 0,
184                "usage"             => 0,
185                "debug"             => 0,
186                "delete-empty-dirs" => 1,
187                "munge-keywords"    => 1,
188                "parse-mime-types"  => 0,
189                "mime-magic-path"   => MIME_MAGIC_PATH,
190                "branch"            => {});
191
192########################################################################
193# Print debugging messages when debug option is set.
194########################################################################
195
196sub debug {
197    return unless $options{debug};
198    print STDERR @_;
199}
200
201########################################################################
202# Helper routines for option validation.
203########################################################################
204
205sub is_valid_depot {
206    my $depot = shift;
207    return $depot =~ m{^//([^/]+/?)*$};
208}
209
210sub is_valid_svnpath {
211    my $path = shift;
212    return $path =~ m{^/?([^/]+/?)*$};
213}
214
215########################################################################
216# Process command-line options.
217########################################################################
218sub process_options {
219
220    GetOptions(\%options, OPT_SPEC) and @ARGV == 0
221        or pod2usage(-exitval => 2, -verbose => 1);
222    pod2usage(-exitval => 1, -verbose => 2)
223        if $options{"help"};
224    pod2usage(-exitval => 1, -verbose => 1)
225        if $options{"usage"};
226    pod2usage(-exitval => 2, -verbose => 0,
227              -message => "Must specify at least one branch")
228        unless keys %{$options{branch}};
229
230    # Validate and sanitize branch specifications
231    while (my ($key, $val) = each %{$options{branch}}) {
232        pod2usage(-exitval => 2, -verbose => 0,
233                  -message => "Invalid Perforce depot specification: \"$key\"")
234          unless is_valid_depot($key);
235        pod2usage(-exitval => 2, -verbose => 0,
236                  -message => "Invalid Subversion repository path \"$val\"")
237          unless is_valid_svnpath($val);
238        if ($val =~ m{.+[^/]$}) {
239            $options{branch}{$key} .= "/";
240        }
241        if ($key =~ m{[^/]$}) {
242            $options{branch}{"$key/"} = $options{branch}{$key};
243            delete $options{branch}{$key};
244        }
245        debug("process_options: branch $key => $val\n");
246    }
247}
248
249########################################################################
250# Does Perforce file lie in a branch we're processing?
251########################################################################
252
253sub is_wanted_file {
254    my $filespec = shift;
255    debug("is_wanted_file: $filespec\n");
256    foreach (keys %{$options{branch}}) {
257        debug("is_wanted_file: considering $_\n");
258        return 1 if $filespec =~ /^$_/;
259    }
260    debug("is_wanted_file: ignoring $filespec\n");
261    return 0;
262}
263
264########################################################################
265# Map Perforce depot spec to Subversion path.
266########################################################################
267
268sub depot2svnpath {
269    my $depot = shift;
270    my $branches = $options{branch};
271    my $key = undef;
272    foreach (sort {length($a) <=> length($b)} keys %$branches) {
273        next unless $depot =~ /^$_/;
274        $key = $_;
275    }
276    return undef unless $key;
277    (my $svnpath = $depot) =~ s/^$key/$branches->{$key}/;
278    debug("depot2svnpath: $depot => $svnpath\n");
279    return $svnpath;
280}
281
282########################################################################
283# Helper routines for Perforce file types.
284########################################################################
285
286sub p4_has_keyword_expansion {
287    my $type = shift;
288    for (qw(ktext kxtext)) {
289        return 1 if $type eq $_;
290    }
291    return 0;
292}
293
294sub p4_has_executable_flag {
295    my $type = shift;
296    for (qw(cxtext kxtext uxbinary xbinary xltext xtempobj xtext xunicode)) {
297        return 1 if $type eq $_;
298    }
299    return 0;
300}
301
302########################################################################
303# Return property list based on Perforce file type and (optionally)
304# content MIME type.
305########################################################################
306
307my $mmagic;
308
309sub properties {
310    my ($type, $content_ref) = @_;
311    my @properties;
312    if (p4_has_keyword_expansion($type)) {
313        push @properties, "svn:keywords" => join(" ", values %KEYWORD_MAP);
314    }
315    if (p4_has_executable_flag($type)) {
316        push @properties, "svn:executable" => 1;
317    }
318    if ($options{"parse-mime-types"}) {
319        unless ($mmagic) {
320            $mmagic = File::MMagic->new($options{"mime-magic-path"})
321              or die "Unable to open MIME magic file "
322                . $options{"mime-magic-path"} . $!;
323        }
324        my $mtype = $mmagic->checktype_contents($$content_ref);
325        push(@properties, "svn:mime-type" => $mtype) if $mtype;
326    }
327    return \@properties;
328}
329
330########################################################################
331# Replace Perforce keywords in file content with equivalent Subversion
332# keywords.
333########################################################################
334
335sub munge_keywords {
336    return unless $options{"munge-keywords"};
337    my $content_ref = shift;
338    while (my ($key, $val) = each %KEYWORD_MAP) {
339        $$content_ref =~ s/\$$key(?\:[^\$\n]*)?\$(\W)/\$$val\$$1/g;
340    }
341}
342
343########################################################################
344# Return parent directories of a path
345########################################################################
346
347sub parent_directories {
348    my $path = shift;
349    my @components;
350    my $offset = 0;
351    while ((my $ix = index($path, "/", $offset)) >= 0) {
352        $offset = $ix + 1;
353        push @components, substr($path, 0, $offset);
354    }
355    return @components;
356}
357
358########################################################################
359# Return parent directory of a path
360########################################################################
361
362sub parent_directory {
363    my $path = shift;
364    (my $parent_dir = $path) =~ s|[^/]+/?$||;
365    return $parent_dir;
366}
367
368########################################################################
369# Convert Subversion property list to string.
370########################################################################
371
372sub svn_props2string {
373    my $properties = shift;
374    my $result;
375    if (defined $properties) {
376        while (my ($key, $val) = splice(@$properties, 0, 2)) {
377            $result .= sprintf("K %d\n%s\n", length($key), $key);
378            $result .= sprintf("V %d\n%s\n", length($val), $val);
379        }
380    }
381    $result .= "PROPS-END";
382    return $result;
383}
384
385########################################################################
386# Routines to print Subversion dump/restore records.
387########################################################################
388
389sub svn_dump_format_version {
390    my ($version) = @_;
391    print "SVN-fs-dump-format-version: $version\n\n";
392}
393
394sub svn_revision {
395    my ($revision, $properties) = @_;
396    my $ppty_txt = svn_props2string($properties);
397    my $ppty_len = length($ppty_txt) + 1;
398    print <<"EOT";
399Revision-number: $revision
400Prop-content-length: $ppty_len
401Content-length: $ppty_len
402
403$ppty_txt
404
405EOT
406}
407
408sub svn_add_dir {
409    my ($path, $properties) = @_;
410    $dir_usage{parent_directory($path)}++;
411    my $ppty_txt = svn_props2string($properties);
412    my $ppty_len = length($ppty_txt) + 1;
413    print <<"EOT";
414Node-path: $path
415Node-kind: dir
416Node-action: add
417Prop-content-length: $ppty_len
418Content-length: $ppty_len
419
420$ppty_txt
421
422EOT
423}
424
425sub svn_add_file {
426    my ($path, $properties, $text) = @_;
427    $dir_usage{parent_directory($path)}++;
428    my $ppty_txt = svn_props2string($properties);
429    my $ppty_len = length($ppty_txt) + 1;
430    my $text_len = length($text);
431    my $text_md5 = md5_hex($text);
432    my $content_len = $ppty_len + $text_len;
433    print <<"EOT";
434Node-path: $path
435Node-kind: file
436Node-action: add
437Text-content-length: $text_len
438Text-content-md5: $text_md5
439Prop-content-length: $ppty_len
440Content-length: $content_len
441
442$ppty_txt
443$text
444
445EOT
446}
447
448sub svn_add_symlink {
449    my ($path, $properties, $text) = @_;
450    push(@$properties, ("svn:special","*"));
451    $text = "link $text";
452    $dir_usage{parent_directory($path)}++;
453    my $ppty_txt = svn_props2string($properties);
454    my $ppty_len = length($ppty_txt) + 1;
455    my $text_len = length($text);
456    my $text_md5 = md5_hex($text);
457    my $content_len = $ppty_len + $text_len;
458    print <<"EOT";
459Node-path: $path
460Node-kind: file
461Node-action: add
462Text-content-length: $text_len
463Text-content-md5: $text_md5
464Prop-content-length: $ppty_len
465Content-length: $content_len
466
467$ppty_txt
468$text
469
470EOT
471}
472
473sub svn_edit_file {
474    my ($path, $properties, $text) = @_;
475    my $ppty_txt = svn_props2string($properties);
476    my $ppty_len = length($ppty_txt) + 1;
477    my $text_len = length($text);
478    my $text_md5 = md5_hex($text);
479    my $content_len = $ppty_len + $text_len;
480    print <<"EOT";
481Node-path: $path
482Node-kind: file
483Node-action: change
484Text-content-length: $text_len
485Text-content-md5: $text_md5
486Prop-content-length: $ppty_len
487Content-length: $content_len
488
489$ppty_txt
490$text
491
492EOT
493}
494
495sub svn_delete {
496    my ($path) = @_;
497    $dir_usage{parent_directory($path)}--;
498
499    print <<"EOT";
500Node-path: $path
501Node-action: delete
502
503EOT
504}
505
506sub svn_add_copy {
507    my ($path, $properties, $content, $from_path, $from_rev) = @_;
508    $dir_usage{parent_directory($path)}++;
509    my $ppty_txt = svn_props2string($properties);
510    my $ppty_len = length($ppty_txt) + 1;
511    my $content_md5 = md5_hex($content);
512    print <<"EOT";
513Node-path: $path
514Node-kind: file
515Node-action: add
516Node-copyfrom-rev: $from_rev
517Node-copyfrom-path: $from_path
518Text-copy-source-md5: $content_md5
519Prop-content-length: $ppty_len
520Content-length: $ppty_len
521
522$ppty_txt
523
524EOT
525}
526
527sub svn_replace_copy {
528    my ($path, $properties, $content, $from_path, $from_rev) = @_;
529    my $ppty_txt = svn_props2string($properties);
530    my $ppty_len = length($ppty_txt) + 1;
531    my $content_md5 = md5_hex($content);
532    print <<"EOT";
533Node-path: $path
534Node-kind: file
535Node-action: replace
536Node-copyfrom-rev: $from_rev
537Node-copyfrom-path: $from_path
538Text-copy-source-md5: $content_md5
539Prop-content-length: $ppty_len
540Content-length: $ppty_len
541
542$ppty_txt
543
544EOT
545}
546
547sub svn_add_parent_dirs {
548    my $svn_path = shift;
549    debug("svn_add_parent_dirs passed $svn_path\n");
550    foreach my $dir (parent_directories($svn_path)) {
551        next if $dir_seen{$dir}++;
552        debug("svn_add_parent_dirs adding $dir\n");
553        svn_add_dir($dir, undef);
554    }
555}
556
557sub svn_delete_empty_parent_dirs {
558    return unless $options{"delete-empty-dirs"} && @_;
559    debug("svn_delete_empty_parent_dirs: passed  @_\n");
560
561    my @deleted_dirs;
562    for (@_) {
563        $_ = parent_directory($_) or next;
564        debug("svn_delete_empty_parent_dirs: $_ usage $dir_usage{$_}\n");
565        if ($dir_usage{$_} == 0 && $dir_seen{$_} > 0) {
566            debug("svn_delete_empty_parent_dirs: deleting $_\n");
567            svn_delete($_);
568            $dir_seen{$_} = 0;
569            push(@deleted_dirs, $_);
570        }
571    }
572
573    svn_delete_empty_parent_dirs(@deleted_dirs);
574}
575
576#########################################################################
577# Routines for interacting with Perforce server.
578#########################################################################
579
580my $global_p4;
581
582sub p4_init {
583    return $global_p4
584        if defined $global_p4;
585    my $p4 = P4->new();
586    $p4->SetUser($options{user}) if $options{user};
587    $p4->SetClient($options{client}) if $options{client};
588    $p4->SetPort($options{port}) if $options{port};
589    $p4->SetPassword($options{password}) if $options{password};
590    $p4->ParseForms();
591    while (!$p4->Init()) {
592        warn "failed to connect to Perforce server";
593        sleep 3;
594        warn "retrying...\n";
595    };
596    return $global_p4 = $p4;
597}
598
599sub p4_get_changes {
600    my $p4 = p4_init();
601    my @changes;
602    foreach my $branch (keys %{$options{branch}}) {
603        debug("p4_get_changes: branch $branch\n");
604        push @changes, $p4->Changes($branch . "...");
605        die "@{$p4->Errors()}" if $p4->ErrorCount();
606    }
607    #$p4->Final();
608    my %seen = map {$_->{change} => 1} @changes;
609    return sort {$a <=> $b} keys %seen;
610}
611
612sub p4_get_change_details {
613    my $change_num = shift;
614    debug("p4_get_change_details: $change_num\n");
615    my $p4 = p4_init();
616    my $change = $p4->Describe($change_num);
617    die "@{$p4->Errors()}" if $p4->ErrorCount();
618    #$p4->Final();
619    my %result;
620    $result{author} = $change->{user};
621    $result{log}  = $change->{desc};
622    $result{date} = time2str(SVN_DATE_TEMPLATE, $change->{time});
623    for (my $i = 0; $i < @{$change->{depotFile}}; $i++) {
624        my $file = $change->{depotFile}[$i];
625        my $action = $change->{action}[$i];
626        my $type = $change->{type}[$i];
627        if (is_wanted_file($file)) {
628            push @{$result{actions}}, {action => $action,
629                                       path => $file,
630                                       type => $type};
631        }
632    }
633    return \%result;
634}
635
636#
637# We have to jump through hoops to get the file content because
638# Print() behaves inconsistently. For text files, it returns an array
639# reference, the first element of which is a hash reference with
640# details we're not interested in, and remaining elements the file
641# content; but for binary files it just returns the hash reference
642# and writes the content to STDOUT.  Painful!
643#
644sub p4_get_file_content {
645    my $filespec = shift;
646    debug("p4_get_file_content: $filespec\n");
647    my $p4 = p4_init();
648    my $result = $p4->Print($filespec);
649    die "@{$p4->Errors()}" if $p4->ErrorCount();
650    my $content = "";
651    if (ref $result eq "ARRAY") {
652        for (my $i = 1; $i < @$result; $i++) {
653            $content .= $result->[$i];
654        }
655    }
656    #$p4->Final();
657    return $content;
658}
659
660sub p4_files_are_identical {
661    my ($src_fspec, $dst_fspec) = @_;
662    debug("p4_files_are_identical: @_\n");
663    my $p4 = p4_init();
664    my $res = $p4->Diff2($src_fspec, $dst_fspec);
665    die "@{$p4->Errors()}" if $p4->ErrorCount();
666    if (ref $res) {
667        if (ref $res eq "ARRAY") {
668            debug("p4_files_are_identical: $res->[0]\n");
669        } elsif (ref $res eq "SCALAR") {
670            debug("p4_files_are_identical: $$res\n");
671        } else {
672            debug("p4_files_are_identical: not an ARRAY nor a SCALAR\n");
673        }
674        return 0;
675    }
676    debug("p4_files_are_identical: $res\n");
677    return $res =~ /identical$/;
678}
679
680#
681# If $path was not modified by this change, return (undef, undef),
682# which signals to the caller to ignore this file.  If we are unable,
683# for any reason, to determine the source of a branch/integrate,
684# return (undef, -n), signalling to the caller to treat this as an
685# add/edit.
686#
687sub p4_get_copyfrom_filerev {
688    my ($path, $change) = @_;
689    debug("p4_get_copyfrom_filerev: $path\@$change\n");
690    if ($change > 1 && p4_files_are_identical($path.'@'.$change,
691                                              $path.'@'.($change-1))) {
692        debug("p4_get_copyfrom_filerev: $path\@$change unchanged\n");
693        return (undef, undef);
694    }
695    my $p4 = p4_init();
696    my $res = $p4->Filelog("$path\@$change");
697    die "@{$p4->Errors()}" if $p4->ErrorCount();
698    debug(Dumper($res));
699    unless ($res->{how}) {
700        debug("p4_get_copyfrom_filerev: returning undef#-1\n");
701        return (undef, -1);
702    }
703    unless (ref $res->{how} eq "ARRAY" && $res->{how}[0]) {
704        die "Unexpected return from P4 Filelog ($path\@$change)\n";
705    }
706###    unless ($res->{how}[0]) {
707###        debug("p4_get_copyfrom_filerev: returning undef#undef (path 1)\n");
708###        return (undef, undef);
709###    }
710    my $i;
711    for ($i = 0; $i < @{$res->{how}[0]}; $i++) {
712        last if $res->{how}[0][$i] =~ /from$/;
713    }
714    if ($i < 0 || $i > $#{$res->{how}[0]}) {
715        debug("p4_get_copyfrom_filerev: returning undef#-2)\n");
716        return (undef, -2);
717    }
718    my $copyfrom_path = $res->{file}[0][$i];
719    my $copyfrom_rev  = $res->{erev}[0][$i];
720    debug("p4_get_copyfrom_filerev: returning $copyfrom_path$copyfrom_rev\n");
721    return ($copyfrom_path, $copyfrom_rev);
722}
723
724########################################################################
725# Return Subversion revision of Perforce file at given revision.
726########################################################################
727
728sub p4_file2svnrev {
729    my ($file, $rev) = @_;
730    debug("p4_file2svnrev: $file$rev\n");
731    my $p4 = p4_init();
732    my $filelog = $p4->Filelog($file . $rev);
733    die "@{$p4->Errors()}" if $p4->ErrorCount();
734    my $change = shift @{$filelog->{change}};
735    die "can't map $file$rev to Subversion revision"
736      unless defined $rev_map{$change};
737    return $rev_map{$change};
738}
739
740########################################################################
741# Routines for converting Perforce actions to Subversion dump/restore
742# records.
743########################################################################
744
745sub p4add2svn {
746    my ($path, $type, $change) = @_;
747
748    my $svn_path = depot2svnpath($path)
749      or die "Unable to determine SVN path for $path\n";
750    svn_add_parent_dirs($svn_path);
751    my $content = p4_get_file_content("$path\@$change");
752    munge_keywords(\$content) if $type =~ /text$/;
753    chop $content if $type =~ /symlink$/;
754    my $properties = properties($type, \$content);
755    if ($type =~ /symlink$/) {
756        svn_add_symlink($svn_path, $properties, $content);
757    } else {
758        svn_add_file($svn_path, $properties, $content);
759    }
760}
761
762sub p4delete2svn {
763    my ($path, $type, $change) = @_;
764
765    my $svn_path = depot2svnpath($path)
766      or die "Unable to determine SVN path for $path\n";
767    svn_delete($svn_path);
768    push @deleted_files, $svn_path;
769}
770
771sub p4edit2svn {
772    my ($path, $type, $change) = @_;
773    my $svn_path = depot2svnpath($path)
774      or die "Unable to determine SVN path for $path\n";
775    my $content = p4_get_file_content("$path\@$change");
776    munge_keywords(\$content) if $type =~ /text$/;
777    my $properties = properties($type, \$content);
778    svn_edit_file($svn_path, $properties, $content);
779}
780
781sub p4branch2svn {
782    my ($path, $type, $change) = @_;
783    my $svn_path = depot2svnpath($path)
784      or die "Unable to determine SVN path for $path\n";
785    my ($from_path, $from_rev) = p4_get_copyfrom_filerev($path, $change);
786    unless ($from_path) {
787        if ($from_rev) {
788            p4add2svn($path, $type, $change);
789        }
790        else {
791            warn "Ignoring $path\@$change\n";
792        }
793        return;
794    }
795    unless (p4_files_are_identical($from_path.$from_rev, "$path\@$change")) {
796        p4add2svn($path, $type, $change);
797        return;
798    }
799    svn_add_parent_dirs($svn_path);
800    my $content = p4_get_file_content($from_path . $from_rev);
801    my $svn_from_path = depot2svnpath($from_path);
802    munge_keywords(\$content) if $type =~ /text$/;
803    my $properties = properties($type, \$content);
804    if ($svn_from_path) {
805        my $svn_from_rev  = p4_file2svnrev($from_path, $from_rev);
806        svn_add_copy($svn_path, $properties, $content,
807                     $svn_from_path, $svn_from_rev);
808    }
809    else {
810        # Source is outside the branch(es) we're processing: treat as add
811        svn_add_file($svn_path, $properties, $content);
812    }
813}
814
815sub p4integrate2svn {
816    my ($path, $type, $change) = @_;
817    my $svn_path = depot2svnpath($path)
818      or die "Unable to determine SVN path for $path\n";
819    my ($from_path, $from_rev) = p4_get_copyfrom_filerev($path, $change);
820    unless ($from_path) {
821        if ($from_rev) {
822            p4edit2svn($path, $type, $change);
823        }
824        else {
825            warn "Ignoring $path\@$change\n";
826        }
827        return;
828    }
829    unless (p4_files_are_identical($from_path.$from_rev, "$path\@$change")) {
830        p4edit2svn($path, $type, $change);
831        return;
832    }
833    my $content = p4_get_file_content($from_path . $from_rev);
834    my $svn_from_path = depot2svnpath($from_path);
835    munge_keywords(\$content) if $type =~ /text$/;
836    my $properties = properties($type, \$content);
837    if ($svn_from_path) {
838        my $svn_from_rev  = p4_file2svnrev($from_path, $from_rev);
839        svn_replace_copy($svn_path, $properties, $content,
840                         $svn_from_path, $svn_from_rev);
841    }
842    else {
843        # Source is outside the branch(es) we're processing: treat as edit
844        svn_edit_file($svn_path, $properties, $content);
845    }
846}
847
848########################################################################
849# Main processing
850########################################################################
851
852process_options();
853
854my %p42svn = ("add"       => \&p4add2svn,
855              "delete"    => \&p4delete2svn,
856              "edit"      => \&p4edit2svn,
857              "branch"    => \&p4branch2svn,
858              "integrate" => \&p4integrate2svn);
859
860my $svn_rev = 1;
861
862binmode(STDOUT);
863svn_dump_format_version(SVN_FS_DUMP_FORMAT_VERSION);
864foreach my $change_num (p4_get_changes()) {
865    my $details = p4_get_change_details($change_num);
866    my @properties = ("svn:log"    => $details->{log},
867                      "svn:author" => $details->{author},
868                      "svn:date"   => $details->{date});
869    @deleted_files = ();
870    svn_revision($svn_rev, \@properties);
871    $rev_map{$change_num} = $svn_rev++;
872    foreach (@{$details->{actions}}) {
873        if (defined $p42svn{$_->{action}}) {
874            $p42svn{$_->{action}}->($_->{path}, $_->{type}, $change_num);
875        }
876        else {
877            warn "Action $_->{action} not recognized "
878              ."($_->{path}\@$change_num)\n";
879        }
880    }
881    #
882    # This must be done last in case files are both created and
883    # deleted in the same directory in the course of a single change.
884    #
885    svn_delete_empty_parent_dirs(@deleted_files);
886}
Note: See TracBrowser for help on using the repository browser.