Skip to content

Conversation

@jeffbi
Copy link
Contributor

@jeffbi jeffbi commented Jun 15, 2017

Fixes #3951

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.

@jeffbi
Copy link
Contributor Author

jeffbi commented Jun 15, 2017

@iSazonov, @daxian-dbw can someone look at the AppVeyor failure? Looking at the details, It appears to be failing very early in the run

@iSazonov
Copy link
Collaborator

@jeffbi I believe it is temporary error - please restart CI by reward your commit.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use autoproperty if we can.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modified the -FollowSymlink parameter to set -Recurse, and the -Depth parameter does. Thus we cannot use auto-property here.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Closed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Collaborator

@iSazonov iSazonov Jun 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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() ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have this on top level - see lines 1610-1616. I believe we should remove the two lines.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is called recursively, so I think we need to leave these lines here.

Copy link
Collaborator

@iSazonov iSazonov Jun 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the Dir function, the tracker object also acts as a flag that the user gave the -FollowSymlink parameter.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to refactor code and had no benefits.

Closed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we check "recurse" here? Have we Recurse and FollowSymlink in only the same ParameterSet?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 .

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modified the -FollowSymlink parameter to set -Recurse, and the -Depth parameter does.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Context.MyInvocation.BoundParameters.ContainsKey("FollowSymlink"))

Can we use FollowSymlink property?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FollowSymlink is a property of GetChildItemCommand, not of the FileSystemProvider.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for clarify!

Closed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can move "tracker != null" and exclude one "if":

if (tracker != null && tracker.Visited(recursiveDirectory.FullName)) 

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
                            }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored code to eliminate enterDir.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Closed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please re-format the comment. Maybe put a) and b) on new lines. And "the directory the symlink" confuse me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modified the comment.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Closed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"{0}" can bee very long so maybe we re-format the message?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-formatted the message to put the path at the end.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Closed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated "@brief" ?

Copy link
Contributor Author

@jeffbi jeffbi Jun 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one describes the function. The other describes the file.

I used the existing getlinkcount.cpp as the pattern for this.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Closed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use the pattern?

enterDir = tracker != null;

Or we should use explicit "false"?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Closed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it supported on Windows 7?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's supported as early as XP.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Closed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Closed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Context.MyInvocation.BoundParameters.ContainsKey("FollowSymlink"))

Can we use FollowSymlink property?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now we don't use dev and inode and we could out Tuple.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do still use dev and inode. That's how we uniquely identify a file system object.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the GetInodeData is only called in Visit and Visited method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Did you want to move the implementation up into the InodeTracker class?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant simple change like internal static bool GetInodeData(string path, out (dev, inode) var)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Closed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we exclude repetition "listing ... listed"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed "already-listed" to "already-visited"

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Closed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In line 170

this.Recurse = true; // Bug 2391925 - Get-ChildItem -Depth should auto-set -Recurse

Should we use the same pattern?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like that was in response to a specific bug. In our case it's just part of a new feature.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid we could catch the same bug here. Maybe maintainers clarify.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The question for maintainers: should we follow the file patterns and move the private variable to a line below 342?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use the same pattern in Visited and Visit methods - insert a new line here or remove a line after "var" in Visit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching that. New line inserted.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Closed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do it makes sense to break it down into two lines?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made the calculation more concise.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Closed.

@iSazonov
Copy link
Collaborator

@daxian-dbw Could you please assign a reviewer?

@iSazonov
Copy link
Collaborator

iSazonov commented Jun 22, 2017

@jeffbi Great work! Thanks!
I leave two questions opened for maintainers.

@daxian-dbw daxian-dbw self-assigned this Jun 22, 2017
@daxian-dbw
Copy link
Member

@jeffbi and @iSazonov thanks for the good work and review! I will quickly go through the changes before merging.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't use public modifier for members of an internal class. Use internal/private as appropriate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method name should be IsPathVisited. It's good for readability.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Copy link
Member

@daxian-dbw daxian-dbw Jun 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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=LAHBLAHB

It'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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So then -FollowSymlink should not imply -Recurse? It has meaning only if -Recurse is also explicitly given?

Copy link
Collaborator

@iSazonov iSazonov Jun 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Event without -FollowSymlink, the cmdlet will follow symlinks specified in -Path

Copy link
Collaborator

