git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH 4/7] Add join_paths() to safely concatenate paths.

From
Michael G. Schwern <schwern@pobox.com>
Date
Jul 28, 2012, 09:38 UTC
Message-ID
<1343468312-72024-5-git-send-email-schwern@pobox.com>
In-Reply-To
<1343468312-72024-1-git-send-email-schwern@pobox.com>
From: "Michael G. Schwern" <schwern@pobox.com>
Otherwise you might wind up with things like...
    my $path1 = undef;
    my $path2 = 'foo';
    my $path = $path1 . '/' . $path2;
creating '/foo'.  Or this...
    my $path1 = 'foo/';
    my $path2 = 'bar';
    my $path = $path1 . '/' . $path2;
creating 'foo//bar'.
Could have used File::Spec, but I'm shying away from it due to SVN
1.7's pickiness about paths.  Felt it would be better to have our own
we can control completely.
---
 git-svn.perl                 |  3 ++-
 perl/Git/SVN.pm              | 10 ++++++----
 perl/Git/SVN/Utils.pm        | 32 ++++++++++++++++++++++++++++++++
 t/Git-SVN/Utils/join_paths.t | 32 ++++++++++++++++++++++++++++++++
 4 files changed, 72 insertions(+), 5 deletions(-)
 create mode 100644 t/Git-SVN/Utils/join_paths.t
