3 #=======================================================================
5 # File ID: 21045066-f743-11dd-9679-000475e441b9
9 # ©opyleft 2008– Øyvind A. Holm <sunny@sunbase.org>
10 # License: GNU General Public License version 2 or later, see end of
11 # file for legal stuff.
12 #=======================================================================
37 $progname =~ s/^.*\/(.*?)$/$1/;
38 our $VERSION = "0.00";
40 Getopt
::Long
::Configure
("bundling");
43 "debug" => \
$Opt{'debug'},
44 "dest|d=s" => \
$Opt{'dest'},
45 "dry-run" => \
$Opt{'dry-run'},
46 "help|h" => \
$Opt{'help'},
47 "revisions|r=s" => \
$Opt{'revisions'},
48 "source|s=s" => \
$Opt{'source'},
49 "verbose|v+" => \
$Opt{'verbose'},
50 "version" => \
$Opt{'version'},
52 ) || die("$progname: Option error. Use -h for help.\n");
54 $Opt{'debug'} && ($Debug = 1);
55 $Opt{'help'} && usage
(0);
56 if ($Opt{'version'}) {
61 my $Lh = "[0-9a-fA-F]";
62 my $v1_templ = "$Lh\{8}-$Lh\{4}-1$Lh\{3}-$Lh\{4}-$Lh\{12}";
65 my $PROP_PREFIX = "getsvnfiles:";
67 'author' => "${PROP_PREFIX}author",
68 'date' => "${PROP_PREFIX}date",
69 'log' => "${PROP_PREFIX}log",
70 'revision' => "${PROP_PREFIX}revision",
71 'source' => "${PROP_PREFIX}source",
75 my ($start_rev, $end_rev);
78 for my $chkdef (qw{source dest revisions
}) {
79 length($Opt{$chkdef}) ||
80 (warn("$progname: --$chkdef option not defined\n"), $missing_args = 1);
82 $missing_args && exit(1);
84 if ($Opt{'revisions'} =~ /^(\d+):(\d+|head)$/i) {
85 ($start_rev, $end_rev) = (uc($1), uc($2));
87 die("$progname: \"$Opt{'revisions'}\": " .
88 "Invalid format of --revisions argument\n");
91 if (!-f
$Opt{'dest'}) {
92 die("$progname: $Opt{'dest'}: File does not exist\n");
95 @Revs = revisions
($Opt{'source'}, $start_rev, $end_rev);
97 D
(sprintf("\@Revs = '%s'\n", join(", ", @Revs)));
99 my $tmpfile_logmsg = mktemp
("$progname-logmsg-XXXXXX");
100 my $tmpfile_propset = mktemp
("$progname-propset-XXXXXX");
101 mysyst
($CMD_SVN, "update", $Opt{'dest'});
102 for my $curr_rev (@Revs) {
103 print(STDERR
"======== $progname: Downloading revision $curr_rev ========\n");
105 if (!$Opt{'dry-run'}) {
106 my %rev_info = revision_info
($Opt{'source'}, $curr_rev);
107 my $log_msg = log_message
($Opt{'source'}, $curr_rev);
108 if (open(LogMsgFP
, ">", $tmpfile_logmsg)) {
109 print(LogMsgFP prepare_logmsg
($log_msg));
110 chomp(my $uuid = `suuid -t $progname -w eo -c "Commit r$curr_rev of $Opt{'dest'} from $Opt{'source'}"`);
111 print(LogMsgFP
"\n$uuid");
113 if (!defined($uuid) || $uuid !~ /^$v1_templ$/) {
114 unlink($tmpfile_logmsg)
115 || warn("$progname: $tmpfile_logmsg: Cannot delete temp file: $!\n");
116 die("$progname: suuid error, aborting.\n");
119 die("$progname: $tmpfile_logmsg: Cannot create file: $!\n");
121 if (open(CatFP
, "$CMD_SVN cat $Opt{'source'} -r$curr_rev |")) {
122 if (open(DestFP
, ">", $Opt{'dest'})) {
128 die("$progname: $Opt{'dest'}: Cannot open file for write: $!\n");
132 die("$progname: Cannot open \"svn cat\" pipe: $!\n");
134 svn_propset
($Prop{'source'}, $Opt{'source'}, $Opt{'dest'});
135 svn_propset
($Prop{'revision'}, $curr_rev, $Opt{'dest'});
136 svn_propset
($Prop{'date'}, $rev_info{'date'}, $Opt{'dest'});
137 svn_propset
($Prop{'author'}, $rev_info{'author'}, $Opt{'dest'});
138 svn_propset
($Prop{'log'}, $rev_info{'log'}, $Opt{'dest'});
139 mysyst
($CMD_SVN, "commit", "-F", $tmpfile_logmsg, $Opt{'dest'});
142 if (!$Opt{'dry-run'}) {
143 mysyst
($CMD_SVN, "propdel", $Prop{'author'}, $Opt{'dest'});
144 mysyst
($CMD_SVN, "propdel", $Prop{'date'}, $Opt{'dest'});
145 mysyst
($CMD_SVN, "propdel", $Prop{'log'}, $Opt{'dest'});
146 mysyst
($CMD_SVN, "propdel", $Prop{'revision'}, $Opt{'dest'});
147 mysyst
($CMD_SVN, "propdel", $Prop{'source'}, $Opt{'dest'});
148 my $file_loc = file_location
($Opt{'dest'});
149 if (open(LogMsgFP
, ">", $tmpfile_logmsg)) {
152 "%s: %u revision%s between r%s:%s downloaded from\n" .
156 " Deleted download properties.\n" .
159 $progname, scalar(@Revs), scalar(@Revs) == 1 ?
"" : "s",
160 $start_rev, $end_rev, $Opt{'source'},
161 file_location
($Opt{'dest'}),
162 `suuid -t $progname -w eo -c "Delete download properties from $Opt{'dest'}, downloaded from $Opt{'source'}"`
167 die("$progname: $tmpfile_logmsg: Cannot open file for write: $!\n");
170 mysyst
($CMD_SVN, "commit", "-F", $tmpfile_logmsg, $Opt{'dest'});
171 unlink($tmpfile_logmsg);
175 # Find the repository location for a file {{{
177 my $Info = `$CMD_SVN info --xml $File`;
178 my ($Url, $Root) = ("", "");
179 $Info =~ /<url>(.*?)<\/url
>/s
&& ($Url = $1);
180 $Info =~ /<root>(.*?)<\/root
>/s
&& ($Root = $1);
181 my $Retval = substr($Url, length($Root));
187 # Set file property for a file {{{
188 my ($Propname, $Propval, $Dest) = @_;
189 D
("svn_propset('$Propname', '$Propval', '$Dest');");
190 if (open(PropFP
, ">", $tmpfile_propset)) {
191 print(PropFP
$Propval);
194 die("$progname: $tmpfile_propset: Cannot create file: $!\n");
196 mysyst
($CMD_SVN, "propset", $Propname, "-F", $tmpfile_propset, $Dest);
197 unlink($tmpfile_propset);
205 $Txt =~ s/(\n)/$1# /gs;
207 $Txt =~ s/\n# \n/\n#\n/gs;
208 $Txt =~ s/\n# \n/\n#\n/gs;
209 $Txt =~ s/#\n(# ------------------------------------------------------------------------\n)/$1/gs;
210 $Txt =~ s/^# ------------------------------------------------------------------------\n//s;
211 $Txt =~ s/# ------------------------------------------------------------------------\n$//s;
217 # Return log message for a specific revision {{{
218 my ($Source, $Rev) = @_;
219 my $Retval = `$CMD_SVN log -r$Rev $Source`;
225 # Return raw log message for a specific revision {{{
226 my ($Source, $Rev) = @_;
228 my $Xml = `$CMD_SVN log --xml -r$Rev $Source`;
229 $Xml =~ /<author>(.*?)<\/author
>/s
&& ($Retval{'author'} = xml_to_txt
($1));
230 $Xml =~ /<date>(.*?)<\/date
>/s
&& ($Retval{'date'} = xml_to_txt
($1));
231 $Xml =~ /<msg>(.*?)<\/msg>/s
&& ($Retval{'log'} = xml_to_txt
($1));
232 # $Retval =~ s/^.*<msg>(.*?)<\/msg>.*$/$1/s;
238 # Return an array of revision numbers from a specific revision range
239 # for a version controlled element
241 my ($File, $Start, $End) = @_;
242 D
("revisions('$File', '$Start', '$End')");
243 my $safe_file = escape_filename
($File);
247 if (open(PipeFP
, "$CMD_SVN log --xml -r$Start:$End $safe_file |")) {
248 $Data = join("", <PipeFP
>);
250 $Data =~ s/<logentry\b.*?\brevision="(\d+)".*?>/push(@Revs, "$1")/egs;
256 sub escape_filename
{
257 # Kludge for handling file names with spaces and characters that
258 # trigger shell functions
261 # $Name =~ s/\\/\\\\/g;
262 # $Name =~ s/([ \t;\|!&"'`#\$\(\)<>\*\?])/\\$1/g;
267 } # escape_filename()
270 # Convert XML data to plain text {{{
274 $Txt =~ s/&/&/gs;
280 # Customised system() {{{
282 msg
(1, sprintf("%s \"%s\"", $Opt{'dry-run'} ?
"Simulating" : "Executing", join(" ", @Args)));
283 $Opt{'dry-run'} || system(@Args);
288 # Wait until Enter is pressed if --debug {{{
290 print(STDERR
"debug: Press ENTER...");
296 # Print program version {{{
297 print("$progname v$VERSION\n");
302 # Send the help message to stdout {{{
305 if ($Opt{'verbose'}) {
311 Usage: $progname [OPTIONS] -s SOURCE -d DEST -r STARTREV:ENDREV
313 Copy all revisions of a file from a remote repository into a local file
314 and commit it with the log message from the remote repository. Does not
315 handle file properties yet.
320 Use X as local file destination.
322 Don’t make any changes, only simulate.
325 -r X:Y, --revision X:Y
326 Copy revision range X:Y from remote repository.
328 Source file to download from remote repository.
330 Increase level of verbosity. Can be repeated.
332 Print version information.
334 Print debugging messages.
342 # Print a status message to stderr based on verbosity level {{{
343 my ($verbose_level, $Txt) = @_;
345 if ($Opt{'verbose'} >= $verbose_level) {
346 print(STDERR
"$progname: $Txt\n");
352 # Print a debugging message {{{
354 my @call_info = caller;
355 chomp(my $Txt = shift);
356 my $File = $call_info[1];
358 $File =~ s
#^.*/(.*?)$#$1#;
359 print(STDERR
"$File:$call_info[2] $$ $Txt\n");
366 # Plain Old Documentation (POD) {{{
376 [options] [file [files [...]]]
386 =item B<-h>, B<--help>
388 Print a brief help summary.
390 =item B<-v>, B<--verbose>
392 Increase level of verbosity. Can be repeated.
396 Print version information.
400 Print debugging messages.
410 Made by Øyvind A. Holm S<E<lt>sunny@sunbase.orgE<gt>>.
414 Copyleft © Øyvind A. Holm E<lt>sunny@sunbase.orgE<gt>
415 This is free software; see the file F<COPYING> for legalese stuff.
419 This program is free software: you can redistribute it and/or modify it
420 under the terms of the GNU General Public License as published by the
421 Free Software Foundation, either version 2 of the License, or (at your
422 option) any later version.
424 This program is distributed in the hope that it will be useful, but
425 WITHOUT ANY WARRANTY; without even the implied warranty of
426 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
427 See the GNU General Public License for more details.
429 You should have received a copy of the GNU General Public License along
431 If not, see L<http://www.gnu.org/licenses/>.
439 # vim: set fenc=UTF-8 ft=perl fdm=marker ts=4 sw=4 sts=4 et fo+=w :