Join GitHub today
GitHub is home to over 36 million developers working together to host and review code, manage projects, and build software together.
Sign upAdd location history for Set-Location to enable 'cd -' scenario (issue #2188) #5051
Conversation
bergmeister
requested review from
adityapatwardhan and
daxian-dbw
as
code owners
Oct 7, 2017
This comment has been minimized.
This comment has been minimized.
|
Should it be some per- |
This comment has been minimized.
This comment has been minimized.
|
@PetSerAl Interesting thought and I did not know that the ISE tabs share the same process. +1 for sharing your knowledge. :) |
This comment has been minimized.
This comment has been minimized.
|
As far as I understand, Also PowerShell ISE is just an example of PowerShell host, which use more than one Simply: there are no rule "only one |
This comment has been minimized.
This comment has been minimized.
|
OK. I see what you mean. My assumption was that |
This comment has been minimized.
This comment has been minimized.
Even when |
This comment has been minimized.
This comment has been minimized.
|
I agree with @PetSerAl. See PoshRSJobs for an example of how users can run multiple runspaces in the same process. Rather than make this an environment variable just make it a session (runspace) variable e.g. |
iSazonov
assigned
lzybkr
Oct 9, 2017
This comment has been minimized.
This comment has been minimized.
|
I agree with @PetSerAl too - we should take into account many runspaces. Main question - Do we really need this in scripts? Set-Location -
cd -looks very poorly read. Using Push-Location Pop-Location pair looks more readable and understandable. Next question - Should we keep a history and implement some step back by "cd-; cd-; cd -"? |
This comment has been minimized.
This comment has been minimized.
|
Ok. If you say runspace variable, do you mean we should declare it as an automatic variable or is there a subtle difference? How would one ban its usage in scripts? About the history: the idea is to keep it simple at the moment and another PR could improve it to use a history stack (that is the point about agile development: make something minimal and viable with value that works and then iteratively enhance it) |
This comment has been minimized.
This comment has been minimized.
As an example of this, see the PSCX package. It implements a cd stack that you can backwards navigate with
It also handles two cases that I particularly like:
and
You don't. That isn't something I'd worry about. However there are other CD type packages out there. Some allow you to fuzzy match when CD'ing to directories. |
This comment has been minimized.
This comment has been minimized.
|
I think we should easily implement the history based on "Stack" from the class. |
This comment has been minimized.
This comment has been minimized.
|
I'd prefer a stack as well, though it needs to be bounded. You could start with this code from PSReadline. I don't think it's necessary to prevent the use of |
lzybkr
requested changes
Oct 9, 2017
| @@ -1054,6 +1071,29 @@ protected override void ProcessRecord() | |||
| } | |||
| } // ProcessRecord | |||
|
|
|||
| private string ReplacePathWithLastLocationIfApplicable(string path) | |||
| { | |||
| Environment.SetEnvironmentVariable("DEBUG_PATH", Path); | |||
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
bergmeister
Oct 9, 2017
Author
Contributor
True. Well spotted. This was a development debugging leftover, sorry.
| result = SessionState.Path.SetLocation(Path, CmdletProviderContext); | ||
| Environment.SetEnvironmentVariable(environmentVariableNameForPreviousLocation, initialPath); |
This comment has been minimized.
This comment has been minimized.
lzybkr
Oct 9, 2017
Member
We shouldn't use environment variables, instead we should expose a new variable (PSOldPWD?) that is an instance of a bounded stack.
We could consider a user configurable stack size, but we can leave that for later (not this PR).
I also wonder if this logic should be in SessionState.Path.SetLocation - that could be useful for alternate implementations of Set-Location that call the api.
| result = SessionState.Path.SetDefaultLocationStack(StackName); | ||
| Environment.SetEnvironmentVariable(environmentVariableNameForPreviousLocation, initialPath); |
This comment has been minimized.
This comment has been minimized.
lzybkr
Oct 9, 2017
Member
The previous comments apply here, including moving the logic to SessionState.Path.SetDefaultLocationStack.
| { | ||
| { | ||
| relationshipPath = ReplacePathWithLastLocationIfApplicable(relationshipPath); | ||
| var initialPath = SessionState.Path.CurrentLocation.Path; |
This comment has been minimized.
This comment has been minimized.
lzybkr
Oct 9, 2017
Member
You can revert the changes under RELATIONSHIP_SUPPORTED - the code is dead, and I've submitted a PR to delete it.
This comment has been minimized.
This comment has been minimized.
| result = SessionState.Path.SetLocation (relationshipPath, CmdletProviderContext); | ||
| relationshipPath = ReplacePathWithLastLocationIfApplicable(relationshipPath); | ||
| var initialPath = SessionState.Path.CurrentLocation; | ||
| result = SessionState.Path.SetLocation(relationshipPath, CmdletProviderContext); |
This comment has been minimized.
This comment has been minimized.
lzybkr
Oct 9, 2017
Member
The previous comments apply here, including moving the logic to SessionState.Path.SetLocation. In fact, I think if you did that, the change here wouldn't be necessary.
| @@ -0,0 +1,123 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | |||
This comment has been minimized.
This comment has been minimized.
lzybkr
Oct 9, 2017
Member
I think it's reasonable to put the new string in SessionStateStrings instead of this new file.
This comment has been minimized.
This comment has been minimized.
bergmeister
requested review from
BrucePay and
vors
as
code owners
Oct 10, 2017
This comment has been minimized.
This comment has been minimized.
|
@lzybkr I have implemented your suggestions and used the existing stack implementation that pusdh/popd use under the hood to avoid code duplication. |
iSazonov
reviewed
Oct 11, 2017
|
I don't like side effects - performance decreases, memory consumption increases. |
| It 'Should go to last location when specifying minus as a path using alias' { | ||
| $initialLocation = Get-Location | ||
| Set-Location ([System.IO.Path]::GetTempPath()) | ||
| cd - |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| @@ -916,6 +916,10 @@ protected override void ProcessRecord() | |||
| } | |||
|
|
|||
| result = SessionState.Path.SetLocation(Path, CmdletProviderContext); | |||
| #if UNIX | |||
| // Be consistent with most Unix shells that set the environment variable 'OLDPWD' when changing location | |||
| Environment.SetEnvironmentVariable("OLDPWD", Path); | |||
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
lzybkr
Oct 26, 2017
Member
Agreed - I also don't see the point. Maybe if ps1 files ran in a new process, then it might make sense, but they don't.
And if setting the environment variable was useful, we should explain why it is UNIX only.
This comment has been minimized.
This comment has been minimized.
bergmeister
Oct 30, 2017
Author
Contributor
Ok, I've removed it. I found out later that on Linux cd is also an alias for Set-Location (my assumption was that native Linux commands are not overridden by pwsh but this seems to be one of the exceptions)
lzybkr
reviewed
Oct 26, 2017
| { | ||
| return PopLocation(locationStackHistoryId); | ||
| } | ||
| PushCurrentLocation(locationStackHistoryId); |
This comment has been minimized.
This comment has been minimized.
lzybkr
Oct 26, 2017
Member
Nice - so simple. I do have a concern about an unbounded stack though. This stack will not be popped often, so I think there should be some limit.
This comment has been minimized.
This comment has been minimized.
bergmeister
Oct 30, 2017
Author
Contributor
OK, I've implemented now a bounded stack based on a linked list.
lzybkr
reviewed
Oct 26, 2017
| $tempLocation = Get-Location | ||
| Set-Location - | ||
| Set-Location - | ||
| (Get-Location).Path | Should Be ($tempLocation).Path |
This comment has been minimized.
This comment has been minimized.
lzybkr
Oct 26, 2017
Member
This test doesn't make sense to me - there is one push, but two pops. How can this pass?
This comment has been minimized.
This comment has been minimized.
bergmeister
Oct 30, 2017
Author
Contributor
I have re-written all tests but the reason why it passed was because Set-Location also pushed to the stack (this was a bug that is fixed now)
lzybkr
reviewed
Oct 26, 2017
| (Get-Location).Path | Should Be ($tempLocation).Path | ||
| } | ||
|
|
||
| It 'Should fail if there is no last location' { |
This comment has been minimized.
This comment has been minimized.
lzybkr
Oct 26, 2017
Member
I'm also surprised about this test - it seems like you need a way to clear the stack before running cd - to make sure it reliably raises the exception.
This comment has been minimized.
This comment has been minimized.
bergmeister
Oct 30, 2017
Author
Contributor
I rewrote this test since it does not make sense any more (it was based on the first environment variable based implementation)
bergmeister
added some commits
Oct 29, 2017
iSazonov
reviewed
Oct 30, 2017
|
I wonder why not enhance the existing API? |
This comment has been minimized.
This comment has been minimized.
|
@iSazonov If I understand correctly, changing the existing API to use a bounded stack would be a breaking change for pushd/popd. Enhancing the existing API should just be a matter of changing the type from |
This comment has been minimized.
This comment has been minimized.
|
@bergmeister We should enhance the API not change. Actually you will use new constructor to set limits, existing code will keep current behavior without limits. This will allow us enhance our *-Location cmdlets and allow customers to use the new API possibilities. |
This comment has been minimized.
This comment has been minimized.
|
@iSazonov There were test failures due it being the full test suite: |
bergmeister
added some commits
May 30, 2018
This comment has been minimized.
This comment has been minimized.
|
Those tests seemed to be sporadic and the new build contains 'only' 1 other sporadic failure |
This comment has been minimized.
This comment has been minimized.
|
@bergmeister Fix is ready #6943. |
This comment has been minimized.
This comment has been minimized.
|
@iSazonov Any updates on this so that it goes into the next preview for getting some testing exposure before 6.1 goes RTM after that? |
This comment has been minimized.
This comment has been minimized.
|
@bergmeister I don't understand. Please reword your question. |
This comment has been minimized.
This comment has been minimized.
|
@iSazonov. Nevermind, I just saw that the next preview is already in the making. |
This comment has been minimized.
This comment has been minimized.
|
@bergmeister We are waiting MSFT team sign. |
rjmholt
approved these changes
Jun 22, 2018
|
This looks good to me. Thanks @bergmeister! |
| bool pushNextLocation = true; | ||
| if (originalPath.Equals("-", StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| if (_workingLocationHistoryStack.Count > 0) |
This comment has been minimized.
This comment has been minimized.
rjmholt
Jun 22, 2018
•
Member
I would structure this if as:
if (_workingLocationHistoryStack.Count <= 0)
{
throw new InvalidOperationException(...);
}
// Golden path behaviourThere are some pretty deep if/else trees in the codebase and they get hard to reason about quickly.
This comment has been minimized.
This comment has been minimized.
| { | ||
| this.AddFirst(item); | ||
|
|
||
| if(this.Count > _capacity) |
This comment has been minimized.
This comment has been minimized.
rjmholt
Jun 22, 2018
Member
Maybe include braces for the if and drop the this from property references. That seems to be the style convention of the file.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
@daxian-dbw @adityapatwardhan Have you any thoughts before we merge? |
BrucePay
requested changes
Jun 22, 2018
|
In general it looks good. Just a few things I think need to change. |
| @@ -65,6 +66,9 @@ internal SessionStateInternal(SessionStateInternal parent, bool linkToGlobal, Ex | |||
| // is used for the pushd and popd commands | |||
|
|
|||
| _workingLocationStack = new Dictionary<String, Stack<PathInfo>>(StringComparer.OrdinalIgnoreCase); | |||
| // conservative choice to limit the memory impact in case of a regression | |||
| const int locationHistoryLimit = 20; | |||
| _workingLocationHistoryStack = new BoundedStack<PathInfo>(locationHistoryLimit); | |||
This comment has been minimized.
This comment has been minimized.
BrucePay
Jun 22, 2018
Collaborator
This variable name is easily confused with _workingLocationStack and _workingLocationHistoryStack. I would suggest some more meaningful like _SetLocationHistory. (I don't think it needs it's type in it's name, and if we do decide to implement cd + it won't be a stack anymore.)
This comment has been minimized.
This comment has been minimized.
iSazonov
Jun 23, 2018
•
Collaborator
@bergmeister Please replace _SetLocationHistory -> _setLocationHistory - it is our name convention for static variables.
Also please review CodeFactor issues - maybe you would fix some ones (in your code or near).
| @@ -65,6 +66,9 @@ internal SessionStateInternal(SessionStateInternal parent, bool linkToGlobal, Ex | |||
| // is used for the pushd and popd commands | |||
|
|
|||
| _workingLocationStack = new Dictionary<String, Stack<PathInfo>>(StringComparer.OrdinalIgnoreCase); | |||
| // conservative choice to limit the memory impact in case of a regression | |||
This comment has been minimized.
This comment has been minimized.
BrucePay
Jun 22, 2018
Collaborator
I would put a space before the comment. I'd also add a comment saying that this is for the Set-Location history to distinguish it from the earlier "pushd and popd" comment.
| internal T Pop() | ||
| { | ||
| var item = this.First.Value; | ||
| this.RemoveFirst(); |
This comment has been minimized.
This comment has been minimized.
BrucePay
Jun 22, 2018
Collaborator
Should have a specific error message here instead of letting the base class throw it's own message if the linked list is empty. If we change the implementation to be a circular buffer, then the exception change too.
bergmeister
added some commits
Jun 22, 2018
This comment has been minimized.
This comment has been minimized.
BrucePay
approved these changes
Jun 22, 2018
This comment has been minimized.
This comment has been minimized.
|
It seems we set a absolute record in PR review duration |
iSazonov
merged commit bbd48f6
into
PowerShell:master
Jun 26, 2018
This comment has been minimized.
This comment has been minimized.
|
@bergmeister Thanks for your contribution! |
daxian-dbw
added a commit
that referenced
this pull request
Jun 26, 2018
This comment has been minimized.
This comment has been minimized.
|
@daxian-dbw Please clarify what is the "Revert..." commit? |
This comment has been minimized.
This comment has been minimized.
|
I saw this as well and cannot track it back, the PR is still in master (which is good), so I assume this commit either happened on a branch or the revert got reverted afterwards |
This comment has been minimized.
This comment has been minimized.
|
I think when you use GitHub's UI to revert a PR, it generates a new commit that reverts all the commits since that PR (which is not what we want -- reverting the single commit in the commandline is the better way). That commit is generated on another branch and not merged in, but still references all the other commits and shows up everywhere, which manages to confuse everyone. II'm guessing @daxian-dbw initially tried to use the revert button, and has since overridden it in the commandline. So this merge hasn't been reverted, but a revert commit for it was generated and never used. I know because I clicked the "revert" button once and was answering questions about it for a week. |
This comment has been minimized.
This comment has been minimized.
|
The confusing revert commit comes from https://github.com/PowerShell/PowerShell/pull/7182/commits. Changes from this PR is intact, so don't worry :) |


bergmeister commentedOct 7, 2017
•
edited
PR Summary
Closes #2188
This makes it possible to go back to the last directory using 'cd -', which is exactly the same syntax as on Linux shells.
In contrast to the original proposal, it does not set the environment variable
OLDPWDbut instead uses a bounded stack to store the history in a similar way how Push/Pop-Location do.A localised error message is given if there is no location history.
PR Checklist
.h,.cpp,.cs,.ps1and.psm1files have the correct copyright headerWIP:to the beginning of the title and remove the prefix when the PR is ready.[feature]if the change is significant or affects feature tests