diff --git a/git-svn.perl b/git-svn.perl
index a857484..6e3e240 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -34,6 +34,7 @@ use Git::SVN::Utils qw(
 	can_compress
 	canonicalize_path
 	canonicalize_url
+	join_paths
 );
 
 use Git qw(
@@ -1275,7 +1276,7 @@ sub get_svnprops {
 	$path = $cmd_dir_prefix . $path;
 	fatal("No such file or directory: $path") unless -e $path;
 	my $is_dir = -d $path ? 1 : 0;
-	$path = $gs->{path} . '/' . $path;
+	$path = join_paths($gs->{path}, $path);
 
 	# canonicalize the path (otherwise libsvn will abort or fail to
 	# find the file)
diff --git a/perl/Git/SVN.pm b/perl/Git/SVN.pm
index 7913d8f..b0ed3ea 100644
--- a/perl/Git/SVN.pm
+++ b/perl/Git/SVN.pm
@@ -23,7 +23,11 @@ use Git qw(
     command_output_pipe
     command_close_pipe
 );
-use Git::SVN::Utils qw(fatal can_compress);
+use Git::SVN::Utils qw(
+	fatal
+	can_compress
+	join_paths
+);
 
 my $can_use_yaml;
 BEGIN {
@@ -316,9 +320,7 @@ sub init_remote_config {
 			}
 			my $old_path = $self->path;
 			$url =~ s!^\Q$min_url\E(/|$)!!;
-			if (length $old_path) {
-				$url .= "/$old_path";
-			}
+			$url = join_paths($url, $old_path);
 			$self->path($url);
 			$url = $min_url;
 		}
diff --git a/perl/Git/SVN/Utils.pm b/perl/Git/SVN/Utils.pm
index 7314e52..deade07 100644
--- a/perl/Git/SVN/Utils.pm
+++ b/perl/Git/SVN/Utils.pm
@@ -12,6 +12,7 @@ our @EXPORT_OK = qw(
 	can_compress
 	canonicalize_path
 	canonicalize_url
+	join_paths
 );
 
 
@@ -134,4 +135,35 @@ sub _canonicalize_url_ourselves {
 }
 
 
+=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;
diff --git a/t/Git-SVN/Utils/join_paths.t b/t/Git-SVN/Utils/join_paths.t
new file mode 100644
index 0000000..d4488e7
--- /dev/null
+++ b/t/Git-SVN/Utils/join_paths.t
@@ -0,0 +1,32 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+
+use Test::More 'no_plan';
+
+use Git::SVN::Utils qw(
+	join_paths
+);
+
+# A reference cannot be a hash key, so we use an array.
+my @tests = (
+	[]					=> '',
+	["/x.com", "bar"]			=> '/x.com/bar',
+	["x.com", ""]				=> 'x.com',
+	["/x.com/foo/", undef, "bar"]		=> '/x.com/foo/bar',
+	["x.com/foo/", "/bar/baz/"]		=> 'x.com/foo/bar/baz/',
+	["foo", "bar"]				=> 'foo/bar',
+	["/foo/bar", "baz", "/biff"]		=> '/foo/bar/baz/biff',
+	["", undef, "."]			=> '.',
+	[]					=> '',
+
+);
+
+while(@tests) {
+	my($have, $want) = splice @tests, 0, 2;
+
+	my $args = join ", ", map { qq['$_'] } map { defined($_) ? $_ : 'undef' } @$have;
+	my $name = "join_paths($args) eq '$want'";
+	is join_paths(@$have), $want, $name;
+}
-- 
1.7.11.3
Previous: Jonathan NiederNext: Jonathan Nieder
Message 25 of 39 in “Canonicalize the git-svn path & url accessors”
  1. Michael G. SchwernJul 28, 2012
  2. 1/7 Move the canonicalization functions to Git::SVN::UtilsMichael G. Schwern, Jul 28, 2012
  3. 2/7 Change canonicalize_url() to use the SVN 1.7 API when available.Michael G. Schwern, Jul 28, 2012
  4. Jonathan NiederJul 28, 2012
  5. Michael G SchwernJul 28, 2012
  6. Jonathan NiederJul 28, 2012
  7. Michael G SchwernJul 28, 2012
  8. Jonathan NiederJul 28, 2012
  9. Jonathan NiederJul 28, 2012
  10. Michael G SchwernJul 28, 2012
  11. 0/2 Re: [PATCH 2/7] Change canonicalize_url() to use the SVN 1.7 API when available.Jonathan Nieder, Oct 14, 2012
  12. 1/2 git svn: do not overescape URLs (fallback case)Jonathan Nieder, Oct 14, 2012
  13. 2/2 git svn: canonicalize_url(): use svn_path_canonicalize when availableJonathan Nieder, Oct 14, 2012
  14. Eric WongOct 23, 2012
  15. 3/7 Extract, test and enhance the logic to collapse ../foo paths.Michael G. Schwern, Jul 28, 2012
  16. Eric WongJul 30, 2012
  17. Michael G SchwernJul 30, 2012
  18. Jonathan NiederSep 26, 2012
  19. Eric WongSep 26, 2012
  20. Jonathan NiederSep 26, 2012
  21. Eric WongSep 26, 2012
  22. Jonathan NiederSep 26, 2012
  23. Eric WongSep 27, 2012
  24. Jonathan NiederSep 27, 2012
  25. 4/7 Add join_paths() to safely concatenate paths.Michael G. Schwern, Jul 28, 2012
  26. Jonathan NiederSep 26, 2012
  27. 5/7 Remove irrelevant comment.Michael G. Schwern, Jul 28, 2012
  28. 6/7 Switch path canonicalization to use the SVN API.Michael G. Schwern, Jul 28, 2012
  29. Jonathan NiederJul 28, 2012
  30. Michael G SchwernJul 28, 2012
  31. Eric WongJul 30, 2012
  32. Eric WongAug 2, 2012
  33. Michael G SchwernAug 2, 2012
  34. git-svn: keep leading slash when canonicalizing paths (fallback case)Jonathan Nieder, Oct 5, 2012
  35. Eric WongOct 5, 2012
  36. Junio C HamanoOct 6, 2012
  37. 7/7 Make Git::SVN and Git::SVN::Ra canonicalize paths and urls.Michael G. Schwern, Jul 28, 2012
  38. Jonathan NiederJul 28, 2012
  39. Michael G SchwernJul 28, 2012

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.