PageRenderTime 59ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/SVK-v2.2.3/lib/SVK/Util.pm

#
Perl | 1069 lines | 861 code | 144 blank | 64 comment | 59 complexity | 46c77b222a77dbca7c75b739eb3531b8 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.0
  1. # BEGIN BPS TAGGED BLOCK {{{
  2. # COPYRIGHT:
  3. #
  4. # This software is Copyright (c) 2003-2008 Best Practical Solutions, LLC
  5. # <clkao@bestpractical.com>
  6. #
  7. # (Except where explicitly superseded by other copyright notices)
  8. #
  9. #
  10. # LICENSE:
  11. #
  12. #
  13. # This program is free software; you can redistribute it and/or
  14. # modify it under the terms of either:
  15. #
  16. # a) Version 2 of the GNU General Public License. You should have
  17. # received a copy of the GNU General Public License along with this
  18. # program. If not, write to the Free Software Foundation, Inc., 51
  19. # Franklin Street, Fifth Floor, Boston, MA 02110-1301 or visit
  20. # their web page on the internet at
  21. # http://www.gnu.org/copyleft/gpl.html.
  22. #
  23. # b) Version 1 of Perl's "Artistic License". You should have received
  24. # a copy of the Artistic License with this package, in the file
  25. # named "ARTISTIC". The license is also available at
  26. # http://opensource.org/licenses/artistic-license.php.
  27. #
  28. # This work is distributed in the hope that it will be useful, but
  29. # WITHOUT ANY WARRANTY; without even the implied warranty of
  30. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  31. # General Public License for more details.
  32. #
  33. # CONTRIBUTION SUBMISSION POLICY:
  34. #
  35. # (The following paragraph is not intended to limit the rights granted
  36. # to you to modify and distribute this software under the terms of the
  37. # GNU General Public License and is only of importance to you if you
  38. # choose to contribute your changes and enhancements to the community
  39. # by submitting them to Best Practical Solutions, LLC.)
  40. #
  41. # By intentionally submitting any modifications, corrections or
  42. # derivatives to this work, or any other work intended for use with SVK,
  43. # to Best Practical Solutions, LLC, you confirm that you are the
  44. # copyright holder for those contributions and you grant Best Practical
  45. # Solutions, LLC a nonexclusive, worldwide, irrevocable, royalty-free,
  46. # perpetual, license to use, copy, create derivative works based on
  47. # those contributions, and sublicense and distribute those contributions
  48. # and any derivatives thereof.
  49. #
  50. # END BPS TAGGED BLOCK }}}
  51. package SVK::Util;
  52. use strict;
  53. require Exporter;
  54. our @ISA = qw(Exporter);
  55. our @EXPORT_OK = qw(
  56. IS_WIN32 DEFAULT_EDITOR TEXT_MODE HAS_SYMLINK HAS_SVN_MIRROR $EOL $SEP
  57. get_prompt get_buffer_from_editor edit_file
  58. get_encoding get_encoder from_native to_native
  59. find_svm_source traverse_history
  60. read_file write_file slurp_fh md5_fh bsd_glob mimetype mimetype_is_text
  61. is_binary_file
  62. abs_path abs2rel catdir catfile catpath devnull dirname get_anchor
  63. move_path make_path splitpath splitdir tmpdir tmpfile get_depot_anchor
  64. catdepot abs_path_noexist
  65. is_symlink is_executable is_uri can_run is_path_inside is_depotpath
  66. uri_escape uri_unescape
  67. str2time time2str reformat_svn_date
  68. find_dotsvk
  69. );
  70. use SVK::Version; our $VERSION = $SVK::VERSION;
  71. use Config ();
  72. use SVK::Logger;
  73. use SVK::I18N;
  74. use SVN::Core;
  75. use autouse 'Encode' => qw(resolve_alias($) decode encode);
  76. use File::Glob qw(bsd_glob);
  77. use autouse 'File::Basename' => qw(dirname);
  78. use autouse 'File::Spec::Functions' =>
  79. qw(catdir catpath splitpath splitdir tmpdir);
  80. use autouse 'List::Util' => qw( max(@) );
  81. =head1 NAME
  82. SVK::Util - Utility functions for SVK classes
  83. =head1 SYNOPSIS
  84. use SVK::Util qw( func1 func2 func3 )
  85. =head1 DESCRIPTION
  86. This is yet another abstraction function set for portable file, buffer and
  87. IO handling, tailored to SVK's specific needs.
  88. No symbols are exported by default; the user module needs to specify the
  89. list of functions to import.
  90. =head1 CONSTANTS
  91. =head2 Constant Functions
  92. =head3 IS_WIN32
  93. Boolean flag to indicate whether this system is running Microsoft Windows.
  94. =head3 DEFAULT_EDITOR
  95. The default program to invoke for editing buffers: C<notepad.exe> on Win32,
  96. C<vi> otherwise.
  97. =head3 TEXT_MODE
  98. The I/O layer for text files: C<:crlf> on Win32, empty otherwise.
  99. =head3 HAS_SYMLINK
  100. Boolean flag to indicate whether this system supports C<symlink()>.
  101. =head3 HAS_SVN_MIRROR
  102. Boolean flag to indicate whether we can successfully load L<SVN::Mirror>.
  103. =head2 Constant Scalars
  104. =head3 $SEP
  105. Native path separator: platform: C<\> on dosish platforms, C</> otherwise.
  106. =head3 $EOL
  107. End of line marker: C<\015\012> on Win32, C<\012> otherwise.
  108. =cut
  109. use constant IS_WIN32 => ($^O eq 'MSWin32');
  110. use constant TEXT_MODE => IS_WIN32 ? ':crlf' : '';
  111. use constant DEFAULT_EDITOR => IS_WIN32 ? 'notepad.exe' : 'vi';
  112. use constant HAS_SYMLINK => $Config::Config{d_symlink};
  113. sub HAS_SVN_MIRROR () {
  114. no warnings 'redefine';
  115. local $@;
  116. my $has_svn_mirror = $ENV{SVKNOSVM} ? 0 : eval { require SVN::Mirror; 1 };
  117. *HAS_SVN_MIRROR = $has_svn_mirror ? sub () { 1 } : sub () { 0 };
  118. return $has_svn_mirror;
  119. }
  120. our $SEP = catdir('');
  121. our $EOL = IS_WIN32 ? "\015\012" : "\012";
  122. =head1 FUNCTIONS
  123. =head2 User Interactivity
  124. =head3 get_prompt ($prompt, $pattern)
  125. Repeatedly prompt the user for a line of answer, until it matches
  126. the regular expression pattern. Returns the chomped answer line.
  127. =cut
  128. sub get_prompt { {
  129. my ($prompt, $pattern) = @_;
  130. return '' if ($ENV{'SVKBATCHMODE'});
  131. local $| = 1;
  132. print $prompt;
  133. local *IN;
  134. local *SAVED = *STDIN;
  135. local *STDIN = *STDIN;
  136. my $formfeed = "";
  137. if (!-t STDIN and -r '/dev/tty' and open IN, '<', '/dev/tty') {
  138. *STDIN = *IN;
  139. $formfeed = "\r";
  140. }
  141. require Term::ReadKey;
  142. Term::ReadKey::ReadMode(IS_WIN32 ? 'normal' : 'raw');
  143. my $out = (IS_WIN32 ? sub { 1 } : sub { print @_ });
  144. my $erase;
  145. if (!IS_WIN32 && -t) {
  146. my %keys = Term::ReadKey::GetControlChars();
  147. $erase = $keys{ERASE};
  148. }
  149. my $answer = '';
  150. while (defined(my $key = Term::ReadKey::ReadKey(0))) {
  151. if ($key =~ /[\012\015]/) {
  152. $out->("\n") if $key eq $formfeed;
  153. $out->($key); last;
  154. }
  155. elsif ($key eq "\cC") {
  156. Term::ReadKey::ReadMode('restore');
  157. *STDIN = *SAVED;
  158. Term::ReadKey::ReadMode('restore');
  159. my $msg = loc("Interrupted.\n");
  160. $msg =~ s{\n\z}{$formfeed\n};
  161. die $msg;
  162. }
  163. elsif (defined $erase and $key eq $erase) {
  164. next unless length $answer;
  165. $out->("\cH \cH");
  166. chop $answer; next;
  167. }
  168. elsif ($key eq "\cH") {
  169. next unless length $answer;
  170. $out->("$key $key");
  171. chop $answer; next;
  172. }
  173. elsif ($key eq "\cW") {
  174. my $len = (length $answer) or next;
  175. $out->("\cH" x $len, " " x $len, "\cH" x $len);
  176. $answer = ''; next;
  177. }
  178. elsif (ord $key < 32) {
  179. # control character -- ignore it!
  180. next;
  181. }
  182. $out->($key);
  183. $answer .= $key;
  184. }
  185. if (defined $pattern) {
  186. $answer =~ $pattern or redo;
  187. }
  188. Term::ReadKey::ReadMode('restore');
  189. return $answer;
  190. } }
  191. =head3 edit_file ($file_name)
  192. Launch editor to edit a file.
  193. =cut
  194. sub edit_file {
  195. my ($file) = @_;
  196. my $editor = defined($ENV{SVN_EDITOR}) ? $ENV{SVN_EDITOR}
  197. : defined($ENV{EDITOR}) ? $ENV{EDITOR}
  198. : DEFAULT_EDITOR; # fall back to something
  199. my @editor = split (/ /, $editor);
  200. if ( IS_WIN32 ) {
  201. my $o;
  202. my $e = shift @editor;
  203. $e =~ s/^"//;
  204. while ( !defined($o = can_run ($e)) ) {
  205. die loc ("Can not find the editor: %1\n", $e) unless @editor;
  206. $e .= " ".shift @editor;
  207. $e =~ s/"$//;
  208. }
  209. unshift @editor, $o;
  210. }
  211. $logger->info(loc("Waiting for editor..."));
  212. # XXX: check $?
  213. system {$editor[0]} (@editor, $file) and die loc("Aborted: %1\n", $!);
  214. }
  215. =head3 get_buffer_from_editor ($what, $sep, $content, $filename, $anchor, $targets_ref)
  216. XXX Undocumented
  217. =cut
  218. sub get_buffer_from_editor {
  219. my ( $what, $sep, $content, $file, $anchor, $targets_ref ) = @_;
  220. my $fh;
  221. if ( defined $content ) {
  222. ( $fh, $file ) = tmpfile( $file, TEXT => 1, UNLINK => 0 );
  223. print $fh $content;
  224. close $fh;
  225. } else {
  226. open $fh, $file or die $!;
  227. local $/;
  228. $content = <$fh>;
  229. close $fh;
  230. }
  231. my $time = time;
  232. while (!$ENV{'SVKBATCHMODE'} && 1) {
  233. open my $fh, '<', $file or die $!;
  234. my $md5 = md5_fh($fh);
  235. close $fh;
  236. edit_file($file);
  237. open $fh, '<', $file or die $!;
  238. last if ( $md5 ne md5_fh($fh) );
  239. close $fh;
  240. my $ans = get_prompt(
  241. loc( "%1 not modified: a)bort, e)dit, c)ommit?", ucfirst($what) ),
  242. qr/^[aec]/,
  243. );
  244. last if $ans =~ /^c/;
  245. # XXX: save the file somewhere
  246. unlink($file), die loc("Aborted.\n") if $ans =~ /^a/;
  247. }
  248. open $fh, $file or die $!;
  249. local $/;
  250. my @ret = defined $sep ? split( /\n\Q$sep\E\n/, <$fh>, 2 ) : (<$fh>);
  251. close $fh;
  252. unlink $file;
  253. die loc("Cannot find separator; aborted.\n")
  254. if defined($sep)
  255. and !defined( $ret[1] );
  256. return $ret[0] unless wantarray;
  257. # Compare targets in commit message
  258. my $old_targets = ( split( /\n\Q$sep\E\n/, $content, 2 ) )[1];
  259. $old_targets =~ s/^\?.*//mg; # remove unversioned files
  260. my @new_targets
  261. = map {
  262. s/^\s+//; # proponly change will have leading spacs
  263. [ split( /[\s\+]+/, $_, 2 ) ]
  264. }
  265. grep {
  266. !/^\?/m
  267. } # remove unversioned fils
  268. grep {/\S/}
  269. split( /\n+/, $ret[1] );
  270. if ( $old_targets ne $ret[1] ) {
  271. # Assign new targets
  272. @$targets_ref = map abs2rel( $_->[1], $anchor, undef, '/' ),
  273. @new_targets;
  274. }
  275. return ( $ret[0], \@new_targets );
  276. }
  277. =head3 get_encoding
  278. Get the current encoding from locale
  279. =cut
  280. sub get_encoding {
  281. return 'utf8' if $^O eq 'darwin';
  282. local $@;
  283. return (resolve_alias (eval {
  284. require Locale::Maketext::Lexicon;
  285. local $Locale::Maketext::Lexicon::Opts{encoding} = 'locale';
  286. Locale::Maketext::Lexicon::encoding();
  287. } || eval {
  288. require 'encoding.pm';
  289. defined &encoding::_get_locale_encoding() or die;
  290. return encoding::_get_locale_encoding();
  291. }) or 'utf8');
  292. }
  293. =head3 get_encoder ([$encoding])
  294. =cut
  295. sub get_encoder {
  296. my $enc = shift || get_encoding;
  297. return Encode::find_encoding ($enc);
  298. }
  299. =head3 from_native ($octets, $what, [$encoding])
  300. =cut
  301. sub from_native {
  302. my $enc = ref $_[2] ? $_[2] : get_encoder ($_[2]);
  303. my $buf = eval { $enc->decode ($_[0], 1) };
  304. die loc ("Can't decode %1 as %2.\n", $_[1], $enc->name) if $@;
  305. $_[0] = $buf;
  306. Encode::_utf8_off ($_[0]);
  307. return;
  308. }
  309. =head3 to_native ($octets, $what, [$encoding])
  310. =cut
  311. sub to_native {
  312. my $enc = ref $_[2] ? $_[2] : get_encoder ($_[2]);
  313. Encode::_utf8_on ($_[0]);
  314. my $buf = eval { $enc->encode ($_[0], 1) };
  315. die loc ("Can't encode %1 as %2.\n", $_[1], $enc->name) if $@;
  316. $_[0] = $buf;
  317. return;
  318. }
  319. sub find_svm_source { # DEPRECATED: use SVK::Path->universal, only used in SVK::Command now.
  320. my ($repos, $path, $rev) = @_;
  321. my $t = SVK::Path->real_new({ depot => SVK::Depot->new({repos => $repos}),
  322. path => $path, revision => $rev });
  323. $t->refresh_revision unless $rev;
  324. my $u = $t->universal;
  325. return map { $u->$_ } qw(uuid path rev);
  326. }
  327. =head2 File Content Manipulation
  328. =head3 read_file ($filename)
  329. Read from a file and returns its content as a single scalar.
  330. =cut
  331. sub read_file {
  332. local $/;
  333. open my $fh, "< $_[0]" or die $!;
  334. return <$fh>;
  335. }
  336. =head3 write_file ($filename, $content)
  337. Write out content to a file, overwriting existing content if present.
  338. =cut
  339. sub write_file {
  340. return print $_[1] if ($_[0] eq '-');
  341. open my $fh, '>', $_[0] or die $!;
  342. print $fh $_[1];
  343. }
  344. =head3 slurp_fh ($input_fh, $output_fh)
  345. Read all data from the input filehandle and write them to the
  346. output filehandle. The input may also be a scalar, or reference
  347. to a scalar.
  348. =cut
  349. sub slurp_fh {
  350. my $from = shift;
  351. my $to = shift;
  352. local $/ = \16384;
  353. if (!ref($from)) {
  354. print $to $from;
  355. }
  356. elsif (ref($from) eq 'SCALAR') {
  357. print $to $$from;
  358. }
  359. else {
  360. while (<$from>) {
  361. print $to $_;
  362. }
  363. }
  364. }
  365. =head3 md5_fh ($input_fh)
  366. Calculate MD5 checksum for data in the input filehandle.
  367. =cut
  368. {
  369. no warnings 'once';
  370. push @EXPORT_OK, qw( md5 ); # deprecated compatibility API
  371. *md5 = *md5_fh;
  372. }
  373. sub md5_fh {
  374. require Digest::MD5;
  375. my $fh = shift;
  376. my $ctx = Digest::MD5->new;
  377. $ctx->addfile($fh);
  378. return $ctx->hexdigest;
  379. }
  380. =head3 mimetype ($file)
  381. Return the MIME type for the file, or C<undef> if the MIME database
  382. is missing on the system.
  383. =cut
  384. { my $mm; # C<state $mm>, yuck
  385. sub mimetype {
  386. my ($filename) = @_;
  387. # find an implementation module if necessary
  388. $mm ||= do {
  389. my $module = $ENV{SVKMIME} || 'Internal';
  390. $module =~ s/:://;
  391. $module = "SVK::MimeDetect::$module";
  392. eval "require $module";
  393. die $@ if $@;
  394. $module->new();
  395. };
  396. return $mm->checktype_filename($filename);
  397. }
  398. }
  399. =head3 mimetype_is_text ($mimetype)
  400. Return whether a MIME type string looks like a text file.
  401. =cut
  402. sub mimetype_is_text {
  403. my $type = shift;
  404. scalar $type =~ m{^(?:text/.*
  405. |application/x-(?:perl
  406. |python
  407. |ruby
  408. |php
  409. |java
  410. |[kcz]?sh
  411. |awk
  412. |shellscript)
  413. |image/x-x(?:bit|pix)map)$}x;
  414. }
  415. =head3 is_binary_file ($filename OR $filehandle)
  416. Returns true if the given file or filehandle contains binary data. Otherwise,
  417. returns false.
  418. =cut
  419. sub is_binary_file {
  420. my ($file) = @_;
  421. # let Perl do the hard work
  422. return 1 if -f $file && !-T _; # !-T handles empty files correctly
  423. return;
  424. }
  425. =head2 Path and Filename Handling
  426. =head3 abspath ($path)
  427. Return paths with components in symlink resolved, but keep the final
  428. path even if it's symlink. Returns C<undef> if the base directory
  429. does not exist.
  430. =cut
  431. sub abs_path {
  432. my $path = shift;
  433. if (!IS_WIN32) {
  434. require Cwd;
  435. return Cwd::abs_path ($path) unless -l $path;
  436. my (undef, $dir, $pathname) = splitpath ($path);
  437. return catpath (undef, Cwd::abs_path ($dir), $pathname);
  438. }
  439. # Win32 - Complex handling to get the correct base case
  440. $path = '.' if !length $path;
  441. $path = ucfirst(Win32::GetFullPathName($path));
  442. return undef unless -d dirname($path);
  443. my ($base, $remainder) = ($path, '');
  444. while (length($base) > 1) {
  445. my $new_base = Win32::GetLongPathName($base);
  446. return $new_base.$remainder if defined $new_base;
  447. $new_base = dirname($base);
  448. $remainder = substr($base, length($new_base)) . $remainder;
  449. $base = $new_base;
  450. }
  451. return undef;
  452. }
  453. =head3 abs_path_noexist ($path)
  454. Return paths with components in symlink resolved, but keep the final
  455. path even if it's symlink. Unlike abs_path(), returns a valid value
  456. even if the base directory doesn't exist.
  457. =cut
  458. sub abs_path_noexist {
  459. my $path = shift;
  460. my $rest = '';
  461. until (abs_path ($path)) {
  462. return $rest unless length $path;
  463. my $new_path = dirname($path);
  464. $rest = substr($path, length($new_path)) . $rest;
  465. $path = $new_path;
  466. }
  467. return abs_path ($path) . $rest;
  468. }
  469. =head3 abs2rel ($pathname, $old_basedir, $new_basedir, $sep)
  470. Replace the base directory in the native pathname to another base directory
  471. and return the result.
  472. If the pathname is not under C<$old_basedir>, it is returned unmodified.
  473. If C<$new_basedir> is an empty string, removes the old base directory but
  474. keeps the leading slash. If C<$new_basedir> is C<undef>, also removes
  475. the leading slash.
  476. By default, the return value of this function will use C<$SEP> as its
  477. path separator. Setting C<$sep> to C</> will turn native path separators
  478. into C</> instead.
  479. =cut
  480. sub abs2rel {
  481. my ($pathname, $old_basedir, $new_basedir, $sep) = @_;
  482. my $rel = File::Spec::Functions::abs2rel($pathname, $old_basedir);
  483. if ($rel =~ /(?:\A|\Q$SEP\E)\.\.(?:\Q$SEP\E|\z)/o) {
  484. $rel = $pathname;
  485. }
  486. elsif (defined $new_basedir) {
  487. $rel = catdir($new_basedir, $rel);
  488. }
  489. # resemble file::spec pre-3.13 behaviour, return empty string.
  490. return '' if $rel eq '.';
  491. $rel =~ s/\Q$SEP/$sep/go if $sep and $SEP ne $sep;
  492. return $rel;
  493. }
  494. =head3 catdir (@directories)
  495. Concatenate directory names to form a complete path; also removes the
  496. trailing slash from the resulting string, unless it is the root directory.
  497. =head3 catfile (@directories, $pathname)
  498. Concatenate one or more directory names and a filename to form a complete
  499. path, ending with a filename. If C<$pathname> contains directories, they
  500. will be splitted off to the end of C<@directories>.
  501. =cut
  502. sub catfile {
  503. my $pathname = pop;
  504. return File::Spec::Functions::catfile (
  505. (grep {defined and length} @_), splitdir($pathname)
  506. )
  507. }
  508. =head3 catpath ($volume, $directory, $filename)
  509. XXX Undocumented - See File::Spec
  510. =head3 devnull ()
  511. Return a file name suitable for reading, and guaranteed to be empty.
  512. =cut
  513. my $devnull;
  514. sub devnull () {
  515. IS_WIN32 ? ($devnull ||= tmpfile('', UNLINK => 1))
  516. : File::Spec::Functions::devnull();
  517. }
  518. =head3 get_anchor ($need_target, @paths)
  519. Returns the (anchor, target) pairs for native path @paths. Discard
  520. the targets being returned unless $need_target.
  521. =cut
  522. sub get_anchor {
  523. my $need_target = shift;
  524. map {
  525. my ($volume, $anchor, $target) = splitpath ($_);
  526. chop $anchor if length ($anchor) > 1;
  527. ($volume.$anchor, $need_target ? ($target) : ())
  528. } @_;
  529. }
  530. =head3 get_depot_anchor ($need_target, @paths)
  531. Returns the (anchor, target) pairs for depotpaths @paths. Discard the
  532. targets being returned unless $need_target.
  533. =cut
  534. sub get_depot_anchor {
  535. my $need_target = shift;
  536. map {
  537. my (undef, $anchor, $target) = File::Spec::Unix->splitpath ($_);
  538. chop $anchor if length ($anchor) > 1;
  539. ($anchor, $need_target ? ($target) : ())
  540. } @_;
  541. }
  542. =head3 catdepot ($depot_name, @paths)
  543. =cut
  544. sub catdepot {
  545. return File::Spec::Unix->catdir('/', @_);
  546. }
  547. =head3 make_path ($path)
  548. Create a directory, and intermediate directories as required.
  549. =cut
  550. sub make_path {
  551. my $path = shift;
  552. return undef if !defined($path) or -d $path;
  553. require File::Path;
  554. my @ret = eval { File::Path::mkpath([$path]) };
  555. if ($@) {
  556. $@ =~ s/ at .*//;
  557. die $@;
  558. }
  559. return @ret;
  560. }
  561. =head3 splitpath ($path)
  562. Splits a path in to volume, directory, and filename portions. On systems
  563. with no concept of volume, returns an empty string for volume.
  564. =head3 splitdir ($path)
  565. The opposite of C<catdir()>; return a list of path components.
  566. =head3 tmpdir ()
  567. Return the name of the first writable directory from a list of possible
  568. temporary directories.
  569. =head3 tmpfile (TEXT => $is_textmode, %args)
  570. In scalar context, return the filehandle of a temporary file.
  571. In list context, return the filehandle and the filename.
  572. If C<$is_textmode> is true, the returned file handle is marked with
  573. C<TEXT_MODE>.
  574. See L<File::Temp> for valid keys of C<%args>.
  575. =cut
  576. sub tmpfile {
  577. my ($temp, %args) = @_;
  578. my $dir = tmpdir;
  579. my $text = delete $args{TEXT};
  580. $temp = "svk-${temp}XXXXX";
  581. require File::Temp;
  582. return File::Temp::mktemp ("$dir/$temp") if exists $args{OPEN} && $args{OPEN} == 0;
  583. my $tmp = File::Temp->new ( TEMPLATE => $temp,
  584. DIR => $dir,
  585. SUFFIX => '.tmp',
  586. %args
  587. );
  588. binmode($tmp, TEXT_MODE) if $text;
  589. return wantarray ? ($tmp, $tmp->filename) : $tmp;
  590. }
  591. =head3 is_symlink ($filename)
  592. Return whether a file is a symbolic link, as determined by C<-l>.
  593. If C<$filename> is not specified, return C<-l _> instead.
  594. =cut
  595. sub is_symlink {
  596. HAS_SYMLINK ? @_ ? (-l $_[0]) : (-l _) : 0;
  597. }
  598. =head3 is_executable ($filename)
  599. Return whether a file is likely to be an executable file.
  600. Unlike C<is_symlink()>, the C<$filename> argument is not optional.
  601. =cut
  602. sub is_executable {
  603. require ExtUtils::MakeMaker;
  604. defined($_[0]) and length($_[0]) and MM->maybe_command($_[0]);
  605. }
  606. =head3 can_run ($filename)
  607. Check if we can run some command.
  608. =cut
  609. sub can_run {
  610. my ($_cmd, @path) = @_;
  611. return $_cmd if (-x $_cmd or $_cmd = is_executable($_cmd));
  612. for my $dir ((split /$Config::Config{path_sep}/, $ENV{PATH}), @path, '.') {
  613. my $abs = catfile($dir, $_[0]);
  614. next if -d $abs;
  615. return $abs if (-x $abs or $abs = is_executable($abs));
  616. }
  617. return;
  618. }
  619. =head3 is_uri ($string)
  620. Check if a string is a valid URI.
  621. =cut
  622. sub is_uri {
  623. ($_[0] =~ /^[A-Za-z][-+.A-Za-z0-9]+:/)
  624. }
  625. =head3 move_path ($source, $target)
  626. Move a path to another place, creating intermediate directories in the target
  627. path if neccessary. If move failed, tell the user to move it manually.
  628. =cut
  629. sub move_path {
  630. my ($source, $target) = @_;
  631. if (-d $source and (!-d $target or rmdir($target))) {
  632. require File::Copy;
  633. make_path (dirname($target));
  634. File::Copy::move ($source => $target) and return;
  635. }
  636. $logger->error(loc(
  637. "Cannot rename %1 to %2; please move it manually.",
  638. catfile($source), catfile($target),
  639. ));
  640. }
  641. =head3 traverse_history (root => $fs_root, path => $path,
  642. cross => $cross, callback => $cb($path, $revision))
  643. Traverse the history of $path in $fs_root backwards until the first
  644. copy, unless $cross is true. We do cross renames regardless of the
  645. value of $cross being non-zero, but not -1. We invoke $cb for each
  646. $path, $revision we encounter. If cb returns a nonzero value we stop
  647. traversing as well.
  648. =cut
  649. sub traverse_history {
  650. my %args = @_;
  651. my $old_pool = SVN::Pool->new;
  652. my $new_pool = SVN::Pool->new;
  653. my $spool = SVN::Pool->new_default;
  654. my ($root, $path) = @args{qw/root path/};
  655. # If the root is txn root, get a similar one.
  656. # XXX: We actually want to move this to SVK::Path::, and
  657. # svk::checkout should respect copies on checkout
  658. if ($root->can('txn') && $root->txn) {
  659. ($root, $path) = $root->get_revision_root
  660. ($path, $root->txn->base_revision );
  661. }
  662. my $hist = $root->node_history ($path, $old_pool);
  663. my $rv;
  664. my $revision;
  665. while (1) {
  666. my $ohist = $hist;
  667. $hist = $hist->prev(max(0, $args{cross} || 0), $new_pool);
  668. if (!$hist) {
  669. last if $args{cross};
  670. last unless $hist = $ohist->prev((1), $new_pool);
  671. # We are not supposed to cross copies, ($path,$revision)
  672. # refers to a node in $ohist that is a copy and that has a
  673. # prev if we ask svn to traverse copies.
  674. # Let's find out if the copy was actually a rename instead
  675. # of a copy.
  676. my $root = $root->fs->revision_root($revision, $spool);
  677. my $frompath;
  678. my $fromrev = -1;
  679. # We know that $path was a real copy and it that it has a
  680. # prev, so find the node from which it was copied.
  681. do {
  682. ($fromrev, $frompath) = $root->copied_from($path, $spool);
  683. } until ($fromrev >= 0 || !($path =~ s{/[^/]*$}{}));
  684. die "Assertion failed: $path in $revision isn't a copy."
  685. if $fromrev < 0;
  686. # Ok, $path in $root was a copy of ($frompath,$fromrev).
  687. # If $frompath was deleted in $root then the copy was really
  688. # a rename.
  689. my $entry = $root->paths_changed($spool)->{$frompath};
  690. last unless $entry &&
  691. $entry->change_kind == $SVN::Fs::PathChange::delete;
  692. # XXX Do we need to worry about a parent of $frompath having
  693. # been deleted instead? If so the 2 lines below might work as
  694. # an alternative, to the previous 3 lines. However this also
  695. # treats a delete followed by a copy of an older revision in
  696. # two separate commits as a rename, which technically it's not.
  697. #last unless $root->check_path($frompath, $spool) ==
  698. # $SVN::Node::none;
  699. }
  700. ($path, $revision) = $hist->location ($new_pool);
  701. $old_pool->clear;
  702. $rv = $args{callback}->($path, $revision);
  703. last if !$rv;
  704. $spool->clear;
  705. ($old_pool, $new_pool) = ($new_pool, $old_pool);
  706. }
  707. return $rv;
  708. }
  709. sub reformat_svn_date {
  710. my ($format, $svn_date) = @_;
  711. return time2str($format, str2time($svn_date));
  712. }
  713. sub str2time {
  714. require Time::Local;
  715. my ($year, $month, $day, $hh, $mm, $ss) = split /[-T:]/, $_[0];
  716. $year -= 1900;
  717. $month--;
  718. chop($ss); # remove the 'Z'
  719. my $zone = 0; # UTC
  720. my @lt = localtime(time);
  721. my $frac = $ss - int($ss);
  722. $ss = int $ss;
  723. for ( $year, $month, $day, $hh, $mm, $ss ) {
  724. return undef unless defined($_)
  725. }
  726. return undef
  727. unless ( $month <= 11
  728. && $day >= 1
  729. && $day <= 31
  730. && $hh <= 23
  731. && $mm <= 59
  732. && $ss <= 59 );
  733. my $result;
  734. $result = eval {
  735. local $SIG{__DIE__} = sub { }; # Ick!
  736. Time::Local::timegm( $ss, $mm, $hh, $day, $month, $year );
  737. };
  738. return undef
  739. if !defined $result
  740. or $result == -1
  741. && join( "", $ss, $mm, $hh, $day, $month, $year ) ne "595923311169";
  742. return $result + $frac;
  743. }
  744. sub time2str {
  745. my ($format, $time) = @_;
  746. if (IS_WIN32) {
  747. require Date::Format;
  748. goto \&Date::Format::time2str;
  749. }
  750. require POSIX;
  751. return POSIX::strftime($format, localtime($time) );
  752. }
  753. sub find_dotsvk {
  754. require Cwd;
  755. require Path::Class;
  756. my $p = Path::Class::Dir->new( Cwd::cwd() );
  757. my $prev = "not $p";
  758. my $found = q{};
  759. while ( $p && $p ne $prev && -r $p ) {
  760. $prev = $p;
  761. my $svk = $p->subdir('.svk');
  762. return $svk if -e $svk && -e $svk->file('floating');
  763. $p = $p->parent();
  764. }
  765. return
  766. }
  767. =head3 is_path_inside($path, $parent)
  768. Returns true if unix path C<$path> is inside C<$parent>.
  769. If they are the same, return true as well.
  770. =cut
  771. sub is_path_inside {
  772. my ($path, $parent) = @_;
  773. return 1 if $path eq $parent;
  774. return substr ($path, 0, length ($parent)+1) eq "$parent/";
  775. }
  776. =head3 uri_escape($uri)
  777. Returns escaped URI.
  778. =cut
  779. sub uri_escape {
  780. my ($uri) = @_;
  781. $uri =~ s/([^0-9A-Za-z@%+\-\/:_.!~*'()])/sprintf("%%%02X", ord($1))/eg;
  782. return $uri;
  783. }
  784. =head3 uri_unescape($uri)
  785. Unescape escaped URI and return it.
  786. =cut
  787. sub uri_unescape {
  788. my ($uri) = @_;
  789. $uri =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
  790. return $uri;
  791. }
  792. =head3 is_depotpath($path)
  793. Check if a string is a valid depotpath.
  794. =cut
  795. sub is_depotpath {
  796. ($_[0] =~ m|^/([^/]*)(/.*?)/?$|)
  797. }
  798. 1;
  799. __END__