Skip to content

Commit df9a165

Browse files
authored
Fix problems with application path and installation directory resolution (#951)
When we introduced the rename warning in #551, there was broken logic for resolving the actual invoked program name on Mac and Linux. **Windows** We continue to use `CommandLineToArgvW(GetCommandLine(), ..)` to return the _absolute, full path_ to the entry executable. **macOS** Switch from `_NSGetArgv()` to `_NSGetExecutablePath(..)` which, like the Windows APIs above, returns the full path to the entry executable (or symlink). **Linux** As there is no equivalent to `CommandLineToArgvW` or `_NSGetExecutablePath` here, we instead do some path computation based on the form of `argv[0]` that we get from `/proc/self/cmdline`. - If the value is an absolute path, just use that. - If the value is relative to the current directory (`./name`) then combine this with the current directory. - If the value contains a directory separator (`dir/name`) then also resolve this from the current directory. - Otherwise, this `argv[0]` value must have been a file name (`name`) resolved from the `$PATH`. If we still don't manage to resolve from the `$PATH`, try and resolve the symlink `/proc/self/exe` that points to the executable image that was loaded from disk. Note that we may miss any intermediate link names here, but it's better than nothing. --- In addition, we also never tested the .NET Tool scenario after updating the `ICommandContext.ApplicationPath` to use the entry executable name, whereas previously this was computed from the .NET assembly path. Introduce a separate concept, the `InstallationDirectory` that always points to the home of the core assemblies. This is used for resolving things like in-box UI helpers.
2 parents 73654ae + 322ef98 commit df9a165

File tree

39 files changed

+646
-92
lines changed

39 files changed

+646
-92
lines changed

build/GCM.MSBuild.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>netstandard2.0</TargetFramework>
4+
<TargetFramework>net6.0</TargetFramework>
55
<IncludeBuildOutput>false</IncludeBuildOutput>
66
</PropertyGroup>
77

src/shared/Atlassian.Bitbucket.UI.Avalonia/Program.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ private static void AppMain(object o)
4646
string[] args = (string[]) o;
4747

4848
string appPath = ApplicationBase.GetEntryApplicationPath();
49-
using (var context = new CommandContext(appPath))
49+
string installDir = ApplicationBase.GetInstallationDirectory();
50+
using (var context = new CommandContext(appPath, installDir))
5051
using (var app = new HelperApplication(context))
5152
{
5253
app.RegisterCommand(new CredentialsCommandImpl(context));

src/shared/Atlassian.Bitbucket.UI/Atlassian.Bitbucket.UI.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>netstandard2.0</TargetFramework>
4+
<TargetFrameworks>net6.0</TargetFrameworks>
5+
<TargetFrameworks Condition="'$(OSPlatform)'=='windows'">net6.0;net472</TargetFrameworks>
56
<RootNamespace>Atlassian.Bitbucket.UI</RootNamespace>
67
<AssemblyName>Atlassian.Bitbucket.UI.Shared</AssemblyName>
78
</PropertyGroup>

src/shared/Atlassian.Bitbucket/Atlassian.Bitbucket.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>netstandard2.0</TargetFrameworks>
5-
<TargetFrameworks Condition="'$(OSPlatform)'=='windows'">netstandard2.0;net472</TargetFrameworks>
4+
<TargetFrameworks>net6.0</TargetFrameworks>
5+
<TargetFrameworks Condition="'$(OSPlatform)'=='windows'">net6.0;net472</TargetFrameworks>
66
<AssemblyName>Atlassian.Bitbucket</AssemblyName>
77
<RootNamespace>Atlassian.Bitbucket</RootNamespace>
88
<IsTestProject>false</IsTestProject>
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using System.IO;
2+
using GitCredentialManager.Interop.Linux;
3+
using Xunit;
4+
using static GitCredentialManager.Tests.TestUtils;
5+
6+
namespace GitCredentialManager.Tests.Interop.Linux
7+
{
8+
public class LinuxFileSystemTests
9+
{
10+
[PlatformFact(Platforms.Linux)]
11+
public static void LinuxFileSystem_IsSamePath_SamePath_ReturnsTrue()
12+
{
13+
var fs = new LinuxFileSystem();
14+
15+
string baseDir = GetTempDirectory();
16+
string fileA = CreateFile(baseDir, "a.file");
17+
18+
Assert.True(fs.IsSamePath(fileA, fileA));
19+
}
20+
21+
[PlatformFact(Platforms.Linux)]
22+
public static void LinuxFileSystem_IsSamePath_DifferentFile_ReturnsFalse()
23+
{
24+
var fs = new LinuxFileSystem();
25+
26+
string baseDir = GetTempDirectory();
27+
string fileA = CreateFile(baseDir, "a.file");
28+
string fileB = CreateFile(baseDir, "b.file");
29+
30+
Assert.False(fs.IsSamePath(fileA, fileB));
31+
Assert.False(fs.IsSamePath(fileB, fileA));
32+
}
33+
34+
[PlatformFact(Platforms.Linux)]
35+
public static void LinuxFileSystem_IsSamePath_SameFileDifferentCase_ReturnsFalse()
36+
{
37+
var fs = new LinuxFileSystem();
38+
39+
string baseDir = GetTempDirectory();
40+
string fileA1 = CreateFile(baseDir, "a.file");
41+
string fileA2 = Path.Combine(baseDir, "A.file");
42+
43+
Assert.False(fs.IsSamePath(fileA1, fileA2));
44+
Assert.False(fs.IsSamePath(fileA2, fileA1));
45+
}
46+
47+
[PlatformFact(Platforms.Linux)]
48+
public static void LinuxFileSystem_IsSamePath_SameFileDifferentPathNormalization_ReturnsTrue()
49+
{
50+
var fs = new LinuxFileSystem();
51+
52+
string baseDir = GetTempDirectory();
53+
string subDir = CreateDirectory(baseDir, "subDir1", "subDir2");
54+
string fileA1 = CreateFile(baseDir, "a.file");
55+
string fileA2 = Path.Combine(subDir, "..", "..", "a.file");
56+
57+
Assert.True(fs.IsSamePath(fileA1, fileA2));
58+
Assert.True(fs.IsSamePath(fileA2, fileA1));
59+
}
60+
61+
[PlatformFact(Platforms.Linux)]
62+
public static void LinuxFileSystem_IsSamePath_SameFileViaSymlink_ReturnsTrue()
63+
{
64+
var fs = new LinuxFileSystem();
65+
66+
string baseDir = GetTempDirectory();
67+
string fileA1 = CreateFile(baseDir, "a.file");
68+
string fileA2 = CreateFileSymlink(baseDir, "a.link", fileA1);
69+
70+
Assert.True(fs.IsSamePath(fileA1, fileA2));
71+
Assert.True(fs.IsSamePath(fileA2, fileA1));
72+
}
73+
74+
[PlatformFact(Platforms.Linux)]
75+
public static void LinuxFileSystem_IsSamePath_SameFileRelativePath_ReturnsTrue()
76+
{
77+
var fs = new LinuxFileSystem();
78+
79+
string baseDir = GetTempDirectory();
80+
string fileA1 = CreateFile(baseDir, "a.file");
81+
string fileA2 = "./a.file";
82+
83+
using (ChangeDirectory(baseDir))
84+
{
85+
Assert.True(fs.IsSamePath(fileA1, fileA2));
86+
Assert.True(fs.IsSamePath(fileA2, fileA1));
87+
}
88+
}
89+
}
90+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using System.IO;
2+
using GitCredentialManager.Interop.MacOS;
3+
using Xunit;
4+
using static GitCredentialManager.Tests.TestUtils;
5+
6+
namespace GitCredentialManager.Tests.Interop.MacOS
7+
{
8+
public class MacOSFileSystemTests
9+
{
10+
[PlatformFact(Platforms.MacOS)]
11+
public static void MacOSFileSystem_IsSamePath_SamePath_ReturnsTrue()
12+
{
13+
var fs = new MacOSFileSystem();
14+
15+
string baseDir = GetTempDirectory();
16+
string fileA = CreateFile(baseDir, "a.file");
17+
18+
Assert.True(fs.IsSamePath(fileA, fileA));
19+
}
20+
21+
[PlatformFact(Platforms.MacOS)]
22+
public static void MacOSFileSystem_IsSamePath_DifferentFile_ReturnsFalse()
23+
{
24+
var fs = new MacOSFileSystem();
25+
26+
string baseDir = GetTempDirectory();
27+
string fileA = CreateFile(baseDir, "a.file");
28+
string fileB = CreateFile(baseDir, "b.file");
29+
30+
Assert.False(fs.IsSamePath(fileA, fileB));
31+
Assert.False(fs.IsSamePath(fileB, fileA));
32+
}
33+
34+
[PlatformFact(Platforms.MacOS)]
35+
public static void MacOSFileSystem_IsSamePath_SameFileDifferentCase_ReturnsTrue()
36+
{
37+
var fs = new MacOSFileSystem();
38+
39+
string baseDir = GetTempDirectory();
40+
string fileA1 = CreateFile(baseDir, "a.file");
41+
string fileA2 = Path.Combine(baseDir, "A.file");
42+
43+
Assert.True(fs.IsSamePath(fileA1, fileA2));
44+
Assert.True(fs.IsSamePath(fileA2, fileA1));
45+
}
46+
47+
[PlatformFact(Platforms.MacOS)]
48+
public static void MacOSFileSystem_IsSamePath_SameFileDifferentPathNormalization_ReturnsTrue()
49+
{
50+
var fs = new MacOSFileSystem();
51+
52+
string baseDir = GetTempDirectory();
53+
string subDir = CreateDirectory(baseDir, "subDir1", "subDir2");
54+
string fileA1 = CreateFile(baseDir, "a.file");
55+
string fileA2 = Path.Combine(subDir, "..", "..", "a.file");
56+
57+
Assert.True(fs.IsSamePath(fileA1, fileA2));
58+
Assert.True(fs.IsSamePath(fileA2, fileA1));
59+
}
60+
61+
[PlatformFact(Platforms.MacOS)]
62+
public static void MacOSFileSystem_IsSamePath_SameFileViaSymlink_ReturnsTrue()
63+
{
64+
var fs = new MacOSFileSystem();
65+
66+
string baseDir = GetTempDirectory();
67+
string fileA1 = CreateFile(baseDir, "a.file");
68+
string fileA2 = CreateFileSymlink(baseDir, "a.link", fileA1);
69+
70+
Assert.True(fs.IsSamePath(fileA1, fileA2));
71+
Assert.True(fs.IsSamePath(fileA2, fileA1));
72+
}
73+
74+
[PlatformFact(Platforms.MacOS)]
75+
public static void MacOSFileSystem_IsSamePath_SameFileRelativePath_ReturnsTrue()
76+
{
77+
var fs = new MacOSFileSystem();
78+
79+
string baseDir = GetTempDirectory();
80+
string fileA1 = CreateFile(baseDir, "a.file");
81+
string fileA2 = "./a.file";
82+
83+
using (ChangeDirectory(baseDir))
84+
{
85+
Assert.True(fs.IsSamePath(fileA1, fileA2));
86+
Assert.True(fs.IsSamePath(fileA2, fileA1));
87+
}
88+
}
89+
}
90+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System.IO;
2+
using GitCredentialManager.Interop.Posix;
3+
using Xunit;
4+
using static GitCredentialManager.Tests.TestUtils;
5+
6+
namespace GitCredentialManager.Tests.Interop.Posix
7+
{
8+
public class PosixFileSystemTests
9+
{
10+
[PlatformFact(Platforms.Posix)]
11+
public void PosixFileSystem_ResolveSymlinks_FileLinks()
12+
{
13+
string baseDir = GetTempDirectory();
14+
string realPath = CreateFile(baseDir, "realFile.txt");
15+
string linkPath = CreateFileSymlink(baseDir, "linkFile.txt", realPath);
16+
17+
string actual = PosixFileSystem.ResolveSymbolicLinks(linkPath);
18+
19+
Assert.Equal(realPath, actual);
20+
}
21+
22+
[PlatformFact(Platforms.Posix)]
23+
public void PosixFileSystem_ResolveSymlinks_DirectoryLinks()
24+
{
25+
//
26+
// Create a real file inside of a directory that is a symlink
27+
// to another directory.
28+
//
29+
// /tmp/{uuid}/linkDir/ -> /tmp/{uuid}/realDir/
30+
//
31+
string baseDir = GetTempDirectory();
32+
string realDir = CreateDirectory(baseDir, "realDir");
33+
string linkDir = CreateDirectorySymlink(baseDir, "linkDir", realDir);
34+
string filePath = CreateFile(linkDir, "file.txt");
35+
36+
string actual = PosixFileSystem.ResolveSymbolicLinks(filePath);
37+
38+
string expected = Path.Combine(realDir, "file.txt");
39+
40+
Assert.Equal(expected, actual);
41+
}
42+
}
43+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
using System.IO;
2+
using GitCredentialManager.Interop.Windows;
3+
using Xunit;
4+
using static GitCredentialManager.Tests.TestUtils;
5+
6+
namespace GitCredentialManager.Tests.Interop.Windows
7+
{
8+
public class WindowsFileSystemTests
9+
{
10+
[PlatformFact(Platforms.Windows)]
11+
public static void WindowsFileSystem_IsSamePath_SamePath_ReturnsTrue()
12+
{
13+
var fs = new WindowsFileSystem();
14+
15+
string baseDir = GetTempDirectory();
16+
string fileA = CreateFile(baseDir, "a.file");
17+
18+
Assert.True(fs.IsSamePath(fileA, fileA));
19+
}
20+
21+
[PlatformFact(Platforms.Windows)]
22+
public static void WindowsFileSystem_IsSamePath_DifferentFile_ReturnsFalse()
23+
{
24+
var fs = new WindowsFileSystem();
25+
26+
string baseDir = GetTempDirectory();
27+
string fileA = CreateFile(baseDir, "a.file");
28+
string fileB = CreateFile(baseDir, "b.file");
29+
30+
Assert.False(fs.IsSamePath(fileA, fileB));
31+
Assert.False(fs.IsSamePath(fileB, fileA));
32+
}
33+
34+
[PlatformFact(Platforms.Windows)]
35+
public static void WindowsFileSystem_IsSamePath_SameFileDifferentCase_ReturnsTrue()
36+
{
37+
var fs = new WindowsFileSystem();
38+
39+
string baseDir = GetTempDirectory();
40+
string fileA1 = CreateFile(baseDir, "a.file");
41+
string fileA2 = Path.Combine(baseDir, "A.file");
42+
43+
Assert.True(fs.IsSamePath(fileA1, fileA2));
44+
Assert.True(fs.IsSamePath(fileA2, fileA1));
45+
}
46+
47+
[PlatformFact(Platforms.Windows)]
48+
public static void WindowsFileSystem_IsSamePath_SameFileDifferentPathNormalization_ReturnsTrue()
49+
{
50+
var fs = new WindowsFileSystem();
51+
52+
string baseDir = GetTempDirectory();
53+
string subDir = CreateDirectory(baseDir, "subDir1", "subDir2");
54+
string fileA1 = CreateFile(baseDir, "a.file");
55+
string fileA2 = Path.Combine(subDir, "..", "..", "a.file");
56+
57+
Assert.True(fs.IsSamePath(fileA1, fileA2));
58+
Assert.True(fs.IsSamePath(fileA2, fileA1));
59+
}
60+
61+
[PlatformFact(Platforms.Windows)]
62+
public static void WindowsFileSystem_IsSamePath_SameFileRelativePath_ReturnsTrue()
63+
{
64+
var fs = new WindowsFileSystem();
65+
66+
string baseDir = GetTempDirectory();
67+
string fileA1 = CreateFile(baseDir, "a.file");
68+
string fileA2 = @".\a.file";
69+
70+
using (ChangeDirectory(baseDir))
71+
{
72+
Assert.True(fs.IsSamePath(fileA1, fileA2));
73+
Assert.True(fs.IsSamePath(fileA2, fileA1));
74+
}
75+
}
76+
}
77+
}

src/shared/Core.UI/Core.UI.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>netstandard2.0</TargetFramework>
4+
<TargetFrameworks>net6.0</TargetFrameworks>
5+
<TargetFrameworks Condition="'$(OSPlatform)'=='windows'">net6.0;net472</TargetFrameworks>
56
<AssemblyName>gcmcoreui</AssemblyName>
67
<RootNamespace>GitCredentialManager.UI</RootNamespace>
78
</PropertyGroup>

src/shared/Core/Application.cs

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ protected override async Task<int> RunInternalAsync(string[] args)
9797
Context.Trace.WriteLine($"Platform: {info.OperatingSystemType} ({info.CpuArchitecture})");
9898
Context.Trace.WriteLine($"OSVersion: {info.OperatingSystemVersion}");
9999
Context.Trace.WriteLine($"AppPath: {Context.ApplicationPath}");
100+
Context.Trace.WriteLine($"InstallDir: {Context.InstallationDirectory}");
100101
Context.Trace.WriteLine($"Arguments: {string.Join(" ", args)}");
101102

102103
var parser = new CommandLineBuilder(rootCommand)
@@ -252,25 +253,13 @@ Task IConfigurableComponent.UnconfigureAsync(ConfigurationTarget target)
252253
}
253254

254255
// Clear app entry
255-
config.UnsetAll(configLevel, helperKey, Regex.Escape(appPath));
256+
string appEntryValue = currentValues[appIndex];
257+
config.UnsetAll(configLevel, helperKey, Regex.Escape(appEntryValue));
256258
}
257259

258260
return Task.CompletedTask;
259261
}
260262

261-
private string GetGitConfigAppName()
262-
{
263-
const string gitCredentialPrefix = "git-credential-";
264-
265-
string appName = Path.GetFileNameWithoutExtension(Context.ApplicationPath);
266-
if (appName != null && appName.StartsWith(gitCredentialPrefix, StringComparison.OrdinalIgnoreCase))
267-
{
268-
return appName.Substring(gitCredentialPrefix.Length);
269-
}
270-
271-
return Context.ApplicationPath;
272-
}
273-
274263
private string GetGitConfigAppPath()
275264
{
276265
string path = Context.ApplicationPath;

0 commit comments

Comments
 (0)