@iSazonov iSazonov Jun 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it will turn out that making -FollowSymlink implicitly imply -Recurse is better, but for now here are my 2 cents:

  1. It's always better to force the user to be explicit about their intent, so dir -Recurse -FollowSymlink is more readable than dir -FollowSymlink in my opinion.
  2. For simplicity reason, this PR can just add the -FollowSymlink parameter without worrying about its dependency to -Recurse. In my understanding (correct me if I'm wrong) the related code won't execute when it's not recursively iterating file system items. Then we can get feedback from users and base on that to decide if we should make -FollowSymlink implicitly imply -Recurse.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to dynamic parameter. -FollowSymlink no longer implies -Recurse.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Skip already-visited directory {0} might be better.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be "Skip"? Or perhaps "Skipping" or "Skipped"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correspondingly, maybe this name can be changed to VisitPath.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And, does this method really need to return anything? If GetInodeData failed, then we just don't skip anything.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed both.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now we return nothing. Please remove the line.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Closed.

Copy link
Collaborator

@iSazonov iSazonov Jun 29, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong pattern.
We can use tracker?.VisitPath(directory.FullName); or if (...) { ... }.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Closed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can remove one if.

if (fspDynamicParam?.FollowSymlink) 

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean "hidden" directories? If so we can safely mark "hidden" directories as "visited" - in both cases they is really skipped.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A directory might be hidden. A symbolic link (or several such) might not be.

Copy link
Collaborator

@iSazonov iSazonov Jul 2, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. We can move the hidden check above line 1861 - it is less expensive then double call 'GetInodeData' in VisitPath and IsPathVisited.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now CoreFX has a new method Dictionary.TryAdd - Could we use them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used new method

@iSazonov
Copy link
Collaborator

iSazonov commented Jul 3, 2017

LGTM.

@daxian-dbw Could you please continue the code review?

Copy link
Member

@daxian-dbw daxian-dbw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for switching to the dynamic parameter approach! See some more comments below.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing </summary>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why using a Dictionary? It seems HashSet would be good.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use new Dictionary.TryAdd() but HashSet.Add() works the same - so we can use HashSet.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HashSet is much better. Fixed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A new parameter should usually be added to the end of the parameter list.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
    }
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought I had a check for recurse at one time. It must have gotten lost in the shuffle. Fixed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we can use else if (...) so that one nested block can be avoided.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With HashSet it will looks just better.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor comment: maybe use the following pattern to avoid var inodeData = (0UL, 0UL);?

if (InternalSymbolicLinkLinkCodeMethods.GetInodeData(path, out (UInt64, UInt64) inodeData))

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Jeff Bienstadt added 9 commits July 5, 2017 23:41
…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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add the parameter if we don't use them?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. If the object is not in the set, add it and return true.
  2. If the object is already in the set, return false which means it was not added in this call because it's already there.

Similarly, InodeTracker.TryVisitPath(path) should be

  1. If the path is not visited yet, visit it and return true.
  2. If the path is already visited, return false which means it was not visited in this call because it's already visited.

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 @iSazonov what do you think?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the TryVisitPath more the IsVisitPath.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works for me. I'll make the change.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome. Thank you both!

Copy link
Member

@daxian-dbw daxian-dbw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 more comments

bool nameOnly,
ReturnContainers returnContainers)
ReturnContainers returnContainers,
InodeTracker tracker) // tracker will be non-null only if the user invoked the -FollowSymLinks switch parameter.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor comment: this comment is not accurate now -- tracker will be null when -FollowSymlinks is specified but -Recurse is not.

/// </param>
/// <param name="visitIfNotAlready">
/// Flag to indicate whether the path should be marked as visited if
/// it has not already been visited.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. If the object is not in the set, add it and return true.
  2. If the object is already in the set, return false which means it was not added in this call because it's already there.

Similarly, InodeTracker.TryVisitPath(path) should be

  1. If the path is not visited yet, visit it and return true.
  2. If the path is already visited, return false which means it was not visited in this call because it's already visited.

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 @iSazonov what do you think?

Copy link
Member

@daxian-dbw daxian-dbw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jeffbi and @iSazonov Thank you both for the great work and thorough review!

@daxian-dbw daxian-dbw merged commit 1688703 into PowerShell:master Jul 6, 2017
@jeffbi jeffbi deleted the Get-Child-3951 branch July 6, 2017 23:14
@iSazonov
Copy link
Collaborator

iSazonov commented Jul 7, 2017

@jeffbi Many thanks for the great work!

@jeffbi
Copy link
Contributor Author

jeffbi commented Jul 7, 2017

@iSazonov and @daxian-dbw Thanks for the review.

@sba923
Copy link
Contributor

sba923 commented Apr 29, 2019

Very good job!

P.S. I would probably have called the new parameter -FollowSymlinks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Need the ability to opt into / out of following symlinks with Get-ChildItem -Recurse, behavior differs between editions

5 participants