code: func (f FileInfo) isEquivalent(other FileInfo, ignorePerms bool, ignoreBlocks bool, ignoreFlags uint32) bool {
  	if f.MustRescan() || other.MustRescan() {
  		// These are per definition not equivalent because they don't
  		// represent a valid state, even if both happen to have the
  		// MustRescan bit set.
  		return false
  	}
  
  	// Mask out the ignored local flags before checking IsInvalid() below
  	f.LocalFlags &^= ignoreFlags
  	other.LocalFlags &^= ignoreFlags
  
  	if f.Name != other.Name || f.Type != other.Type || f.Deleted != other.Deleted || f.IsInvalid() != other.IsInvalid() {
  		return false
  	}
  
  	if !ignorePerms && !f.NoPermissions && !other.NoPermissions && !PermsEqual(f.Permissions, other.Permissions) {
  		return false
  	}
  
  	switch f.Type {
  	case FileInfoTypeFile:
  		return f.Size == other.Size && f.ModTime().Equal(other.ModTime()) && (ignoreBlocks || BlocksEqual(f.Blocks, other.Blocks))
  	case FileInfoTypeSymlink:
  		return f.SymlinkTarget == other.SymlinkTarget
  	case FileInfoTypeDirectory:
  		return true
  	}
  
  	return false
  }
docs: // isEquivalent checks that the two file infos represent the same actual file content,
  // i.e. it does purposely not check only selected (see below) struct members.
  // Permissions (config) and blocks (scanning) can be excluded from the comparison.
  // Any file info is not "equivalent", if it has different
  //  - type
  //  - deleted flag
  //  - invalid flag
  //  - permissions, unless they are ignored
  // A file is not "equivalent", if it has different
  //  - modification time
  //  - size
  //  - blocks, unless there are no blocks to compare (scanning)
  // A symlink is not "equivalent", if it has different
  //  - target
  // A directory does not have anything specific to check.