git-svn: add join_paths() to safely concatenate paths
[gitweb.git] / perl / Git / SVN / Utils.pm
index 496006bc7b3b9492f2747f689a8cdda411eb5806..4005da9d7d08c4119c6a0b39c7bb6eb716581518 100644 (file)
@@ -3,9 +3,17 @@ package Git::SVN::Utils;
 use strict;
 use warnings;
 
+use SVN::Core;
+
 use base qw(Exporter);
 
-our @EXPORT_OK = qw(fatal can_compress);
+our @EXPORT_OK = qw(
+       fatal
+       can_compress
+       canonicalize_path
+       canonicalize_url
+       join_paths
+);
 
 
 =head1 NAME
@@ -56,4 +64,106 @@ sub can_compress {
 }
 
 
+=head3 canonicalize_path
+
+    my $canoncalized_path = canonicalize_path($path);
+
+Converts $path into a canonical form which is safe to pass to the SVN
+API as a file path.
+
+=cut
+
+# Turn foo/../bar into bar
+sub _collapse_dotdot {
+       my $path = shift;
+
+       1 while $path =~ s{/[^/]+/+\.\.}{};
+       1 while $path =~ s{[^/]+/+\.\./}{};
+       1 while $path =~ s{[^/]+/+\.\.}{};
+
+       return $path;
+}
+
+
+sub canonicalize_path {
+       my ($path) = @_;
+       my $dot_slash_added = 0;
+       if (substr($path, 0, 1) ne "/") {
+               $path = "./" . $path;
+               $dot_slash_added = 1;
+       }
+       # File::Spec->canonpath doesn't collapse x/../y into y (for a
+       # good reason), so let's do this manually.
+       $path =~ s#/+#/#g;
+       $path =~ s#/\.(?:/|$)#/#g;
+       $path = _collapse_dotdot($path);
+       $path =~ s#/$##g;
+       $path =~ s#^\./## if $dot_slash_added;
+       $path =~ s#^/##;
+       $path =~ s#^\.$##;
+       return $path;
+}
+
+
+=head3 canonicalize_url
+
+    my $canonicalized_url = canonicalize_url($url);
+
+Converts $url into a canonical form which is safe to pass to the SVN
+API as a URL.
+
+=cut
+
+sub canonicalize_url {
+       my $url = shift;
+
+       # The 1.7 way to do it
+       if ( defined &SVN::_Core::svn_uri_canonicalize ) {
+               return SVN::_Core::svn_uri_canonicalize($url);
+       }
+       # There wasn't a 1.6 way to do it, so we do it ourself.
+       else {
+               return _canonicalize_url_ourselves($url);
+       }
+}
+
+
+sub _canonicalize_url_ourselves {
+       my ($url) = @_;
+       $url =~ s#^([^:]+://[^/]*/)(.*)$#$1 . canonicalize_path($2)#e;
+       return $url;
+}
+
+
+=head3 join_paths
+
+    my $new_path = join_paths(@paths);
+
+Appends @paths together into a single path.  Any empty paths are ignored.
+
+=cut
+
+sub join_paths {
+       my @paths = @_;
+
+       @paths = grep { defined $_ && length $_ } @paths;
+
+       return '' unless @paths;
+       return $paths[0] if @paths == 1;
+
+       my $new_path = shift @paths;
+       $new_path =~ s{/+$}{};
+
+       my $last_path = pop @paths;
+       $last_path =~ s{^/+}{};
+
+       for my $path (@paths) {
+               $path =~ s{^/+}{};
+               $path =~ s{/+$}{};
+               $new_path .= "/$path";
+       }
+
+       return $new_path .= "/$last_path";
+}
+
 1;