As observed in https://github.com/JuliaLang/julia/issues/20695, joinpath causes an error on Windows in situations where a git URL looks like a relative path with a drive specification – since the drives of the two arguments don't match, an error is thrown. That specific problem can be worked around, but the deeper problem is that joinpath throws in the first place. It's fundamentally just a string manipulation function and shouldn't throw errors. However, that leaves the question of what should happen when the second argument to joinpath is a relative with a drive that doesn't match that of the first argument.
More generally, no path manipulation functions that treat paths as strings detached from the file system should throw errors.
it's a function for manipulating paths, that happens to operate on strings. if the inputs aren't valid paths, then shouldn't be calling joinpath.
Yes, leaving surprising booby traps for programmers in seemingly innocuous functions – especially ones that only explode on some platforms – is exactly how we like to design our APIs.
There are two very distinct classes of file/path manipulation functions:
The former should not throw errors – they are fundamentally just data manipulation functions. We already have the example why – an unexpected bug in a function that assumed that calling joinpath on two strings to see if they made a valid path together was a safe operation. It was not.
It can't give a correct answer in this corner case. The only correct answer is that your inputs are erroneous.
It can return an incorrect path which will trigger an error when trying to open the file. That way you only need to catch exceptions in one place rather than two. Anyway a path is generally composed just before being used to access a file.
It can probably just return the second object. Answering the question: "relative to a relative path B on drive A, what is the full path to the relative path C on drive D", is probably most sensibly "the relative path D on drive C" (with the first clause adding no useful information). So joinpath("A:B", "C:D") would reasonably be "C:D". The downside is that the commutativity is wrong, if you treat joinpath as equivalent to a sequence of cd operations, but I think we can live with that. Also, fwiw, that's python's interpretation:
> "C:\Program Files\Python36\python.exe"
Python 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 08:06:12) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> os.path.join("A:B", "C:D")
'C:D'
if you treat joinpath as equivalent to a sequence of cd operations
Also, fwiw, this interpretation is valid generally on nix systems (calling normpath is not information-preserving, calling cd is). But this interpretation is *not valid generally on windows systems (calling normpath is information-preserving, calling cd is not).
We should follow Python here since we've followed them on basically everything else path-related. As @nalimilan points out, if it's not a sane answer then opening the file or checking for it will give the appropriate result (error/does not exist).
Resolved: in the case where joinpath currently throws an error, we should return the second path.
Should anyone with a Windows machine want to do this, the functional change is this:
diff --git a/base/path.jl b/base/path.jl
index c042bef833..bcb04129ee 100644
--- a/base/path.jl
+++ b/base/path.jl
@@ -208,7 +208,7 @@ function joinpath(a::String, b::String)
isabspath(b) && return b
A, a = splitdrive(a)
B, b = splitdrive(b)
- !isempty(B) && A != B && throw(ArgumentError("drive mismatch: $A$a $B$b"))
+ !isempty(B) && A != B && return B
C = isempty(B) ? A : B
isempty(a) ? string(C,b) :
ismatch(path_separator_re, a[end:end]) ? string(C,a,b) :
In addition to that, however, it should have tests for different cases in joinpath, including the one that is changed here, and a NEWS.md entry.