Enable Set-Location -LiteralPath to work with folders named "-" and "+" (#8089)

This commit is contained in:
Steve Lee 2018-10-23 21:37:59 -07:00 committed by Ilya
parent 3e4fa87901
commit 9141b11c5e
4 changed files with 113 additions and 47 deletions

View file

@ -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)
{

View file

@ -190,6 +190,50 @@ namespace System.Management.Automation
return _sessionState.SetLocation(path, context);
} // SetLocation
/// <summary>
/// Changes the current location to the specified path.
/// </summary>
/// <param name="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.
/// </param>
/// <param name="context">
/// The context under which the command is running.
/// </param>
/// <param name="literalPath">
/// Indicates if the path is a literal path.
/// </param>
/// <returns>
/// The path of the new current location.
/// </returns>
/// <exception cref="ArgumentNullException">
/// If <paramref name="path"/> is null.
/// </exception>
/// <exception cref="ArgumentException">
/// If <paramref name="path"/> does not exist, is not a container, or
/// resolved to multiple containers.
/// </exception>
/// <exception cref="ProviderNotFoundException">
/// If <paramref name="path"/> refers to a provider that does not exist.
/// </exception>
/// <exception cref="DriveNotFoundException">
/// If <paramref name="path"/> refers to a drive that does not exist.
/// </exception>
/// <exception cref="ProviderInvocationException">
/// If the provider associated with <paramref name="path"/> threw an
/// exception.
/// </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
/// <summary>
/// Determines if the specified path is the current location or a parent of the current location.
/// </summary>

View file

@ -13,7 +13,7 @@ using Dbg = System.Management.Automation;
namespace System.Management.Automation
{
/// <summary>
/// Holds the state of a Monad Shell session
/// Holds the state of a Monad Shell session.
/// </summary>
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
/// <summary>
/// Changes the current working directory to the path specified
/// Changes the current working directory to the path specified.
/// </summary>
/// <param name="path">
/// The path of the new current working directory
/// The path of the new current working directory.
/// </param>
/// <returns>
/// The PathInfo object representing the path of the location
@ -194,6 +191,47 @@ namespace System.Management.Automation
/// If the <paramref name="path"/> could not be resolved.
/// </exception>
internal PathInfo SetLocation(string path, CmdletProviderContext context)
{
return SetLocation(path, context, literalPath: false);
}
/// <summary>
/// Changes the current working directory to the path specified.
/// </summary>
/// <param name="path">
/// The path of the new current working directory.
/// </param>
/// <param name="context">
/// The context the provider uses when performing the operation.
/// </param>
/// <param name="literalPath">
/// Indicate if the path is a literal path.
/// </param>
/// <returns>
/// The PathInfo object representing the path of the location
/// that was set.
/// </returns>
/// <exception cref="ArgumentNullException">
/// If <paramref name="path"/> is null.
/// </exception>
/// <exception cref="ArgumentException">
/// If <paramref name="path"/> does not exist, is not a container, or
/// resolved to multiple containers.
/// </exception>
/// <exception cref="ProviderNotFoundException">
/// If <paramref name="path"/> refers to a provider that does not exist.
/// </exception>
/// <exception cref="DriveNotFoundException">
/// If <paramref name="path"/> refers to a drive that does not exist.
/// </exception>
/// <exception cref="ProviderInvocationException">
/// If the provider associated with <paramref name="path"/> threw an
/// exception.
/// </exception>
/// <exception cref="ItemNotFoundException">
/// If the <paramref name="path"/> could not be resolved.
/// </exception>
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<PathInfo> _setLocationHistory;
/// <summary>
/// A stack of the most recently pushed locations
/// A stack of the most recently pushed locations.
/// </summary>
private Dictionary<String, Stack<PathInfo>> _workingLocationStack;
private const string startingDefaultStackName = "default";
/// <summary>
/// The name of the default location stack
/// The name of the default location stack.
/// </summary>
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,

View file

@ -165,6 +165,19 @@ Describe "Set-Location" -Tags "CI" {
}
}
It 'Should nativate to literal path "<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 {