| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
|
Ilya (@iSazonov), Dongbo Wang (@daxian-dbw) can someone look at the AppVeyor failure? Looking at the details, It appears to be failing very early in the run |
Sorry, something went wrong.
|
jeffbi I believe it is temporary error - please restart CI by reward your commit. |
Sorry, something went wrong.
There was a problem hiding this comment.
We should use autoproperty if we can.
Sorry, something went wrong.
There was a problem hiding this comment.
Modified the -FollowSymlink parameter to set -Recurse, and the -Depth parameter does. Thus we cannot use auto-property here.
Sorry, something went wrong.
There was a problem hiding this comment.
Closed.
Sorry, something went wrong.
There was a problem hiding this comment.
Can we use a Tuple<dev, inode> as key? If so it exclude "dictionary in dictionary".
Sample and c# 7.0 pattern https://stackoverflow.com/questions/2877660/composite-key-dictionary
Sorry, something went wrong.
There was a problem hiding this comment.
Part of that discussion (https://stackoverflow.com/questions/2877660/composite-key-dictionary#comment14110652_2877820) suggests that the nested dictionary approach may be faster.
As for the C#7 pattern, I thought I read somewhere in another code review that C#7 code should be avoided. Is that not the case?
Sorry, something went wrong.
There was a problem hiding this comment.
C#7 pattern
We have no strict documentation. Different maintainers may have different requirements. My understanding: New patterns are welcome for the new code. Existing patterns (not relevant to the PR) should not be changed without maintainer's requiring.
the nested dictionary approach may be faster.
There, the main answer has recently been updated and there is a reference to gist with the tests. The conclusion is now Tuple keys is fast.
We both think that performance is important here. So maybe it makes sense to spend time testing cases.
Another thought is, in most cases, the user will have one volume. We could use one dictionary until we find a reference to another volume. Although it will complicate the code and it's not clear how much we win. Ooops, for other volume we can make separate recursive Dir() call - in the case we have to switch Dictionary (tracker) only on "top" level and only on Symlink when we can meet new volume. In other words, we will have two dictionaries but they will not be nested and the number of comparisons will be less - we'll be faster.
And maybe rename Dir() in something like DirectoryVisitor() ?
Sorry, something went wrong.
There was a problem hiding this comment.
It may be that on Windows the user will have only one volume, but that is much less the case on Unix. For example, on my Linux box the root (/) directory has device ID 2049, the /dev directory has ID 6, and the /proc directory has ID 4. Symlinks can cross device/volume boundaries.
I've changed the code to use the C#7 ValueTuple mechanism.
Sorry, something went wrong.
There was a problem hiding this comment.
We have this on top level - see lines 1610-1616. I believe we should remove the two lines.
Sorry, something went wrong.
There was a problem hiding this comment.
This function is called recursively, so I think we need to leave these lines here.
Sorry, something went wrong.
There was a problem hiding this comment.
tracker = new InodeTracker(); - It's not expensive. We can always create this on top level and not check many times during recursion.
We can tracker.Visit(directory.FullName); before call the Dir.
Sorry, something went wrong.
There was a problem hiding this comment.
In the Dir function, the tracker object also acts as a flag that the user gave the -FollowSymlink parameter.
Sorry, something went wrong.
There was a problem hiding this comment.
I tried to refactor code and had no benefits.
Closed.
Sorry, something went wrong.
There was a problem hiding this comment.
Should we check "recurse" here? Have we Recurse and FollowSymlink in only the same ParameterSet?
Sorry, something went wrong.
There was a problem hiding this comment.
There are two parameter sets for Get-ChildItem: Items and LiteralItems. The -Recurse and -FollowSymlink parameters are in both parameter sets.
The Depth parameter, which is also in both, implies -Recurse by setting the Recurse property. Should -FollowSymlink follow that pattern? That would remove the check of recurse here.
Sorry, something went wrong.
There was a problem hiding this comment.
We can not define explicitly mandatory sub-parameters 😕 That would be a useful feature.
So it is more safe to leave "recurse" here.
If -Depth implies -Recurse I believe we can do the same with -FollowSymlink .
Sorry, something went wrong.
There was a problem hiding this comment.
Modified the -FollowSymlink parameter to set -Recurse, and the -Depth parameter does.
Sorry, something went wrong.
There was a problem hiding this comment.
Context.MyInvocation.BoundParameters.ContainsKey("FollowSymlink"))
Can we use FollowSymlink property?
Sorry, something went wrong.
There was a problem hiding this comment.
FollowSymlink is a property of GetChildItemCommand, not of the FileSystemProvider.
Sorry, something went wrong.
There was a problem hiding this comment.
Thanks for clarify!
Closed.
Sorry, something went wrong.
There was a problem hiding this comment.
We can move "tracker != null" and exclude one "if":
if (tracker != null && tracker.Visited(recursiveDirectory.FullName))
Sorry, something went wrong.
There was a problem hiding this comment.
It seems we can exclude enterDir:
if (tracker != null)
{
if (tracker.Visited(recursiveDirectory.FullName))
{
WriteWarning(StringUtil.Format(FileSystemProviderStrings.AlreadyListedDirectory,
recursiveDirectory.FullName));
continue;
}
}
else
{
if (InternalSymbolicLinkLinkCodeMethods.IsReparsePoint(recursiveDirectory))
{
continue;
}
}
bool hidden = false;
if (!Force)
{
hidden = (recursiveDirectory.Attributes & FileAttributes.Hidden) != 0;
}
// if "Hidden" is explicitly specified anywhere in the attribute filter, then override
// default hidden attribute filter.
if (Force || !hidden || isFilterHiddenSpecified || isSwitchFilterHiddenSpecified)
{
Dir(recursiveDirectory, recurse, depth - 1, tracker, nameOnly, returnContainers);
}
Sorry, something went wrong.
There was a problem hiding this comment.
Refactored code to eliminate enterDir.
Sorry, something went wrong.
There was a problem hiding this comment.
Closed.
Sorry, something went wrong.
There was a problem hiding this comment.
Please re-format the comment. Maybe put a) and b) on new lines. And "the directory the symlink" confuse me.
Sorry, something went wrong.
There was a problem hiding this comment.
Modified the comment.
Sorry, something went wrong.
There was a problem hiding this comment.
Closed.
Sorry, something went wrong.
There was a problem hiding this comment.
"{0}" can bee very long so maybe we re-format the message?
Sorry, something went wrong.
There was a problem hiding this comment.
Re-formatted the message to put the path at the end.
Sorry, something went wrong.
There was a problem hiding this comment.
Closed.
Sorry, something went wrong.
There was a problem hiding this comment.
Duplicated "brief" ?
Sorry, something went wrong.
There was a problem hiding this comment.
This one describes the function. The other describes the file.
I used the existing getlinkcount.cpp as the pattern for this.
Sorry, something went wrong.
There was a problem hiding this comment.
Closed.
Sorry, something went wrong.
There was a problem hiding this comment.
Can we use the pattern?
enterDir = tracker != null;Or we should use explicit "false"?
Sorry, something went wrong.
There was a problem hiding this comment.
Closed.
Sorry, something went wrong.
There was a problem hiding this comment.
Is it supported on Windows 7?
Sorry, something went wrong.
There was a problem hiding this comment.
It's supported as early as XP.
Sorry, something went wrong.
There was a problem hiding this comment.
Closed.
Sorry, something went wrong.
There was a problem hiding this comment.
Closed.
Sorry, something went wrong.
There was a problem hiding this comment.
Context.MyInvocation.BoundParameters.ContainsKey("FollowSymlink"))
Can we use FollowSymlink property?
Sorry, something went wrong.
There was a problem hiding this comment.
Now we don't use dev and inode and we could out Tuple.
Sorry, something went wrong.
There was a problem hiding this comment.
We do still use dev and inode. That's how we uniquely identify a file system object.
Sorry, something went wrong.
There was a problem hiding this comment.
I see the GetInodeData is only called in Visit and Visited method.
Sorry, something went wrong.
There was a problem hiding this comment.
Yes. Did you want to move the implementation up into the InodeTracker class?
Sorry, something went wrong.
There was a problem hiding this comment.
I meant simple change like internal static bool GetInodeData(string path, out (dev, inode) var)
Sorry, something went wrong.
There was a problem hiding this comment.
Closed.
Sorry, something went wrong.
There was a problem hiding this comment.
Can we exclude repetition "listing ... listed"?
Sorry, something went wrong.
There was a problem hiding this comment.
Changed "already-listed" to "already-visited"
Sorry, something went wrong.
There was a problem hiding this comment.
Closed.
Sorry, something went wrong.
There was a problem hiding this comment.
In line 170
this.Recurse = true; // Bug 2391925 - Get-ChildItem -Depth should auto-set -RecurseShould we use the same pattern?
Sorry, something went wrong.
There was a problem hiding this comment.
It looks like that was in response to a specific bug. In our case it's just part of a new feature.
Sorry, something went wrong.
There was a problem hiding this comment.
I'm afraid we could catch the same bug here. Maybe maintainers clarify.
Sorry, something went wrong.
There was a problem hiding this comment.
The question for maintainers: should we follow the file patterns and move the private variable to a line below 342?
Sorry, something went wrong.
There was a problem hiding this comment.
Please use the same pattern in Visited and Visit methods - insert a new line here or remove a line after "var" in Visit.
Sorry, something went wrong.
There was a problem hiding this comment.
Thanks for catching that. New line inserted.
Sorry, something went wrong.
There was a problem hiding this comment.
Closed.
Sorry, something went wrong.
There was a problem hiding this comment.
Do it makes sense to break it down into two lines?
Sorry, something went wrong.
There was a problem hiding this comment.
Made the calculation more concise.
Sorry, something went wrong.
There was a problem hiding this comment.
Closed.
Sorry, something went wrong.
|
Dongbo Wang (@daxian-dbw) Could you please assign a reviewer? |
Sorry, something went wrong.
|
jeffbi Great work! Thanks! |
Sorry, something went wrong.
|
jeffbi and Ilya (@iSazonov) thanks for the good work and review! I will quickly go through the changes before merging. |
Sorry, something went wrong.
There was a problem hiding this comment.
Please don't use public modifier for members of an internal class. Use internal/private as appropriate.
Sorry, something went wrong.
There was a problem hiding this comment.
Fixed.
Sorry, something went wrong.
There was a problem hiding this comment.
This method name should be IsPathVisited. It's good for readability.
Sorry, something went wrong.
There was a problem hiding this comment.
Fixed.
Sorry, something went wrong.
There was a problem hiding this comment.
I think -FollowSymlink should be a dynamic parameter provided by file system provider so that when using Get-ChildItem with another provider, this parameter won't show up.
-FollowSymlink is very much like the -CodeSigningCert parameter of Get-ChildItem -- -CodeSigningCert only applicable to the certificate provider and -FollowSymlink only applicable to FileSystemProvider. It's unlike the -Depth parameter which could be commonly meaningful to many underlying providers.
PS:17> Get-ChildItem Cert:\ -CodeSigningCert -Recurse
PSParentPath: Microsoft.PowerShell.Security\Certificate::CurrentUser\REQUEST
Thumbprint Subject
---------- -------
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX CN=BLAHBLAH
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX CN=LAHBLAHBIt's doable to make -FollowSymlink depends on the appearance of -Recurse in the dynamic parameter, but I suggest for this PR let's make things simpler by not having this design. If it's a highly demanded feature, we can enable it in a separate PR.
Please take a look at the following places to see how dynamic parameters are retrieved from the underlying provider:
https://github.com/PowerShell/PowerShell/blob/master/src/Microsoft.PowerShell.Commands.Management/commands/management/GetChildrenCommand.cs#L372-L387
Sorry, something went wrong.
There was a problem hiding this comment.
So then -FollowSymlink should not imply -Recurse? It has meaning only if -Recurse is also explicitly given?
Sorry, something went wrong.
There was a problem hiding this comment.
powercode initiated #4102 for disscusion of parameter sets selection. So it is design case whether or not imply "mandatory" parameter by subparameter.
And what is if a path in -Path is Symlink?
Sorry, something went wrong.
There was a problem hiding this comment.
Event without -FollowSymlink, the cmdlet will follow symlinks specified in -Path
Sorry, something went wrong.
There was a problem hiding this comment.
Should we follow w/o FollowSymlink in the case? If no we can use FollowSymlink w/o Recurce. If yes we should rename FollowSymlink in FollowSymlinkInRecursion.
Sorry, something went wrong.
There was a problem hiding this comment.
Maybe it will turn out that making -FollowSymlink implicitly imply -Recurse is better, but for now here are my 2 cents:
Sorry, something went wrong.
There was a problem hiding this comment.
Changed to dynamic parameter. -FollowSymlink no longer implies -Recurse.
Sorry, something went wrong.
There was a problem hiding this comment.
if (recurse && Context.MyInvocation.BoundParameters.ContainsKey("FollowSymlink"))
This is fragile as you don't check the value. Think about dir -follow:$false. We should go with the dynamic parameter approach for -FollowSymlink
Sorry, something went wrong.
There was a problem hiding this comment.
I think Skip already-visited directory {0} might be better.
Sorry, something went wrong.
There was a problem hiding this comment.
Should this be "Skip"? Or perhaps "Skipping" or "Skipped"?
Sorry, something went wrong.
There was a problem hiding this comment.
Fixed.
Sorry, something went wrong.
There was a problem hiding this comment.
I see that you are following a pattern like IsSameFileSystemItem or IsHardLink. Ideally, code here should be refactored to consolidate Unix/Windows implementations in one place and eliminate wrapper functions like IsSameFileSystemItem and IsHardLink. Instead they should be
internal static bool IsSameFileSystemItem(string pathOne, string pathTwo)
{
#if UNIX
// Unix implementation goes here
#else
// Windows implementation goes here
#endif
}I'm not asking you to do this change for this PR, just want to call out that it should be this way moving forward. I opened #4086 to track the refactoring task.
Sorry, something went wrong.
There was a problem hiding this comment.
Correspondingly, maybe this name can be changed to VisitPath.
Sorry, something went wrong.
There was a problem hiding this comment.
And, does this method really need to return anything? If GetInodeData failed, then we just don't skip anything.
Sorry, something went wrong.
There was a problem hiding this comment.
Fixed both.
Sorry, something went wrong.
There was a problem hiding this comment.
Now we return nothing. Please remove the line.
Sorry, something went wrong.
There was a problem hiding this comment.
Closed.
Sorry, something went wrong.
There was a problem hiding this comment.
Wrong pattern.
We can use tracker?.VisitPath(directory.FullName); or if (...) { ... }.
Sorry, something went wrong.
There was a problem hiding this comment.
Closed.
Sorry, something went wrong.
There was a problem hiding this comment.
We can remove one if.
if (fspDynamicParam?.FollowSymlink)
Sorry, something went wrong.
There was a problem hiding this comment.
Sorry, I again under 'tracker == null' doulble check pressure.
It seem we can remove one if remove lines 1675-1678, add its before first Dir call and here add:
else
{
tracker.VisitPath(directory.FullName);
}Next, we can exclude VisitPath at all if make "visit" in IsPathVisited (we should rename it maybe as TryPathVisit).
Sorry, something went wrong.
There was a problem hiding this comment.
I don't agree with this. If we mark the directory has having been visited here, then if the test at line 1883 evaluates to false the Dir method will not be called and we will not visit the directory even though we have already claimed to have done so.
Sorry, something went wrong.
There was a problem hiding this comment.
Do you mean "hidden" directories? If so we can safely mark "hidden" directories as "visited" - in both cases they is really skipped.
Sorry, something went wrong.
There was a problem hiding this comment.
A directory might be hidden. A symbolic link (or several such) might not be.
Sorry, something went wrong.
There was a problem hiding this comment.
You are right. We can move the hidden check above line 1861 - it is less expensive then double call 'GetInodeData' in VisitPath and IsPathVisited.
Sorry, something went wrong.
There was a problem hiding this comment.
Now CoreFX has a new method Dictionary.TryAdd - Could we use them?
Sorry, something went wrong.
There was a problem hiding this comment.
Used new method
Sorry, something went wrong.
|
LGTM. Dongbo Wang (@daxian-dbw) Could you please continue the code review? |
Sorry, something went wrong.
There was a problem hiding this comment.
Thanks for switching to the dynamic parameter approach! See some more comments below.
Sorry, something went wrong.
There was a problem hiding this comment.
Missing </summary>
Sorry, something went wrong.
There was a problem hiding this comment.
Fixed.
Sorry, something went wrong.
There was a problem hiding this comment.
Why using a Dictionary? It seems HashSet would be good.
Sorry, something went wrong.
There was a problem hiding this comment.
We use new Dictionary.TryAdd() but HashSet.Add() works the same - so we can use HashSet.
Sorry, something went wrong.
There was a problem hiding this comment.
HashSet is much better. Fixed.
Sorry, something went wrong.
There was a problem hiding this comment.
A new parameter should usually be added to the end of the parameter list.
Sorry, something went wrong.
There was a problem hiding this comment.
Fixed.
Sorry, something went wrong.
There was a problem hiding this comment.
If recurse == false, then we should avoid creating the tracker.
if (recurse)
{
GetChildDynamicParameters fspDynamicParam = DynamicParameters as GetChildDynamicParameters;
if (fspDynamicParam != null && fspDynamicParam.FollowSymlink)
{
tracker = new InodeTracker(directory.FullName);
}
}
Sorry, something went wrong.
There was a problem hiding this comment.
I thought I had a check for recurse at one time. It must have gotten lost in the shuffle. Fixed.
Sorry, something went wrong.
There was a problem hiding this comment.
Here we can use else if (...) so that one nested block can be avoided.
Sorry, something went wrong.
There was a problem hiding this comment.
Fixed.
Sorry, something went wrong.
There was a problem hiding this comment.
This method goes against the convention of Try<Verb> pattern. Take TryAdd as an instance, if added, then return true; if not added, then return false. Following that conversion, TryVisitPath should be: if visited, then return true; if not visited, then return false. So TryVisitPath makes it confusing understanding the code.
How about making it IsPathVisited(bool visitIfNotAlready = false)?
internal bool IsPathVisited(bool visitIfNotAlready = false)
{
bool isPathVisited = false;
if (InternalSymbolicLinkLinkCodeMethods.GetInodeData(path, out (UInt64, UInt64) inodeData))
{
if (visitIfNotAlready)
{
isPathVisited = !_visitations.Add(inodeData)
}
else
{
isPathVisited = _visitations.Contains(inodeData)
}
}
return isPathVisited;
}
Sorry, something went wrong.
There was a problem hiding this comment.
With HashSet it will looks just better.
Sorry, something went wrong.
There was a problem hiding this comment.
Fixed.
Sorry, something went wrong.
There was a problem hiding this comment.
This class seems to be a helper that will only be used in FileSystemProvider code, so can we make it a private nested class within FileSystemProvider? (just like private static class NativeMethods in FileSystemProvider)
Sorry, something went wrong.
There was a problem hiding this comment.
Fixed.
Sorry, something went wrong.
There was a problem hiding this comment.
Minor comment: maybe use the following pattern to avoid var inodeData = (0UL, 0UL);?
if (InternalSymbolicLinkLinkCodeMethods.GetInodeData(path, out (UInt64, UInt64) inodeData))
Sorry, something went wrong.
There was a problem hiding this comment.
Looks good.
Sorry, something went wrong.
There was a problem hiding this comment.
Fixed.
Sorry, something went wrong.
…ops. (#3951) Add -FollowSymlink switch parameter to Get-ChildItem. Add a mechanism for tracking visited directories. Add native code to get dev/inode information on Unix. Add warning when refusing to enter an already-visited directory.
o Switched from nested dictionary to single dictionary keyed by
ValueTuple (device, inode).
o Modified warning message to put path at end.
o -FollowSymlink switch now implies -Recurse
o Other code and comment refactorizings.
* Make InodeTracker a private nested class
* Change from Dictionary to HashSet in InodeTracker
* Change InodeTracker's TryVisitPath to IsPathVisited with a parameter for marking a path as visited if not already
* Add check for -Recurse before creating InodeTracker object
* Change order of parameters in Dir method
* Remove unneeded nested block level
* Add missing </summary> tag
* Add -Pending on some tests to get past Pester errors
(see issue #4145)
| /// </param> | ||
| /// <param name="visitIfNotAlready"> | ||
| /// Flag to indicate whether the path should be marked as visited if | ||
| /// it has not already been visited. |
There was a problem hiding this comment.
Should we add the parameter if we don't use them?
Sorry, something went wrong.
There was a problem hiding this comment.
Normally I would say no. In this case the existence of the parameter and its name give a clue about the side effect of marking the path as visited.
Sorry, something went wrong.
There was a problem hiding this comment.
I give this some more thinking, and believes that the TryVisitPath signature is more natural in this case. But we need to follow the Try<> pattern.
Again, take HashSet.Add(object) as an example:
Similarly, InodeTracker.TryVisitPath(path) should be
The code would be like:
internal bool TryVisitPath(string path)
{
bool returnValue = false;
if (InternalSymbolicLinkLinkCodeMethods.GetInodeData(path, out (UInt64, UInt64) inodeData))
{
returnValue = _visitations.Add(inodeData);
}
return returnValue;
}jeffbi Ilya (@iSazonov) what do you think?
Sorry, something went wrong.
There was a problem hiding this comment.
I like the TryVisitPath more the IsVisitPath.
Sorry, something went wrong.
There was a problem hiding this comment.
Works for me. I'll make the change.
Sorry, something went wrong.
There was a problem hiding this comment.
Awesome. Thank you both!
Sorry, something went wrong.
There was a problem hiding this comment.
2 more comments
Sorry, something went wrong.
| bool nameOnly, | ||
| ReturnContainers returnContainers) | ||
| ReturnContainers returnContainers, | ||
| InodeTracker tracker) // tracker will be non-null only if the user invoked the -FollowSymLinks switch parameter. |
There was a problem hiding this comment.
Minor comment: this comment is not accurate now -- tracker will be null when -FollowSymlinks is specified but -Recurse is not.
Sorry, something went wrong.
| /// </param> | ||
| /// <param name="visitIfNotAlready"> | ||
| /// Flag to indicate whether the path should be marked as visited if | ||
| /// it has not already been visited. |
There was a problem hiding this comment.
I give this some more thinking, and believes that the TryVisitPath signature is more natural in this case. But we need to follow the Try<> pattern.
Again, take HashSet.Add(object) as an example:
Similarly, InodeTracker.TryVisitPath(path) should be
The code would be like:
internal bool TryVisitPath(string path)
{
bool returnValue = false;
if (InternalSymbolicLinkLinkCodeMethods.GetInodeData(path, out (UInt64, UInt64) inodeData))
{
returnValue = _visitations.Add(inodeData);
}
return returnValue;
}jeffbi Ilya (@iSazonov) what do you think?
Sorry, something went wrong.
There was a problem hiding this comment.
jeffbi and Ilya (@iSazonov) Thank you both for the great work and thorough review!
Sorry, something went wrong.
|
jeffbi Many thanks for the great work! |
Sorry, something went wrong.
|
Ilya (@iSazonov) and Dongbo Wang (@daxian-dbw) Thanks for the review. |
Sorry, something went wrong.
|
Very good job! P.S. I would probably have called the new parameter -FollowSymlinks |
Sorry, something went wrong.
…ops (PowerShell#4020) Add the dynamic parameter `-FollowSymlink` to `Get-ChildItem`. Add a mechanism for tracking visited directories. Add native code to get device/inode information on Unix/Windows. Add warning when refusing to enter an already-visited directory.
| Back | FazBrowse Home | New Git URL |
Fixes #3951