From 9141b11c5e7a498ee44f760fd9735ec165d369e9 Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Tue, 23 Oct 2018 21:37:59 -0700 Subject: [PATCH] Enable Set-Location -LiteralPath to work with folders named "-" and "+" (#8089) --- .../commands/management/Navigation.cs | 2 +- .../engine/PathInterfaces.cs | 44 ++++++++ .../engine/SessionStateLocationAPIs.cs | 101 ++++++++++-------- .../Set-Location.Tests.ps1 | 13 +++ 4 files changed, 113 insertions(+), 47 deletions(-) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Navigation.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Navigation.cs index a58d0963d8..0df8460880 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Navigation.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Navigation.cs @@ -801,7 +801,7 @@ namespace Microsoft.PowerShell.Commands Path = SessionState.Internal.GetSingleProvider(Commands.FileSystemProvider.ProviderName).Home; } - result = SessionState.Path.SetLocation(Path, CmdletProviderContext); + result = SessionState.Path.SetLocation(Path, CmdletProviderContext, ParameterSetName == literalPathSet); } catch (PSNotSupportedException notSupported) { diff --git a/src/System.Management.Automation/engine/PathInterfaces.cs b/src/System.Management.Automation/engine/PathInterfaces.cs index eafa2dc576..3db667b079 100644 --- a/src/System.Management.Automation/engine/PathInterfaces.cs +++ b/src/System.Management.Automation/engine/PathInterfaces.cs @@ -190,6 +190,50 @@ namespace System.Management.Automation return _sessionState.SetLocation(path, context); } // SetLocation + /// + /// Changes the current location to the specified path. + /// + /// + /// The path to change the location to. This can be either a drive-relative or provider-relative + /// path. It cannot be a provider-internal path. + /// + /// + /// The context under which the command is running. + /// + /// + /// Indicates if the path is a literal path. + /// + /// + /// The path of the new current location. + /// + /// + /// If is null. + /// + /// + /// If does not exist, is not a container, or + /// resolved to multiple containers. + /// + /// + /// If refers to a provider that does not exist. + /// + /// + /// If refers to a drive that does not exist. + /// + /// + /// If the provider associated with threw an + /// exception. + /// + internal PathInfo SetLocation(string path, CmdletProviderContext context, bool literalPath) + { + Dbg.Diagnostics.Assert( + _sessionState != null, + "The only constructor for this class should always set the sessionState field"); + + // Parameter validation is done in the session state object + + return _sessionState.SetLocation(path, context, literalPath); + } // SetLocation + /// /// Determines if the specified path is the current location or a parent of the current location. /// diff --git a/src/System.Management.Automation/engine/SessionStateLocationAPIs.cs b/src/System.Management.Automation/engine/SessionStateLocationAPIs.cs index 4b4ee74b75..7237134f16 100644 --- a/src/System.Management.Automation/engine/SessionStateLocationAPIs.cs +++ b/src/System.Management.Automation/engine/SessionStateLocationAPIs.cs @@ -13,7 +13,7 @@ using Dbg = System.Management.Automation; namespace System.Management.Automation { /// - /// Holds the state of a Monad Shell session + /// Holds the state of a Monad Shell session. /// internal sealed partial class SessionStateInternal { @@ -79,7 +79,6 @@ namespace System.Management.Automation } // If namespace ID is empty, we will use the current working drive - PSDriveInfo drive = null; if (namespaceID.Length == 0) @@ -89,7 +88,6 @@ namespace System.Management.Automation else { // First check to see if the provider exists - ProvidersCurrentWorkingDrive.TryGetValue(GetSingleProvider(namespaceID), out drive); } @@ -107,7 +105,6 @@ namespace System.Management.Automation context.Drive = drive; // Now make the namespace specific path - string path = null; if (drive.Hidden) @@ -129,10 +126,10 @@ namespace System.Management.Automation } // GetNamespaceCurrentLocation /// - /// Changes the current working directory to the path specified + /// Changes the current working directory to the path specified. /// /// - /// The path of the new current working directory + /// The path of the new current working directory. /// /// /// The PathInfo object representing the path of the location @@ -194,6 +191,47 @@ namespace System.Management.Automation /// If the could not be resolved. /// internal PathInfo SetLocation(string path, CmdletProviderContext context) + { + return SetLocation(path, context, literalPath: false); + } + + /// + /// Changes the current working directory to the path specified. + /// + /// + /// The path of the new current working directory. + /// + /// + /// The context the provider uses when performing the operation. + /// + /// + /// Indicate if the path is a literal path. + /// + /// + /// The PathInfo object representing the path of the location + /// that was set. + /// + /// + /// If is null. + /// + /// + /// If does not exist, is not a container, or + /// resolved to multiple containers. + /// + /// + /// If refers to a provider that does not exist. + /// + /// + /// If refers to a drive that does not exist. + /// + /// + /// If the provider associated with threw an + /// exception. + /// + /// + /// If the could not be resolved. + /// + internal PathInfo SetLocation(string path, CmdletProviderContext context, bool literalPath) { if (path == null) { @@ -208,18 +246,20 @@ namespace System.Management.Automation switch (originalPath) { - case string originalPathSwitch when originalPathSwitch.Equals("-", StringComparison.OrdinalIgnoreCase): + case string originalPathSwitch when !literalPath && originalPathSwitch.Equals("-", StringComparison.Ordinal): if (_setLocationHistory.UndoCount <= 0) { throw new InvalidOperationException(SessionStateStrings.LocationUndoStackIsEmpty); } + path = _setLocationHistory.Undo(this.CurrentLocation).Path; break; - case string originalPathSwitch when originalPathSwitch.Equals("+", StringComparison.OrdinalIgnoreCase): + case string originalPathSwitch when !literalPath && originalPathSwitch.Equals("+", StringComparison.Ordinal): if (_setLocationHistory.RedoCount <= 0) { throw new InvalidOperationException(SessionStateStrings.LocationRedoStackIsEmpty); } + path = _setLocationHistory.Redo(this.CurrentLocation).Path; break; default: @@ -241,7 +281,6 @@ namespace System.Management.Automation // The path is a provider-direct path so use the current // provider and its hidden drive but don't modify the path // at all. - provider = CurrentLocation.Provider; CurrentDrive = provider.HiddenDrive; } @@ -254,7 +293,6 @@ namespace System.Management.Automation { // See if the path is a relative or absolute // path. - if (Globber.IsAbsolutePath(path, out driveName)) { // Since the path is an absolute path @@ -269,7 +307,7 @@ namespace System.Management.Automation string colonTerminatedVolume = CurrentDrive.Name + ':'; if (CurrentDrive.VolumeSeparatedByColon && (path.Length == colonTerminatedVolume.Length)) { - path = Path.Combine((colonTerminatedVolume + Path.DirectorySeparatorChar), CurrentDrive.CurrentLocation); + path = Path.Combine(colonTerminatedVolume + Path.DirectorySeparatorChar, CurrentDrive.CurrentLocation); } // Now that the current working drive is set, @@ -312,11 +350,10 @@ namespace System.Management.Automation { throw; } - catch (Exception) // Catch-all OK, 3rd party callout + catch (Exception) { // Reset the drive to the previous drive and // then rethrow the error - CurrentDrive = previousWorkingDrive; throw; } @@ -325,7 +362,6 @@ namespace System.Management.Automation { // Set the current working drive back to the previous // one in case it was changed. - CurrentDrive = previousWorkingDrive; throw @@ -336,7 +372,6 @@ namespace System.Management.Automation } // We allow globbing the location as long as it only resolves a single container. - bool foundContainer = false; bool pathIsContainer = false; bool pathIsProviderQualifiedPath = false; @@ -357,12 +392,11 @@ namespace System.Management.Automation { // The path should be the provider-qualified path without the provider ID // or :: - string providerInternalPath = LocationGlobber.RemoveProviderQualifier(resolvedPath.Path); try { - currentPath = NormalizeRelativePath(GetSingleProvider(providerName), providerInternalPath, String.Empty, normalizePathContext); + currentPath = NormalizeRelativePath(GetSingleProvider(providerName), providerInternalPath, string.Empty, normalizePathContext); } catch (NotSupportedException) { @@ -381,11 +415,10 @@ namespace System.Management.Automation { throw; } - catch (Exception) // Catch-all OK, 3rd party callout + catch (Exception) { // Reset the drive to the previous drive and // then rethrow the error - CurrentDrive = previousWorkingDrive; throw; } @@ -413,23 +446,20 @@ namespace System.Management.Automation { throw; } - catch (Exception) // Catch-all OK, 3rd party callout + catch (Exception) { // Reset the drive to the previous drive and // then rethrow the error - CurrentDrive = previousWorkingDrive; throw; } } // Now see if there was errors while normalizing the path - if (normalizePathContext.HasErrors()) { // Set the current working drive back to the previous // one in case it was changed. - CurrentDrive = previousWorkingDrive; normalizePathContext.ThrowFirstErrorOrDoNothing(); @@ -440,8 +470,6 @@ namespace System.Management.Automation normalizePathContext.RemoveStopReferral(); } - // Check to see if the path is a container - bool isContainer = false; CmdletProviderContext itemContainerContext = @@ -459,7 +487,6 @@ namespace System.Management.Automation { // Set the current working drive back to the previous // one in case it was changed. - CurrentDrive = previousWorkingDrive; itemContainerContext.ThrowFirstErrorOrDoNothing(); @@ -472,7 +499,6 @@ namespace System.Management.Automation // Treat this as a container because providers that only // support the ContainerCmdletProvider interface are really // containers at their root. - isContainer = true; } } @@ -486,10 +512,8 @@ namespace System.Management.Automation if (foundContainer) { // The path resolved to more than one container - // Set the current working drive back to the previous // one in case it was changed. - CurrentDrive = previousWorkingDrive; throw @@ -520,7 +544,6 @@ namespace System.Management.Automation { // Remove the root slash since it is implied that the // current working directory is relative to the root. - if (!LocationGlobber.IsProviderDirectPath(path) && path.StartsWith(StringLiterals.DefaultPathSeparatorString, StringComparison.Ordinal) && !pathIsProviderQualifiedPath) @@ -538,7 +561,6 @@ namespace System.Management.Automation { // Set the current working drive back to the previous // one in case it was changed. - CurrentDrive = previousWorkingDrive; throw @@ -550,12 +572,10 @@ namespace System.Management.Automation // Now make sure the current drive is set in the provider's // current working drive hashtable - ProvidersCurrentWorkingDrive[CurrentDrive.Provider] = CurrentDrive; // Set the $PWD variable to the new location - this.SetVariable(SpecialVariables.PWDVarPath, this.CurrentLocation, false, true, CommandOrigin.Internal); // If an action has been defined for location changes, invoke it now. @@ -646,12 +666,10 @@ namespace System.Management.Automation // Check to see if the path that was specified is within the current // working drive - if (drive == CurrentDrive) { // The path needs to be normalized to get rid of relative path tokens // so they don't interfere with our path comparisons below - CmdletProviderContext normalizePathContext = new CmdletProviderContext(context); @@ -689,7 +707,6 @@ namespace System.Management.Automation s_tracer.WriteLine("Provider path = {0}", providerSpecificPath); // Get the current working directory provider specific path - PSDriveInfo currentWorkingDrive = null; ProviderInfo currentDriveProvider = null; @@ -710,7 +727,6 @@ namespace System.Management.Automation // See if the path is the current working directory or a parent // of the current working directory - s_tracer.WriteLine( "Comparing {0} to {1}", providerSpecificPath, @@ -720,7 +736,6 @@ namespace System.Management.Automation { // The path is the current working directory so // return true - s_tracer.WriteLine("The path is the current working directory"); result = true; @@ -729,7 +744,6 @@ namespace System.Management.Automation { // Check to see if the specified path is a parent // of the current working directory - string lockedDirectory = currentWorkingPath; while (lockedDirectory.Length > 0) @@ -738,7 +752,6 @@ namespace System.Management.Automation // as it can even if that means it has to traverse higher // than the mount point for this drive. That is // why we are passing the empty string as the root here. - lockedDirectory = GetParentPath( drive.Provider, @@ -755,7 +768,6 @@ namespace System.Management.Automation { // The path is a parent of the current working // directory - s_tracer.WriteLine( "The path is a parent of the current working directory: {0}", lockedDirectory); @@ -784,13 +796,13 @@ namespace System.Management.Automation private readonly HistoryStack _setLocationHistory; /// - /// A stack of the most recently pushed locations + /// A stack of the most recently pushed locations. /// private Dictionary> _workingLocationStack; private const string startingDefaultStackName = "default"; /// - /// The name of the default location stack + /// The name of the default location stack. /// private string _defaultStackName = startingDefaultStackName; @@ -887,7 +899,6 @@ namespace System.Management.Automation if (WildcardPattern.ContainsWildcardCharacters(stackName)) { // Need to glob the stack name, but it can only glob to a single. - bool haveMatch = false; WildcardPattern stackNamePattern = @@ -948,7 +959,6 @@ namespace System.Management.Automation { // Remove the stack from the stack list if it // no longer contains any paths. - _workingLocationStack.Remove(stackName); } } @@ -990,7 +1000,6 @@ namespace System.Management.Automation { // If the request was for the default stack, but it doesn't // yet exist, create a dummy stack and return it. - if (String.Equals( stackName, startingDefaultStackName, diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Location.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Location.Tests.ps1 index 8228e79bb3..5824419f3b 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Location.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Location.Tests.ps1 @@ -165,6 +165,19 @@ Describe "Set-Location" -Tags "CI" { } } + It 'Should nativate to literal path ""' -TestCases @( + @{ path = "-" }, + @{ path = "+" } + ) { + param($path) + + Set-Location $TestDrive + $literalPath = Join-Path $TestDrive $path + New-Item -ItemType Directory -Path $literalPath + Set-Location -LiteralPath $path + (Get-Location).Path | Should -BeExactly $literalPath + } + Context 'Test the LocationChangedAction event handler' { AfterEach {