Repository: facelessuser/wcmatch

Issue:
Add support for `***`, zsh style globstar that recurse symlinks 
This could be enabled for those who wish to control recursive symlink searching on demand. I think it would use a flag `GLOBSTARLONG` (which is the opposite of the zsh flag wish shortens 3 to 2). This would normally be used instead of the `FOLLOW` flag, not with it. I think it would make sense if `FOLLOW` and `GLOBSTARLONG` are used together to defer to `GLOBSTARLONG`.  Though, if used with `MATCHBASE` or `pathlib`'s `rglob`, `FOLLOW` would force a `***` to preceede the pattern vs `**`, and then `GLOBSTARLONG` would control explicit `**` or `***`. So that may be the only case using them together may make sense.

Hints from issue comments:

Solution patch:
diff --git a/docs/src/dictionary/en-custom.txt b/docs/src/dictionary/en-custom.txt
index fa27e12..60a5f58 100644
--- a/docs/src/dictionary/en-custom.txt
+++ b/docs/src/dictionary/en-custom.txt
@@ -64,6 +64,7 @@ preprocessing
 preprocessors
 prerelease
 prereleases
+recurse
 recursing
 regex
 sharepoint
diff --git a/docs/src/markdown/about/changelog.md b/docs/src/markdown/about/changelog.md
index 75bfe3d..9368fa2 100644
--- a/docs/src/markdown/about/changelog.md
+++ b/docs/src/markdown/about/changelog.md
@@ -2,9 +2,13 @@
 
 ## 10.0
 
+-   **NEW**: Added `GLOBSTARLONG` which adds support for the Zsh style `***` which acts like `**` with `GLOBSTAR` but
+    but traverses symlinks.
+-   **NEW**: `pathlib.match` will respect symlinks (when the `REALPATH` flag is given) and hidden file rules (enable
+    `DOTALL` to match hidden files).
 -   **NEW**: Symlinks should not be traversed when `GLOBSTAR` is enabled unless `FOLLOW` is also enabled, but they
     should still be matched. Prior to this change, symlinks were not traversed _and_ they were ignored from matching
-    which contradicts how Bash works, which is are general target.
+    which contradicts how Bash works and could be confusing to users.
 -   **FIX**: Fix some inconsistencies with `globmatch` and symlink handling when `REALPATH` is enabled.
 
 ## 9.0
diff --git a/docs/src/markdown/glob.md b/docs/src/markdown/glob.md
index 0d3c6a0..b26ad64 100644
--- a/docs/src/markdown/glob.md
+++ b/docs/src/markdown/glob.md
@@ -19,7 +19,8 @@ character `#!py3 r'\?'`.  If you want to represent a literal backslash, you must
 Pattern           | Meaning
 ----------------- | -------
 `*`               | Matches everything except slashes.  On Windows it will avoid matching backslashes as well as slashes.
-`**`              | Matches zero or more directories, but will never match the directories `.` and `..`. Requires the [`GLOBSTAR`](#globstar) flag.
+`**`              | Matches zero or more directories, but will never match the directories ` . ` and `..`. Requires the [`GLOBSTAR`](#globstar) flag.
+`***`             | Like `**` but will also recurse symlinks. Requires the [`GLOBSTARLONG`](#globstarlong) flag.
 `?`               | Matches any single character.
 `[seq]`           | Matches any character in seq.
 `[!seq]`          | Matches any character not in seq. Will also accept character exclusions in the form of `[^seq]`.
@@ -766,10 +767,27 @@ When `MINUSNEGATE` is used with [`NEGATE`](#negate), exclusion patterns are reco
 
 `GLOBSTAR` enables the feature where `**` matches zero or more directories.
 
+#### `glob.GLOBSTARLONG, glob.GL` {: #globstarlong}
+
+/// new | New 10.0
+///
+
+When `GLOBSTARLONG` is enabled `***` will act like `**`, but will cause symlinks to be traversed as well.
+
+Enabling `GLOBSTARLONG` automatically enables [`GLOBSTAR`](#globstar).
+
+[`FOLLOW`](#follow) will be ignored and `***` will be required to traverse a symlink. But it should be noted that when
+using [`MATCHBASE`](#matchbase) and [`FOLLOW`](#follow) with `GLOBSTARLONG`, that [`FOLLOW`](#follow) will cause the
+implicit leading `**` that [`MATCHBASE`](#matchbase) applies to act as an implicit `***`.
+
 #### `glob.FOLLOW, glob.L` {: #follow}
 
 `FOLLOW` will cause [`GLOBSTAR`](#globstar) patterns (`**`) to traverse symlink directories.
 
+`FOLLOW` will have no affect if using [`GLOBSTARLONG`](#globstarlong) and an explicit `***` will be required to traverse
+a symlink. `FOLLOW` will have an affect if enabled with [`GLOBSTARLONG`](#globstarlong) and [`MATCHBASE`](#matchbase)
+and will cause the implicit leading `**` that `MATCHBASE` applies to act as an implicit `***`.
+
 #### `glob.REALPATH, glob.P` {: #realpath}
 
 In the past, only [`glob`](#glob) and [`iglob`](#iglob) operated on the filesystem, but with `REALPATH`, other
@@ -786,6 +804,9 @@ logic so that the path must meet the following in order to match:
 -   Path must exist.
 -   Directories that are symlinks will not be traversed by [`GLOBSTAR`](#globstar) patterns (`**`) unless the
     [`FOLLOW`](#follow) flag is enabled.
+-   If [`GLOBSTARLONG`](#globstarlong) is enabled, `***` will traverse symlinks, [`FOLLOW`](#follow) will be ignored
+    except if [`MATCHBASE`](#matchbase) is also enabled, in that case, the implicit leading `**` added by
+    [`MATCHBASE`](#matchbase) will act as `***`.
 -   When presented with a pattern where the match must be a directory, but the file path being compared doesn't indicate
     the file is a directory with a trailing slash, the command will look at the filesystem to determine if it is a
     directory.
diff --git a/docs/src/markdown/pathlib.md b/docs/src/markdown/pathlib.md
index b095e26..0965480 100644
--- a/docs/src/markdown/pathlib.md
+++ b/docs/src/markdown/pathlib.md
@@ -252,22 +252,12 @@ pattern(s).
 
 `match` mimics Python's `pathlib` version of `match`. Python's `match` uses a right to left evaluation. Wildcard Match
 emulates this behavior as well. What this means is that when provided with a path `some/path/name`, the patterns `name`,
-`path/name` and `some/path/name` will all match.
-
-Because the path is evaluated right to left, dot files may not prevent matches when `DOTGLOB` is disabled.
-
-```pycon3
->>> from wcmatch import pathlib
->>> pathlib.PurePath('.dotfile/file').match('file')
-True
->>> pathlib.PurePath('../.dotfile/file').match('file')
-True
-```
+`path/name` and `some/path/name` will all match. Essentially, it matches what [`rglob`](#rglob) returns.
 
 `match` does not access the filesystem, but you can force the path to access the filesystem if you give it the
 [`REALPATH`](#realpath) flag. We do not restrict this, but we do not enable it by default.
-[`REALPATH`](#realpath) simply forces the match to check the filesystem to see if the file exists and is a
-directory or not.
+[`REALPATH`](#realpath) simply forces the match to check the filesystem to see if the file exists, if it is a
+directory or not, and whether it is a symlink.
 
 Since [`Path`](#path) is derived from [`PurePath`](#purepath), this method is also available in
 [`Path`](#path) objects.
@@ -302,8 +292,8 @@ a list of patterns. It will return a boolean indicating whether the objects file
 
 `globmatch` does not access the filesystem, but you can force the path to access the filesystem if you give it the
 [`REALPATH`](#realpath) flag. We do not restrict this, but we do not enable it by default.
-[`REALPATH`](#realpath) simply forces the match to check the filesystem to see if the file exists and is a
-directory or not.
+[`REALPATH`](#realpath) simply forces the match to check the filesystem to see if the file exists, if it is a
+directory or not, and whether it is a symlink.
 
 Since [`Path`](#path) is derived from  [`PurePath`](#purepath), this method is also available in
 [`Path`](#path) objects.
@@ -323,6 +313,19 @@ True
 `exclude` parameter was added.
 ///
 
+#### `PurePath.full_match` {: #full_match}
+
+/// new | new 10.0
+///
+
+```py3
+def full_match(self, patterns, *, flags=0, limit=1000, exclude=None):
+```
+
+Python 3.13 added the new `full_match` method to `PurePath` objects. Essentially, this does for normal `pathlib`
+what our existing `PurePath.globmatch` has been doing prior to Python 3.13. We've added an alias for
+`PurePath.full_match` that redirects to [`PurePath.globmatch`](#globmatch) for completeness.
+
 #### `Path.glob` {: #glob}
 
 ```py3
@@ -446,10 +449,27 @@ When `MINUSNEGATE` is used with [`NEGATE`](#negate), exclusion patterns are reco
 
 `GLOBSTAR` enables the feature where `**` matches zero or more directories.
 
+#### `glob.GLOBSTARLONG, glob.GL` {: #globstarlong}
+
+/// new | New 10.0
+///
+
+When `GLOBSTARLONG` is enabled `***` will act like `**`, but will cause symlinks to be traversed as well.
+
+Enabling `GLOBSTARLONG` automatically enables [`GLOBSTAR`](#globstar).
+
+[`FOLLOW`](#follow) will be ignored and `***` will be required to traverse a symlink. But it should be noted that when
+using [`MATCHBASE`](#matchbase) and [`FOLLOW`](#follow) with `GLOBSTARLONG`, that [`FOLLOW`](#follow) will cause the
+implicit leading `**` that [`MATCHBASE`](#matchbase) applies to act as an implicit `***`.
+
 #### `pathlib.FOLLOW, pathlib.L` {: #follow}
 
 `FOLLOW` will cause `GLOBSTAR` patterns (`**`) to match and traverse symlink directories.
 
+`FOLLOW` will have no affect if using [`GLOBSTARLONG`](#globstarlong) and an explicit `***` will be required to traverse
+a symlink. `FOLLOW` will have an affect if enabled with [`GLOBSTARLONG`](#globstarlong) and [`MATCHBASE`](#matchbase)
+and will cause the implicit leading `**` that `MATCHBASE` applies to act as an implicit `***`.
+
 #### `pathlib.REALPATH, pathlib.P` {: #realpath}
 
 In the past, only `glob` and `iglob` operated on the filesystem, but with `REALPATH`, other functions will now operate
@@ -466,6 +486,10 @@ that the path must meet the following in order to match:
 -   Path must exist.
 -   Directories that are symlinks will not be matched by [`GLOBSTAR`](#globstar) patterns (`**`) unless the
     [`FOLLOW`](#follow) flag is enabled.
+-   If [`GLOBSTARLONG`](#globstarlong) is enabled, `***` will traverse symlinks, [`FOLLOW`](#follow) will be ignored
+    except if [`MATCHBASE`](#matchbase) is also enabled, in that case, the implicit leading `**` added by
+    [`MATCHBASE`](#matchbase) will act as `***`. This also affects the implicit leading `**` adding by
+    [`rglob`](#rglob).
 -   When presented with a pattern where the match must be a directory, but the file path being compared doesn't indicate
     the file is a directory with a trailing slash, the command will look at the filesystem to determine if it is a
     directory.
diff --git a/wcmatch/_wcparse.py b/wcmatch/_wcparse.py
index ba297f5..628f673 100644
--- a/wcmatch/_wcparse.py
+++ b/wcmatch/_wcparse.py
@@ -148,14 +148,14 @@ FORCEUNIX = 0x20000
 GLOBTILDE = 0x40000
 NOUNIQUE = 0x80000
 NODOTDIR = 0x100000
+GLOBSTARLONG = 0x200000
 
 # Internal flag
 _TRANSLATE = 0x100000000  # Lets us know we are performing a translation, and we just want the regex.
 _ANCHOR = 0x200000000  # The pattern, if it starts with a slash, is anchored to the working directory; strip the slash.
 _EXTMATCHBASE = 0x400000000  # Like `MATCHBASE`, but works for multiple directory levels.
 _NOABSOLUTE = 0x800000000  # Do not allow absolute patterns
-_RTL = 0x1000000000  # Match from right to left
-_NO_GLOBSTAR_CAPTURE = 0x2000000000  # Disallow `GLOBSTAR` capturing groups.
+_NO_GLOBSTAR_CAPTURE = 0x1000000000  # Disallow `GLOBSTAR` capturing groups.
 
 FLAG_MASK = (
     CASE |
@@ -167,6 +167,7 @@ FLAG_MASK = (
     DOTMATCH |
     EXTMATCH |
     GLOBSTAR |
+    GLOBSTARLONG |
     BRACE |
     REALPATH |
     FOLLOW |
@@ -182,7 +183,6 @@ FLAG_MASK = (
     _TRANSLATE |
     _ANCHOR |
     _EXTMATCHBASE |
-    _RTL |
     _NOABSOLUTE |
     _NO_GLOBSTAR_CAPTURE
 )
@@ -210,8 +210,6 @@ _PATH_STAR_NO_DOTMATCH = _NO_DIR + fr'(?:(?!\.){_PATH_STAR})?'
 _PATH_GSTAR_DOTMATCH = r'(?:(?!(?:[{sep}]|^)(?:\.{{1,2}})($|[{sep}])).)*?'
 # `GLOBSTAR` with `DOTMATCH` disabled. Don't allow a dot to follow /
 _PATH_GSTAR_NO_DOTMATCH = r'(?:(?!(?:[{sep}]|^)\.).)*?'
-# Special right to left matching
-_PATH_GSTAR_RTL_MATCH = r'.*?'
 # Next char cannot be a dot
 _NO_DOT = r'(?![.])'
 # Following char from sequence cannot be a separator or a dot
@@ -783,7 +781,7 @@ def compile(  # noqa: A001
     positive, negative = compile_pattern(patterns, flags, limit, exclude)
     return WcRegexp(
         tuple(positive), tuple(negative),
-        bool(flags & REALPATH), bool(flags & PATHNAME), bool(flags & FOLLOW)
+        bool(flags & REALPATH), bool(flags & PATHNAME), bool(flags & FOLLOW) and not bool(flags & GLOBSTARLONG)
     )
 
 
@@ -932,7 +930,9 @@ class WcParse(Generic[AnyStr]):
         self.is_bytes = isinstance(pattern, bytes)
         self.pathname = bool(flags & PATHNAME)
         self.raw_chars = bool(flags & RAWCHARS)
-        self.globstar = self.pathname and bool(flags & GLOBSTAR)
+        self.globstarlong = self.pathname and bool(flags & GLOBSTARLONG)
+        self.globstar = self.pathname and (self.globstarlong or bool(flags & GLOBSTAR))
+        self.follow = bool(flags & FOLLOW)
         self.realpath = bool(flags & REALPATH) and self.pathname
         self.translate = bool(flags & _TRANSLATE)
         self.negate = bool(flags & NEGATE)
@@ -941,7 +941,6 @@ class WcParse(Generic[AnyStr]):
         self.extend = bool(flags & EXTMATCH)
         self.matchbase = bool(flags & MATCHBASE)
         self.extmatchbase = bool(flags & _EXTMATCHBASE)
-        self.rtl = bool(flags & _RTL)
         self.anchor = bool(flags & _ANCHOR)
         self.nodotdir = bool(flags & NODOTDIR)
         self.capture = self.translate
@@ -1273,8 +1272,7 @@ class WcParse(Generic[AnyStr]):
             else:
                 star = self.path_star
                 globstar = self.path_gstar_dot1
-            if self.globstar_capture:
-                globstar = f'({globstar})'
+            capture = self.globstar_capture
         else:
             if self.after_start and not self.dot:
                 star = _NO_DOT + _STAR
@@ -1284,15 +1282,29 @@ class WcParse(Generic[AnyStr]):
         value = star
 
         if self.after_start and self.globstar and not self.in_list:
-            skip = False
+            skip = True
             try:
                 c = next(i)
                 if c != '*':
                     i.rewind(1)
                     raise StopIteration
+                skip = False
+
+                # Test for triple star. If found, do not make a capturing group.
+                # Capturing groups are used to filter out symlinks, but triple stars force symlinks.
+                if self.globstarlong:
+                    c = next(i)
+                    if c != '*':
+                        i.rewind(1)
+                        raise StopIteration
+                    capture = False
+
             except StopIteration:
                 # Could not acquire a second star, so assume single star pattern
-                skip = True
+                pass
+
+            if capture:
+                globstar = f'({globstar})'
 
             if not skip:
                 try:
@@ -1561,7 +1573,6 @@ class WcParse(Generic[AnyStr]):
         if root_specified:
             self.matchbase = False
             self.extmatchbase = False
-            self.rtl = False
 
         if not root_specified and self.realpath:
             current.append(_NO_WIN_ROOT if self.win_drive_detect else _NO_ROOT)
@@ -1630,31 +1641,15 @@ class WcParse(Generic[AnyStr]):
             if number:
                 self.matchbase = False
                 self.extmatchbase = False
-                self.rtl = False
 
         if self.matchbase or self.extmatchbase:
-            globstar = self.globstar
-            self.globstar = True
-            self.root('**', prepend)
-            self.globstar = globstar
-
-        elif self.rtl:
-            # Add a `**` that can capture anything: dots, special directories, symlinks, etc.
-            # We are simulating right to left, so everything on the left should be accepted without
-            # question.
-            globstar = self.globstar
-            dot = self.dot
-            gstar = self.path_gstar_dot1
-            globstar_capture = self.globstar_capture
-            self.path_gstar_dot1 = _PATH_GSTAR_RTL_MATCH
-            self.dot = True
-            self.globstar = True
-            self.globstar_capture = False
-            self.root('**', prepend)
-            self.globstar = globstar
-            self.dot = dot
-            self.path_gstar_dot1 = gstar
-            self.globstar_capture = globstar_capture
+            if self.globstarlong and self.follow:
+                self.root('***', prepend)
+            else:
+                globstar = self.globstar
+                self.globstar = True
+                self.root('**', prepend)
+                self.globstar = globstar
 
         # We have an escape, but it escapes nothing
         if p == '\\':
@@ -1663,7 +1658,7 @@ class WcParse(Generic[AnyStr]):
         if p:
             self.root(p, result)
 
-        if p and (self.matchbase or self.extmatchbase or self.rtl):
+        if p and (self.matchbase or self.extmatchbase):
             result = prepend + result
 
         case_flag = 'i' if not self.case_sensitive else ''
diff --git a/wcmatch/glob.py b/wcmatch/glob.py
index adf5152..bd1a0bc 100644
--- a/wcmatch/glob.py
+++ b/wcmatch/glob.py
@@ -19,8 +19,8 @@ __all__ = (
     "CASE", "IGNORECASE", "RAWCHARS", "DOTGLOB", "DOTMATCH",
     "EXTGLOB", "EXTMATCH", "GLOBSTAR", "NEGATE", "MINUSNEGATE", "BRACE", "NOUNIQUE",
     "REALPATH", "FOLLOW", "MATCHBASE", "MARK", "NEGATEALL", "NODIR", "FORCEWIN", "FORCEUNIX", "GLOBTILDE",
-    "NODOTDIR", "SCANDOTDIR", "SUPPORT_DIR_FD",
-    "C", "I", "R", "D", "E", "G", "N", "M", "B", "P", "L", "S", "X", 'K', "O", "A", "W", "U", "T", "Q", "Z", "SD",
+    "NODOTDIR", "SCANDOTDIR", "SUPPORT_DIR_FD", "GLOBSTARLONG",
+    "C", "I", "R", "D", "E", "G", "N", "M", "B", "P", "L", "S", "X", 'K', "O", "A", "W", "U", "T", "Q", "Z", "SD", "GL",
     "iglob", "glob", "globmatch", "globfilter", "escape", "is_magic"
 )
 
@@ -50,6 +50,7 @@ U = FORCEUNIX = _wcparse.FORCEUNIX
 T = GLOBTILDE = _wcparse.GLOBTILDE
 Q = NOUNIQUE = _wcparse.NOUNIQUE
 Z = NODOTDIR = _wcparse.NODOTDIR
+GL = GLOBSTARLONG = _wcparse.GLOBSTARLONG
 
 K = MARK = 0x1000000
 SD = SCANDOTDIR = 0x2000000
@@ -58,7 +59,6 @@ _PATHLIB = 0x8000000
 
 # Internal flags
 _EXTMATCHBASE = _wcparse._EXTMATCHBASE
-_RTL = _wcparse._RTL
 _NOABSOLUTE = _wcparse._NOABSOLUTE
 _PATHNAME = _wcparse.PATHNAME
 
@@ -69,6 +69,7 @@ FLAG_MASK = (
     DOTMATCH |
     EXTMATCH |
     GLOBSTAR |
+    GLOBSTARLONG |
     NEGATE |
     MINUSNEGATE |
     BRACE |
@@ -84,7 +85,6 @@ FLAG_MASK = (
     NOUNIQUE |
     NODOTDIR |
     _EXTMATCHBASE |
-    _RTL |
     _NOABSOLUTE
 )
 
@@ -121,7 +121,7 @@ def _flag_transform(flags: int) -> int:
 
 
 class _GlobPart(
-    namedtuple('_GlobPart', ['pattern', 'is_magic', 'is_globstar', 'dir_only', 'is_drive']),
+    namedtuple('_GlobPart', ['pattern', 'is_magic', 'is_globstar', 'is_globstarlong', 'dir_only', 'is_drive']),
 ):
     """File Glob."""
 
@@ -161,7 +161,9 @@ class _GlobSplit(Generic[AnyStr]):
         self.unix = _wcparse.is_unix_style(flags)
         self.flags = flags
         self.no_abs = bool(flags & _wcparse._NOABSOLUTE)
-        self.globstar = bool(flags & GLOBSTAR)
+        self.globstarlong = bool(flags & GLOBSTARLONG)
+        self.globstar = self.globstarlong or bool(flags & GLOBSTAR)
+        self.follow = bool(flags & FOLLOW)
         self.matchbase = bool(flags & MATCHBASE)
         self.extmatchbase = bool(flags & _wcparse._EXTMATCHBASE)
         self.tilde = bool(flags & GLOBTILDE)
@@ -277,16 +279,17 @@ class _GlobSplit(Generic[AnyStr]):
         if l and value in (b'', ''):
             return
 
-        globstar = value in (b'**', '**') and self.globstar
+        globstarlong = self.globstarlong and value in (b'***', '***')
+        globstar = globstarlong or (self.globstar and value in (b'**', '**'))
         magic = self.is_magic(value)
         if magic:
             v = _wcparse._compile(value, self.flags)  # type: Pattern[AnyStr] | AnyStr
         else:
             v = value
         if globstar and l and l[-1].is_globstar:
-            l[-1] = _GlobPart(v, magic, globstar, dir_only, False)
+            l[-1] = _GlobPart(v, magic, globstar, globstarlong, dir_only, False)
         else:
-            l.append(_GlobPart(v, magic, globstar, dir_only, False))
+            l.append(_GlobPart(v, magic, globstar, globstarlong, dir_only, False))
 
     def split(self) -> list[_GlobPart]:
         """Start parsing the pattern."""
@@ -308,11 +311,11 @@ class _GlobSplit(Generic[AnyStr]):
         if self.win_drive_detect:
             root_specified, drive, slash, end = _wcparse._get_win_drive(pattern)
             if drive is not None:
-                parts.append(_GlobPart(drive.encode('latin-1') if is_bytes else drive, False, False, True, True))
+                parts.append(_GlobPart(drive.encode('latin-1') if is_bytes else drive, False, False, False, True, True))
                 start = end - 1
                 i.advance(start)
             elif drive is None and root_specified:
-                parts.append(_GlobPart(b'\\' if is_bytes else '\\', False, False, True, True))
+                parts.append(_GlobPart(b'\\' if is_bytes else '\\', False, False, False, True, True))
                 if pattern.startswith('/'):
                     start = 0
                     i.advance(1)
@@ -320,7 +323,7 @@ class _GlobSplit(Generic[AnyStr]):
                     start = 1
                     i.advance(2)
         elif not self.win_drive_detect and pattern.startswith('/'):
-            parts.append(_GlobPart(b'/' if is_bytes else '/', False, False, True, True))
+            parts.append(_GlobPart(b'/' if is_bytes else '/', False, False, False, True, True))
             start = 0
             i.advance(1)
 
@@ -357,14 +360,21 @@ class _GlobSplit(Generic[AnyStr]):
                 self.store(value.encode('latin-1') if is_bytes else value, parts, False)  # type: ignore[arg-type]
 
         if len(pattern) == 0:
-            parts.append(_GlobPart(pattern.encode('latin-1') if is_bytes else pattern, False, False, False, False))
+            parts.append(
+                _GlobPart(pattern.encode('latin-1') if is_bytes else pattern, False, False, False, False, False)
+            )
 
         if (
             (self.extmatchbase and not parts[0].is_drive) or
             (self.matchbase and len(parts) == 1 and not parts[0].dir_only)
         ):
-            self.globstar = True
-            parts.insert(0, _GlobPart(b'**' if is_bytes else '**', True, True, True, False))
+            if self.globstarlong and self.follow:
+                gstar = b'***' if is_bytes else '***'  # type: Any
+                is_globstarlong = True
+            else:
+                gstar = b'**' if is_bytes else '**'
+                is_globstarlong = False
+            parts.insert(0, _GlobPart(gstar, True, True, is_globstarlong, True, False))
 
         if self.no_abs and parts and parts[0].is_drive:
             raise ValueError('The pattern must be a relative path pattern')
@@ -411,19 +421,17 @@ class Glob(Generic[AnyStr]):
         self.pathlib = bool(flags & _PATHLIB)  # type: bool
         if self.pathlib:
             flags ^= _PATHLIB
-        # Right to left searching is only for matching
-        if flags & _RTL:  # pragma: no cover
-            flags ^= _RTL
         self.flags = _flag_transform(flags | REALPATH)  # type: int
         self.negate_flags = self.flags | DOTMATCH | _wcparse._NO_GLOBSTAR_CAPTURE  # type: int
         if not self.scandotdir and not self.flags & NODOTDIR:
             self.flags |= NODOTDIR
         self.raw_chars = bool(self.flags & RAWCHARS)  # type: bool
-        self.follow_links = bool(self.flags & FOLLOW)  # type: bool
         self.dot = bool(self.flags & DOTMATCH)  # type: bool
         self.unix = not bool(self.flags & FORCEWIN)  # type: bool
         self.negate = bool(self.flags & NEGATE)  # type: bool
-        self.globstar = bool(self.flags & GLOBSTAR)  # type: bool
+        self.globstarlong = bool(self.flags & GLOBSTARLONG)  # type: bool
+        self.globstar = self.globstarlong or bool(self.flags & GLOBSTAR)  # type: bool
+        self.follow_links = bool(self.flags & FOLLOW) and not self.globstarlong  # type: bool
         self.braces = bool(self.flags & BRACE)  # type: bool
         self.matchbase = bool(self.flags & MATCHBASE)  # type: bool
         self.case_sensitive = _wcparse.get_case(self.flags)  # type: bool
@@ -652,7 +660,8 @@ class Glob(Generic[AnyStr]):
         curdir: AnyStr,
         matcher: Callable[..., Any] | None,
         dir_only: bool = False,
-        deep: bool = False
+        deep: bool = False,
+        globstar_follow: bool = False
     ) -> Iterator[tuple[AnyStr, bool]]:
         """Recursive directory glob."""
 
@@ -664,12 +673,12 @@ class Glob(Generic[AnyStr]):
                 continue
 
             path = os.path.join(curdir, file)
-            follow = not is_link or self.follow_links
             if (matcher is None and not hidden) or (matcher and matcher(file)):
                 yield path, is_dir
 
+            follow = not is_link or self.follow_links or globstar_follow
             if deep and not hidden and is_dir and follow:
-                yield from self._glob_dir(path, matcher, dir_only, deep)
+                yield from self._glob_dir(path, matcher, dir_only, deep, globstar_follow)
 
     def _glob(self, curdir: AnyStr, part: _GlobPart, rest: list[_GlobPart]) -> Iterator[tuple[AnyStr, bool]]:
         """
@@ -689,6 +698,7 @@ class Glob(Generic[AnyStr]):
         dir_only = part.dir_only
         target = part.pattern
         is_globstar = part.is_globstar
+        is_globstarlong = part.is_globstarlong
 
         if is_magic and is_globstar:
             # Glob star directory `**`.
@@ -724,7 +734,7 @@ class Glob(Generic[AnyStr]):
                 yield os.path.join(curdir, self.empty), True
 
             # Search
-            for path, is_dir in self._glob_dir(curdir, matcher, dir_only, deep=True):
+            for path, is_dir in self._glob_dir(curdir, matcher, dir_only, deep=True, globstar_follow=is_globstarlong):
                 if this:
                     yield from self._glob(path, this, rest[:])
                 else:
diff --git a/wcmatch/pathlib.py b/wcmatch/pathlib.py
index 13e77d6..84d2218 100644
--- a/wcmatch/pathlib.py
+++ b/wcmatch/pathlib.py
@@ -11,8 +11,8 @@ __all__ = (
     "CASE", "IGNORECASE", "RAWCHARS", "DOTGLOB", "DOTMATCH",
     "EXTGLOB", "EXTMATCH", "NEGATE", "MINUSNEGATE", "BRACE",
     "REALPATH", "FOLLOW", "MATCHBASE", "NEGATEALL", "NODIR", "NOUNIQUE",
-    "NODOTDIR", "SCANDOTDIR",
-    "C", "I", "R", "D", "E", "G", "N", "B", "M", "P", "L", "S", "X", "O", "A", "Q", "Z", "SD",
+    "NODOTDIR", "SCANDOTDIR", "GLOBSTARLONG",
+    "C", "I", "R", "D", "E", "G", "N", "B", "M", "P", "L", "S", "X", "O", "A", "Q", "Z", "SD", "GL",
     "Path", "PurePath", "WindowsPath", "PosixPath", "PurePosixPath", "PureWindowsPath"
 )
 
@@ -33,12 +33,12 @@ O = NODIR = glob.NODIR
 A = NEGATEALL = glob.NEGATEALL
 Q = NOUNIQUE = glob.NOUNIQUE
 Z = NODOTDIR = glob.NODOTDIR
+GL = GLOBSTARLONG = glob.GLOBSTARLONG
 
 SD = SCANDOTDIR = glob.SCANDOTDIR
 
 # Internal flags
 _EXTMATCHBASE = _wcparse._EXTMATCHBASE
-_RTL = _wcparse._RTL
 _NOABSOLUTE = _wcparse._NOABSOLUTE
 _PATHNAME = _wcparse.PATHNAME
 _FORCEWIN = _wcparse.FORCEWIN
@@ -53,6 +53,7 @@ FLAG_MASK = (
     DOTMATCH |
     EXTMATCH |
     GLOBSTAR |
+    GLOBSTARLONG |
     NEGATE |
     MINUSNEGATE |
     BRACE |
@@ -65,7 +66,6 @@ FLAG_MASK = (
     NOUNIQUE |
     NODOTDIR |
     _EXTMATCHBASE |
-    _RTL |
     _NOABSOLUTE
 )
 
@@ -124,12 +124,9 @@ class PurePath(pathlib.PurePath):
 
         This uses the same right to left logic that the default `pathlib` object uses.
         Folders and files are essentially matched from right to left.
-
-        `GLOBSTAR` is enabled by default in order match the default behavior of `pathlib`.
-
         """
 
-        return self.globmatch(patterns, flags=flags | _RTL, limit=limit, exclude=exclude)
+        return self.globmatch(patterns, flags=flags | _EXTMATCHBASE, limit=limit, exclude=exclude)
 
     def globmatch(
         self,
@@ -139,12 +136,25 @@ class PurePath(pathlib.PurePath):
         limit: int = _wcparse.PATTERN_LIMIT,
         exclude: str | Sequence[str] | None = None
     ) -> bool:
-        """
-        Match patterns using `globmatch`, but without the right to left logic that the default `pathlib` uses.
+        """Match patterns using `globmatch`, but without the right to left logic that the default `pathlib` uses."""
 
-        `GLOBSTAR` is enabled by default in order match the default behavior of `pathlib`.
+        return glob.globmatch(
+            self._translate_path(),
+            patterns,
+            flags=self._translate_flags(flags),
+            limit=limit,
+            exclude=exclude
+        )
 
-        """
+    def full_match(  # type: ignore[override, unused-ignore]
+        self,
+        patterns: str | Sequence[str],
+        *,
+        flags: int = 0,
+        limit: int = _wcparse.PATTERN_LIMIT,
+        exclude: str | Sequence[str] | None = None
+    ) -> bool:
+        """Alias for Python 3.13 `full_match`, but redirects to use `globmatch`."""
 
         return glob.globmatch(
             self._translate_path(),


Test patch:
diff --git a/tests/test_fnmatch.py b/tests/test_fnmatch.py
index 96800fd..a13313b 100644
--- a/tests/test_fnmatch.py
+++ b/tests/test_fnmatch.py
@@ -45,12 +45,12 @@ class TestFnMatch:
         ['b', 'a', False, 0],
 
         # Test that '\' is handled correctly in character sets;
-        [r'[\]', '\\', False, 0],
-        [r'[!\]', 'a', False, 0],
-        [r'[!\]', '\\', False, 0],
-        [r'[\\]', '\\', True, 0],
-        [r'[!\\]', 'a', True, 0],
-        [r'[!\\]', '\\', False, 0],
+        [R'[\]', '\\', False, 0],
+        [R'[!\]', 'a', False, 0],
+        [R'[!\]', '\\', False, 0],
+        [R'[\\]', '\\', True, 0],
+        [R'[!\\]', 'a', True, 0],
+        [R'[!\\]', '\\', False, 0],
 
         # Test that filenames with newlines in them are handled correctly.
         ['foo*', 'foo\nbar', True, 0],
@@ -67,14 +67,14 @@ class TestFnMatch:
         # Case and Force Unix: slash conventions
         ['usr/bin', 'usr/bin', True, fnmatch.C | fnmatch.U],
         ['usr/bin', 'usr\\bin', False, fnmatch.C | fnmatch.U],
-        [r'usr\\bin', 'usr/bin', False, fnmatch.C | fnmatch.U],
-        [r'usr\\bin', 'usr\\bin', True, fnmatch.C | fnmatch.U],
+        [R'usr\\bin', 'usr/bin', False, fnmatch.C | fnmatch.U],
+        [R'usr\\bin', 'usr\\bin', True, fnmatch.C | fnmatch.U],
 
         # Case and Force Windows: slash conventions
         ['usr/bin', 'usr/bin', True, fnmatch.C | fnmatch.W],
         ['usr/bin', 'usr\\bin', True, fnmatch.C | fnmatch.W],
-        [r'usr\\bin', 'usr/bin', True, fnmatch.C | fnmatch.W],
-        [r'usr\\bin', 'usr\\bin', True, fnmatch.C | fnmatch.W],
+        [R'usr\\bin', 'usr/bin', True, fnmatch.C | fnmatch.W],
+        [R'usr\\bin', 'usr\\bin', True, fnmatch.C | fnmatch.W],
 
         # Wildcard tests
         [b'te*', b'test', True, 0],
@@ -94,12 +94,12 @@ class TestFnMatch:
         # OS specific slash behavior
         ['usr/bin', 'usr/bin', True, 0],
         ['usr/bin', 'usr\\bin', not util.is_case_sensitive(), 0],
-        [r'usr\\bin', 'usr/bin', not util.is_case_sensitive(), 0],
-        [r'usr\\bin', 'usr\\bin', True, 0],
+        [R'usr\\bin', 'usr/bin', not util.is_case_sensitive(), 0],
+        [R'usr\\bin', 'usr\\bin', True, 0],
         ['usr/bin', 'usr\\bin', True, fnmatch.W],
-        [r'usr\\bin', 'usr/bin', True, fnmatch.W],
+        [R'usr\\bin', 'usr/bin', True, fnmatch.W],
         ['usr/bin', 'usr\\bin', False, fnmatch.U],
-        [r'usr\\bin', 'usr/bin', False, fnmatch.U],
+        [R'usr\\bin', 'usr/bin', False, fnmatch.U],
 
         # Ensure that we don't fail on regular expression related symbols
         # such as &&, ||, ~~, --, or [.  Currently re doesn't do anything with
@@ -113,7 +113,7 @@ class TestFnMatch:
 
         # `Dotmatch` cases
         ['.abc', '.abc', True, 0],
-        [r'\.abc', '.abc', True, 0],
+        [R'\.abc', '.abc', True, 0],
         ['?abc', '.abc', True, 0],
         ['*abc', '.abc', True, 0],
         ['[.]abc', '.abc', True, 0],
@@ -126,12 +126,12 @@ class TestFnMatch:
 
         # Turn off `dotmatch` cases
         ['.abc', '.abc', True, fnmatch.D],
-        [r'\.abc', '.abc', True, fnmatch.D],
+        [R'\.abc', '.abc', True, fnmatch.D],
         ['?abc', '.abc', False, fnmatch.D],
         ['*abc', '.abc', False, fnmatch.D],
         ['[.]abc', '.abc', False, fnmatch.D],
         ['*(.)abc', '.abc', True, fnmatch.E | fnmatch.D],
-        [r'*(\.)abc', '.abc', True, fnmatch.E | fnmatch.D],
+        [R'*(\.)abc', '.abc', True, fnmatch.E | fnmatch.D],
         ['*(?)abc', '.abc', False, fnmatch.E | fnmatch.D],
         ['*(?|.)abc', '.abc', True, fnmatch.E | fnmatch.D],
         ['*(?|*)abc', '.abc', False, fnmatch.E | fnmatch.D],
@@ -140,7 +140,7 @@ class TestFnMatch:
         ['a*bc', 'a.bc', True, fnmatch.D],
         ['a[.]bc', 'a.bc', True, fnmatch.D],
         ['a*(.)bc', 'a.bc', True, fnmatch.E | fnmatch.D],
-        [r'a*(\.)bc', 'a.bc', True, fnmatch.E | fnmatch.D],
+        [R'a*(\.)bc', 'a.bc', True, fnmatch.E | fnmatch.D],
         ['a*(?)bc', 'a.bc', True, fnmatch.E | fnmatch.D],
         ['a*(?|.)bc', 'a.bc', True, fnmatch.E | fnmatch.D],
         ['a*(?|*)bc', 'a.bc', True, fnmatch.E | fnmatch.D],
@@ -183,10 +183,10 @@ class TestFnMatch:
         # `(` must be escaped to exclude a name that starts with `(`.
         # If `!(` doesn't start a valid extended glob pattern,
         # it will be treated as a literal, not an exclude pattern.
-        [r'!\(test)', 'test', True, fnmatch.N | fnmatch.E | fnmatch.A],
-        [r'!(test)', 'test', False, fnmatch.N | fnmatch.E | fnmatch.A],
-        [r'!!(test)', 'test', True, fnmatch.N | fnmatch.E | fnmatch.A],
-        [r'!(test', '!(test', True, fnmatch.N | fnmatch.E | fnmatch.A],
+        [R'!\(test)', 'test', True, fnmatch.N | fnmatch.E | fnmatch.A],
+        [R'!(test)', 'test', False, fnmatch.N | fnmatch.E | fnmatch.A],
+        [R'!!(test)', 'test', True, fnmatch.N | fnmatch.E | fnmatch.A],
+        [R'!(test', '!(test', True, fnmatch.N | fnmatch.E | fnmatch.A],
 
         # Backwards ranges
         ['[a-z]', 'a', True, 0],
@@ -196,9 +196,9 @@ class TestFnMatch:
         ['[9--]', '9', False, 0],
 
         # Escaped slashes are just slashes as they aren't treated special beyond normalization.
-        [r'a\/b', ('a/b' if util.is_case_sensitive() else 'a\\\\b'), True, 0],
-        [r'a\/b', 'a/b', True, fnmatch.U],
-        [r'a\/b', 'a\\\\b', True, fnmatch.W]
+        [R'a\/b', ('a/b' if util.is_case_sensitive() else 'a\\\\b'), True, 0],
+        [R'a\/b', 'a/b', True, fnmatch.U],
+        [R'a\/b', 'a\\\\b', True, fnmatch.W]
     ]
 
     @classmethod
@@ -273,13 +273,13 @@ class TestFnMatchFilter:
             0
         ],
         [
-            r'usr\\*',
+            R'usr\\*',
             ['usr/bin', 'usr', 'usr\\lib'],
             (['usr/bin', 'usr\\lib'] if not util.is_case_sensitive() else ['usr\\lib']),
             0
         ],
-        [r'te\st[ma]', ['testm', 'test\\3', 'testa'], ['testm', 'testa'], fnmatch.I],
-        [r'te\st[ma]', ['testm', 'test\\3', 'testa'], ['testm', 'testa'], fnmatch.C],
+        [R'te\st[ma]', ['testm', 'test\\3', 'testa'], ['testm', 'testa'], fnmatch.I],
+        [R'te\st[ma]', ['testm', 'test\\3', 'testa'], ['testm', 'testa'], fnmatch.C],
 
         # Issue #24
         ['*.bar', ["goo.cfg", "foo.bar", "foo.bar.cfg", "foo.cfg.bar"], ["foo.bar", "foo.cfg.bar"], 0],
@@ -395,22 +395,22 @@ class TestFnMatchTranslate(unittest.TestCase):
         self.assertEqual(p1, [r'^(?s:test[^chars])$'])
         self.assertEqual(p2, [])
 
-        p1 = self.split_translate(r'test[^\\-\\&]', flags=flags)[0]
+        p1 = self.split_translate(R'test[^\\-\\&]', flags=flags)[0]
         self.assertEqual(p1, [r'^(?s:test[^\\-\\\&])$'])
 
-        p1 = self.split_translate(r'\\*\\?\\|\\[\\]', flags=flags)[0]
+        p1 = self.split_translate(R'\\*\\?\\|\\[\\]', flags=flags)[0]
         self.assertEqual(p1, [r'^(?s:\\.*?\\.\\)$', r'^(?s:\\[\\])$'])
 
-        p1 = self.split_translate(r'\\u0300', flags=flags | fnmatch.R)[0]
+        p1 = self.split_translate(R'\\u0300', flags=flags | fnmatch.R)[0]
         self.assertEqual(p1, [r'^(?s:\\u0300)$'])
 
     def test_posix_range(self):
         """Test posix range."""
 
-        p = fnmatch.translate(r'[[:ascii:]-z]', flags=self.flags | fnmatch.U | fnmatch.C)
+        p = fnmatch.translate(R'[[:ascii:]-z]', flags=self.flags | fnmatch.U | fnmatch.C)
         self.assertEqual(p, (['^(?s:[\x00-\x7f\\-z])$'], []))
 
-        p = fnmatch.translate(r'[a-[:ascii:]-z]', flags=self.flags | fnmatch.U | fnmatch.C)
+        p = fnmatch.translate(R'[a-[:ascii:]-z]', flags=self.flags | fnmatch.U | fnmatch.C)
         self.assertEqual(p, (['^(?s:[a\\-\x00-\x7f\\-z])$'], []))
 
     @mock.patch('wcmatch.util.is_case_sensitive')
@@ -422,22 +422,22 @@ class TestFnMatchTranslate(unittest.TestCase):
         _wcparse._compile.cache_clear()
 
         p1, p2 = fnmatch.translate(
-            r'test\x70\u0070\U00000070\160\N{LATIN SMALL LETTER P}', flags=flags | fnmatch.R
+            R'test\x70\u0070\U00000070\160\N{LATIN SMALL LETTER P}', flags=flags | fnmatch.R
         )
         self.assertEqual(p1, [r'^(?s:testppppp)$'])
         self.assertEqual(p2, [])
 
         p1, p2 = fnmatch.translate(
-            r'test[\x70][\u0070][\U00000070][\160][\N{LATIN SMALL LETTER P}]', flags=flags | fnmatch.R
+            R'test[\x70][\u0070][\U00000070][\160][\N{LATIN SMALL LETTER P}]', flags=flags | fnmatch.R
         )
         self.assertEqual(p1, [r'^(?s:test[p][p][p][p][p])$'])
         self.assertEqual(p2, [])
 
-        p1, p2 = fnmatch.translate(r'test\t\m', flags=flags | fnmatch.R)
+        p1, p2 = fnmatch.translate(R'test\t\m', flags=flags | fnmatch.R)
         self.assertEqual(p1, [r'^(?s:test\	m)$'])
         self.assertEqual(p2, [])
 
-        p1, p2 = fnmatch.translate(r'test[\\]test', flags=flags | fnmatch.R)
+        p1, p2 = fnmatch.translate(R'test[\\]test', flags=flags | fnmatch.R)
         self.assertEqual(p1, [r'^(?s:test[\\]test)$'])
         self.assertEqual(p2, [])
 
@@ -445,26 +445,26 @@ class TestFnMatchTranslate(unittest.TestCase):
         self.assertEqual(p1, [r'^(?s:test\[)$'])
         self.assertEqual(p2, [])
 
-        p1, p2 = fnmatch.translate(r'test\44test', flags=flags | fnmatch.R)
+        p1, p2 = fnmatch.translate(R'test\44test', flags=flags | fnmatch.R)
         self.assertEqual(p1, [r'^(?s:test\$test)$'])
         self.assertEqual(p2, [])
 
-        p1, p2 = fnmatch.translate(r'test\44', flags=flags | fnmatch.R)
+        p1, p2 = fnmatch.translate(R'test\44', flags=flags | fnmatch.R)
         self.assertEqual(p1, [r'^(?s:test\$)$'])
         self.assertEqual(p2, [])
 
-        p1, p2 = fnmatch.translate(r'test\400', flags=flags | fnmatch.R)
+        p1, p2 = fnmatch.translate(R'test\400', flags=flags | fnmatch.R)
         self.assertEqual(p1, [r'^(?s:testĀ)$'])
         self.assertEqual(p2, [])
 
         with pytest.raises(SyntaxError):
-            fnmatch.translate(r'test\N', flags=flags | fnmatch.R)
+            fnmatch.translate(R'test\N', flags=flags | fnmatch.R)
 
         with pytest.raises(SyntaxError):
-            fnmatch.translate(r'test\Nx', flags=flags | fnmatch.R)
+            fnmatch.translate(R'test\Nx', flags=flags | fnmatch.R)
 
         with pytest.raises(SyntaxError):
-            fnmatch.translate(r'test\N{', flags=flags | fnmatch.R)
+            fnmatch.translate(R'test\N{', flags=flags | fnmatch.R)
 
     def test_default_compile(self):
         """Test default with exclusion."""
@@ -622,8 +622,8 @@ class TestIsMagic(unittest.TestCase):
         self.assertTrue(fnmatch.is_magic("test[", flags=flags))
         self.assertTrue(fnmatch.is_magic("test]", flags=flags))
         self.assertTrue(fnmatch.is_magic("test?", flags=flags))
-        self.assertTrue(fnmatch.is_magic(r"te\\st", flags=flags))
-        self.assertTrue(fnmatch.is_magic(r"te\st", flags=flags))
+        self.assertTrue(fnmatch.is_magic(R"te\\st", flags=flags))
+        self.assertTrue(fnmatch.is_magic(R"te\st", flags=flags))
         self.assertTrue(fnmatch.is_magic("test!", flags=flags))
         self.assertTrue(fnmatch.is_magic("test|", flags=flags))
         self.assertTrue(fnmatch.is_magic("test(", flags=flags))
@@ -690,11 +690,11 @@ class TestFnMatchEscapes(unittest.TestCase):
 
         check = self.check_escape
         check('abc', 'abc')
-        check('[', r'\[')
-        check('?', r'\?')
-        check('*', r'\*')
-        check('[[_/*?*/_]]', r'\[\[_/\*\?\*/_\]\]')
-        check('/[[_/*?*/_]]/', r'/\[\[_/\*\?\*/_\]\]/')
+        check('[', R'\[')
+        check('?', R'\?')
+        check('*', R'\*')
+        check('[[_/*?*/_]]', R'\[\[_/\*\?\*/_\]\]')
+        check('/[[_/*?*/_]]/', R'/\[\[_/\*\?\*/_\]\]/')
 
     @unittest.skipUnless(sys.platform.startswith('win'), "Windows specific test")
     def test_escape_windows(self):
@@ -702,39 +702,39 @@ class TestFnMatchEscapes(unittest.TestCase):
 
         check = self.check_escape
         # `fnmatch` doesn't care about drives
-        check('a:\\?', r'a:\\\?')
-        check('b:\\*', r'b:\\\*')
-        check('\\\\?\\c:\\?', r'\\\\\?\\c:\\\?')
-        check('\\\\*\\*\\*', r'\\\\\*\\\*\\\*')
-        check('//?/c:/?', r'//\?/c:/\?')
-        check('//*/*/*', r'//\*/\*/\*')
-        check('//[^what]/name/temp', r'//\[^what\]/name/temp')
+        check('a:\\?', R'a:\\\?')
+        check('b:\\*', R'b:\\\*')
+        check('\\\\?\\c:\\?', R'\\\\\?\\c:\\\?')
+        check('\\\\*\\*\\*', R'\\\\\*\\\*\\\*')
+        check('//?/c:/?', R'//\?/c:/\?')
+        check('//*/*/*', R'//\*/\*/\*')
+        check('//[^what]/name/temp', R'//\[^what\]/name/temp')
 
     def test_escape_forced_windows(self):
         """Test forced windows escapes."""
 
         check = self.check_escape
         # `fnmatch` doesn't care about drives
-        check('a:\\?', r'a:\\\?', unix=False)
-        check('b:\\*', r'b:\\\*', unix=False)
-        check('\\\\?\\c:\\?', r'\\\\\?\\c:\\\?', unix=True)
-        check('\\\\*\\*\\*', r'\\\\\*\\\*\\\*', unix=True)
-        check('//?/c:/?', r'//\?/c:/\?', unix=True)
-        check('//*/*/*', r'//\*/\*/\*', unix=True)
-        check('//[^what]/name/temp', r'//\[^what\]/name/temp', unix=True)
+        check('a:\\?', R'a:\\\?', unix=False)
+        check('b:\\*', R'b:\\\*', unix=False)
+        check('\\\\?\\c:\\?', R'\\\\\?\\c:\\\?', unix=True)
+        check('\\\\*\\*\\*', R'\\\\\*\\\*\\\*', unix=True)
+        check('//?/c:/?', R'//\?/c:/\?', unix=True)
+        check('//*/*/*', R'//\*/\*/\*', unix=True)
+        check('//[^what]/name/temp', R'//\[^what\]/name/temp', unix=True)
 
     def test_escape_forced_unix(self):
         """Test forced windows Unix."""
 
         check = self.check_escape
         # `fnmatch` doesn't care about drives
-        check('a:\\?', r'a:\\\?', unix=True)
-        check('b:\\*', r'b:\\\*', unix=True)
-        check('\\\\?\\c:\\?', r'\\\\\?\\c:\\\?', unix=True)
-        check('\\\\*\\*\\*', r'\\\\\*\\\*\\\*', unix=True)
-        check('//?/c:/?', r'//\?/c:/\?', unix=True)
-        check('//*/*/*', r'//\*/\*/\*', unix=True)
-        check('//[^what]/name/temp', r'//\[^what\]/name/temp', unix=True)
+        check('a:\\?', R'a:\\\?', unix=True)
+        check('b:\\*', R'b:\\\*', unix=True)
+        check('\\\\?\\c:\\?', R'\\\\\?\\c:\\\?', unix=True)
+        check('\\\\*\\*\\*', R'\\\\\*\\\*\\\*', unix=True)
+        check('//?/c:/?', R'//\?/c:/\?', unix=True)
+        check('//*/*/*', R'//\*/\*/\*', unix=True)
+        check('//[^what]/name/temp', R'//\[^what\]/name/temp', unix=True)
 
 
 class TestExpansionLimit(unittest.TestCase):
diff --git a/tests/test_glob.py b/tests/test_glob.py
index 5f1be9e..0ca62ef 100644
--- a/tests/test_glob.py
+++ b/tests/test_glob.py
@@ -623,8 +623,8 @@ class Testglob(_TestGlob):
         Options(absolute=True, skip=sys.platform != "win32"),
         [('*:',), []],
         [('?:',), []],
-        [(r'\\\\?\\c:\\',), [('\\\\?\\c:\\',)]],
-        [(r'\\\\*\\*\\',), []],
+        [(R'\\\\?\\c:\\',), [('\\\\?\\c:\\',)]],
+        [(R'\\\\*\\*\\',), []],
         Options(absolute=False, skip=False),
 
         Options(skip=not can_symlink()),
@@ -659,7 +659,7 @@ class Testglob(_TestGlob):
             ]
         ],
         [
-            (r'*\\',),
+            (R'*\\',),
             [
                 ('aab', ''), ('aaa', ''), ('a', '')
             ] if not can_symlink() else [
@@ -675,7 +675,7 @@ class Testglob(_TestGlob):
         [('[a]',), [('a',)]],
         [('[!b]',), [('a',)]],
         [('[^b]',), [('a',)]],
-        [(r'@([\a]|\aaa)',), [('a',), ('aaa',)]],
+        [(R'@([\a]|\aaa)',), [('a',), ('aaa',)]],
 
         Options(absolute=True),
         # Test empty.
@@ -1014,6 +1014,91 @@ class Testglob(_TestGlob):
             self.assert_equal(sorted(glob.glob(['dummy', '**/aab'], flags=glob.G)), ['aab',])
 
 
+class TestGlobStarLong(_TestGlob):
+    """Test `***` cases."""
+
+    DEFAULT_FLAGS = glob.BRACE | glob.EXTGLOB | glob.GLOBSTARLONG | glob.MARK | glob.NOUNIQUE
+
+    cases = [
+        # Test `globstar` with `globstarlong` enabled
+        [
+            ('**', '*'),
+            [
+                ('aab',), ('aab', 'F'), ('a',), ('a', 'bcd'), ('a', 'bcd', 'EF'), ('a', 'bcd', 'efg'),
+                ('a', 'bcd', 'efg', 'ha'), ('a', 'D'), ('aaa',), ('aaa', 'zzzF'), ('EF',), ('ZZZ',)
+            ] if not can_symlink() else [
+                ('aab',), ('aab', 'F'), ('a',), ('a', 'bcd'), ('a', 'bcd', 'EF'), ('a', 'bcd', 'efg'),
+                ('a', 'bcd', 'efg', 'ha'), ('a', 'D'), ('aaa',), ('aaa', 'zzzF'), ('EF',), ('ZZZ',),
+                ('sym1',), ('sym2',), ('sym3',)
+            ]
+        ],
+
+        # Test `globstarlong`
+        [
+            ('***', '*'),
+            [
+                ('aab',), ('aab', 'F'), ('a',), ('a', 'bcd'), ('a', 'bcd', 'EF'), ('a', 'bcd', 'efg'),
+                ('a', 'bcd', 'efg', 'ha'), ('a', 'D'), ('aaa',), ('aaa', 'zzzF'), ('EF',), ('ZZZ',)
+            ] if not can_symlink() else [
+                ('aab',), ('aab', 'F'), ('a',), ('a', 'bcd'), ('a', 'bcd', 'EF'), ('a', 'bcd', 'efg'),
+                ('a', 'bcd', 'efg', 'ha'), ('a', 'D'), ('aaa',), ('aaa', 'zzzF'), ('EF',), ('ZZZ',),
+                ('sym1',), ('sym2',), ('sym3',), ('sym3', 'EF'), ('sym3', 'efg'), ('sym3', 'efg', 'ha')
+            ]
+        ],
+
+        # Enable `FOLLOW`. Should be no changes.
+        [
+            ('**', '*'),
+            [
+                ('aab',), ('aab', 'F'), ('a',), ('a', 'bcd'), ('a', 'bcd', 'EF'), ('a', 'bcd', 'efg'),
+                ('a', 'bcd', 'efg', 'ha'), ('a', 'D'), ('aaa',), ('aaa', 'zzzF'), ('EF',), ('ZZZ',)
+            ] if not can_symlink() else [
+                ('aab',), ('aab', 'F'), ('a',), ('a', 'bcd'), ('a', 'bcd', 'EF'), ('a', 'bcd', 'efg'),
+                ('a', 'bcd', 'efg', 'ha'), ('a', 'D'), ('aaa',), ('aaa', 'zzzF'), ('EF',), ('ZZZ',),
+                ('sym1',), ('sym2',), ('sym3',)
+            ],
+            glob.L
+        ],
+        [
+            ('***', '*'),
+            [
+                ('aab',), ('aab', 'F'), ('a',), ('a', 'bcd'), ('a', 'bcd', 'EF'), ('a', 'bcd', 'efg'),
+                ('a', 'bcd', 'efg', 'ha'), ('a', 'D'), ('aaa',), ('aaa', 'zzzF'), ('EF',), ('ZZZ',)
+            ] if not can_symlink() else [
+                ('aab',), ('aab', 'F'), ('a',), ('a', 'bcd'), ('a', 'bcd', 'EF'), ('a', 'bcd', 'efg'),
+                ('a', 'bcd', 'efg', 'ha'), ('a', 'D'), ('aaa',), ('aaa', 'zzzF'), ('EF',), ('ZZZ',),
+                ('sym1',), ('sym2',), ('sym3',), ('sym3', 'EF'), ('sym3', 'efg'), ('sym3', 'efg', 'ha')
+            ],
+            glob.L
+        ]
+    ]
+
+    @classmethod
+    def setup_fs(cls):
+        """Setup file system."""
+
+        cls.mktemp('a', 'D')
+        cls.mktemp('aab', 'F')
+        cls.mktemp('.aa', 'G')
+        cls.mktemp('.bb', 'H')
+        cls.mktemp('aaa', 'zzzF')
+        cls.mktemp('ZZZ')
+        cls.mktemp('EF')
+        cls.mktemp('a', 'bcd', 'EF')
+        cls.mktemp('a', 'bcd', 'efg', 'ha')
+        cls.can_symlink = can_symlink()
+        if cls.can_symlink:
+            os.symlink(cls.norm('broken'), cls.norm('sym1'))
+            os.symlink('broken', cls.norm('sym2'))
+            os.symlink(os.path.join('a', 'bcd'), cls.norm('sym3'))
+
+    @pytest.mark.parametrize("case", cases)
+    def test_glob_cases(self, case):
+        """Test glob cases."""
+
+        self.eval_glob_cases(case)
+
+
 class TestGlobMarked(Testglob):
     """Test glob marked."""
 
@@ -1335,14 +1420,14 @@ class TestGlobCornerCase(_TestGlob):
         [('@(a/b)',), []],
         [('@(a[/]b)',), []],
         [('test[',), [('test[',)]],
-        [(r'a\/b',), [('a', 'b')]],
-        [(r'a[\/]b',), [('a[', ']b')]],
+        [(R'a\/b',), [('a', 'b')]],
+        [(R'a[\/]b',), [('a[', ']b')]],
 
         Options(skip=util.is_case_sensitive()),
         [('a[\\',), [('a[',)]],
         [('@(a[\\',), [('@(a[',)]],
-        [(r'a[\\',), [('a[', '')]],
-        [(r'@(a[\\',), [('@(a[', '')]],
+        [(R'a[\\',), [('a[', '')]],
+        [(R'@(a[\\',), [('@(a[', '')]],
         Options(skip=False)
     ]
 
@@ -1397,53 +1482,53 @@ class TestGlobEscapes(unittest.TestCase):
 
         check = self.check_escape
         check('abc', 'abc')
-        check('[', r'\[')
-        check('?', r'\?')
-        check('*', r'\*')
-        check('[[_/*?*/_]]', r'\[\[_/\*\?\*/_\]\]')
-        check('/[[_/*?*/_]]/', r'/\[\[_/\*\?\*/_\]\]/')
+        check('[', R'\[')
+        check('?', R'\?')
+        check('*', R'\*')
+        check('[[_/*?*/_]]', R'\[\[_/\*\?\*/_\]\]')
+        check('/[[_/*?*/_]]/', R'/\[\[_/\*\?\*/_\]\]/')
 
     @unittest.skipUnless(sys.platform.startswith('win'), "Windows specific test")
     def test_escape_windows(self):
         """Test windows escapes."""
 
         check = self.check_escape
-        check('a:\\?', r'a:\\\?')
-        check('b:\\*', r'b:\\\*')
-        check('\\\\?\\c:\\?', r'\\\\?\\c:\\\?')
-        check('\\\\*\\*\\*', r'\\\\*\\*\\\*')
-        check('//?/c:/?', r'//?/c:/\?')
-        check('//*/*/*', r'//*/*/\*')
-        check('//[^what]/name/temp', r'//[^what]/name/temp')
+        check('a:\\?', R'a:\\\?')
+        check('b:\\*', R'b:\\\*')
+        check('\\\\?\\c:\\?', R'\\\\?\\c:\\\?')
+        check('\\\\*\\*\\*', R'\\\\*\\*\\\*')
+        check('//?/c:/?', R'//?/c:/\?')
+        check('//*/*/*', R'//*/*/\*')
+        check('//[^what]/name/temp', R'//[^what]/name/temp')
 
     def test_escape_forced_windows(self):
         """Test forced windows escapes."""
 
         check = self.check_escape
-        check('a:\\?', r'a:\\\?', unix=False)
-        check('b:\\*', r'b:\\\*', unix=False)
-        check('\\\\?\\c:\\?', r'\\\\?\\c:\\\?', unix=False)
-        check('\\\\*\\*\\*', r'\\\\*\\*\\\*', unix=False)
-        check('//?/c:/?', r'//?/c:/\?', unix=False)
-        check('//*/*/*', r'//*/*/\*', unix=False)
+        check('a:\\?', R'a:\\\?', unix=False)
+        check('b:\\*', R'b:\\\*', unix=False)
+        check('\\\\?\\c:\\?', R'\\\\?\\c:\\\?', unix=False)
+        check('\\\\*\\*\\*', R'\\\\*\\*\\\*', unix=False)
+        check('//?/c:/?', R'//?/c:/\?', unix=False)
+        check('//*/*/*', R'//*/*/\*', unix=False)
         check(
             '//./Volume{b75e2c83-0000-0000-0000-602f00000000}/temp',
-            r'//./Volume\{b75e2c83-0000-0000-0000-602f00000000\}/temp',
+            R'//./Volume\{b75e2c83-0000-0000-0000-602f00000000\}/temp',
             unix=False
         )
-        check('//[^what]/name/temp', r'//[^what]/name/temp', unix=False)
+        check('//[^what]/name/temp', R'//[^what]/name/temp', unix=False)
 
     def test_escape_forced_unix(self):
         """Test forced windows Unix."""
 
         check = self.check_escape
-        check('a:\\?', r'a:\\\?', unix=True)
-        check('b:\\*', r'b:\\\*', unix=True)
-        check('\\\\?\\c:\\?', r'\\\\\?\\c:\\\?', unix=True)
-        check('\\\\*\\*\\*', r'\\\\\*\\\*\\\*', unix=True)
-        check('//?/c:/?', r'//\?/c:/\?', unix=True)
-        check('//*/*/*', r'//\*/\*/\*', unix=True)
-        check('//[^what]/name/temp', r'//\[^what\]/name/temp', unix=True)
+        check('a:\\?', R'a:\\\?', unix=True)
+        check('b:\\*', R'b:\\\*', unix=True)
+        check('\\\\?\\c:\\?', R'\\\\\?\\c:\\\?', unix=True)
+        check('\\\\*\\*\\*', R'\\\\\*\\\*\\\*', unix=True)
+        check('//?/c:/?', R'//\?/c:/\?', unix=True)
+        check('//*/*/*', R'//\*/\*/\*', unix=True)
+        check('//[^what]/name/temp', R'//\[^what\]/name/temp', unix=True)
 
 
 @unittest.skipUnless(sys.platform.startswith('win'), "Windows specific test")
@@ -1573,7 +1658,7 @@ class TestGlobPaths(unittest.TestCase):
         self.assertTrue(len(results) > 0)
         self.assertTrue('\\' not in results)
 
-        results = glob.glob(r'\\*')
+        results = glob.glob(R'\\*')
         self.assertTrue(len(results) > 0)
         self.assertTrue('\\' not in results)
 
diff --git a/tests/test_globmatch.py b/tests/test_globmatch.py
index 85b1182..e16c906 100644
--- a/tests/test_globmatch.py
+++ b/tests/test_globmatch.py
@@ -199,8 +199,8 @@ class TestGlobFilter:
             ]
         ],
 
-        [r'\\.\\./*/', []],
-        [r's/\\..*//', []],
+        [R'\\.\\./*/', []],
+        [R's/\\..*//', []],
 
         # legendary Larry crashes bashes
         ['/^root:/{s/^[^:]*:[^:]*:\\([^:]*\\).*$/\\1/', []],
@@ -307,8 +307,8 @@ class TestGlobFilter:
         ['a/.*/b', ['a/./b', 'a/../b', 'a/.d/b'], 0],
         ['a/.*/b', ['a/.d/b'], glob.Z],
         # Escaped `.` will still be treated as a `.`
-        [r'a/\.*/b', ['a/./b', 'a/../b', 'a/.d/b'], 0],
-        [r'a/\.*/b', ['a/.d/b'], glob.Z],
+        [R'a/\.*/b', ['a/./b', 'a/../b', 'a/.d/b'], 0],
+        [R'a/\.*/b', ['a/.d/b'], glob.Z],
 
         # this also tests that changing the options needs
         # to change the cache key, even if the pattern is
@@ -408,7 +408,7 @@ class TestGlobFilter:
         # it will be treated as a literal, not an exclude pattern.
         Options(skip_split=True),
         [
-            r'!\(a|c)',
+            R'!\(a|c)',
             [
                 '(a|b|c)', '(b|c', '*(b|c', 'a', 'ab', 'ac', 'ad', 'b', 'bc', 'bc,d', 'b|c', 'b|cc',
                 'c', 'c,d', 'c,db', 'cb', 'cb|c', 'd', 'd)', 'x(a|b|c)', 'x(a|c)'
@@ -509,7 +509,7 @@ class TestGlobFilter:
         ],
 
         [
-            r'**\.x/*',
+            R'**\.x/*',
             [
                 'test.x/a', '.x/a'
             ]
@@ -527,7 +527,7 @@ class TestGlobFilter:
             ]
         ),
         ['*//e///*', ['d/e/f', 'a/e/c']],
-        [r'*//\e///*', ['d/e/f', 'a/e/c']],
+        [R'*//\e///*', ['d/e/f', 'a/e/c']],
 
         # Backslash trailing cases
         GlobFiles(
@@ -538,9 +538,9 @@ class TestGlobFilter:
         ['**\\', ['a/b/c/', 'd/e/f/', 'a/e/c/']],
         ['**\\', ['a/b/c/', 'd/e/f/', 'a/e/c/'], glob.U],
         ['**\\', ['a/b/c/', 'd/e/f/', 'a/e/c/'], glob.W],
-        [r'**\\', [] if util.is_case_sensitive() else ['a/b/c/', 'd/e/f/', 'a/e/c/']],
-        [r'**\\', [], glob.U],
-        [r'**\\', ['a/b/c/', 'd/e/f/', 'a/e/c/'], glob.W],
+        [R'**\\', [] if util.is_case_sensitive() else ['a/b/c/', 'd/e/f/', 'a/e/c/']],
+        [R'**\\', [], glob.U],
+        [R'**\\', ['a/b/c/', 'd/e/f/', 'a/e/c/'], glob.W],
 
         # Invalid `extglob` groups
         GlobFiles(
@@ -564,33 +564,33 @@ class TestGlobFilter:
 
         # Basic dot tests
         ['[.]abc', []],
-        [r'[\.]abc', []],
+        [R'[\.]abc', []],
         ['.abc', ['.abc']],
-        [r'\.abc', ['.abc']],
+        [R'\.abc', ['.abc']],
         ['[.]abc', ['.abc'], glob.D],
-        [r'[\.]abc', ['.abc'], glob.D],
+        [R'[\.]abc', ['.abc'], glob.D],
 
         ['.', ['.']],
         ['..', ['..']],
         ['.*', ['.', '..', '.abc', '...', '..abc']],
-        [r'.\a*', ['.abc']],
-        [r'\.', ['.']],
-        [r'\..', ['..']],
-        [r'\.\.', ['..']],
+        [R'.\a*', ['.abc']],
+        [R'\.', ['.']],
+        [R'\..', ['..']],
+        [R'\.\.', ['..']],
         ['..*', ['..', '...', '..abc']],
-        [r'...', ['...']],
-        [r'..\.', ['...']],
+        [R'...', ['...']],
+        [R'..\.', ['...']],
 
         ['.', ['.'], glob.Z],
         ['..', ['..'], glob.Z],
         ['.*', ['.abc', '...', '..abc'], glob.Z],
-        [r'.\a*', ['.abc'], glob.Z],
-        [r'\.', ['.'], glob.Z],
-        [r'\..', ['..'], glob.Z],
-        [r'\.\.', ['..'], glob.Z],
+        [R'.\a*', ['.abc'], glob.Z],
+        [R'\.', ['.'], glob.Z],
+        [R'\..', ['..'], glob.Z],
+        [R'\.\.', ['..'], glob.Z],
         ['..*', ['...', '..abc'], glob.Z],
-        [r'...', ['...'], glob.Z],
-        [r'..\.', ['...'], glob.Z],
+        [R'...', ['...'], glob.Z],
+        [R'..\.', ['...'], glob.Z],
 
         # Dot tests trailing slashes
         GlobFiles(
@@ -604,7 +604,7 @@ class TestGlobFilter:
         ['./', ['./'], glob.Z],
         ['../', ['../'], glob.Z],
         ['..\\', ['../'], glob.Z],
-        [r'.\\', ['./'], glob.W | glob.Z],
+        [R'.\\', ['./'], glob.W | glob.Z],
 
         # Inverse dot tests
         GlobFiles(
@@ -620,8 +620,8 @@ class TestGlobFilter:
         ['.!(test)', ['.', '..', '.abc'], glob.M],
         ['.!(test)', ['.', '..', '.abc'], glob.D | glob.M],
         ['!(.)', ['abc'], glob.M],
-        [r'!(\.)', ['abc'], glob.M],
-        [r'!(\x2e)', ['abc'], glob.M | glob.R],
+        [R'!(\.)', ['abc'], glob.M],
+        [R'!(\x2e)', ['abc'], glob.M | glob.R],
         ['@(!(.))', ['abc'], glob.M],
         ['!(@(.))', ['abc'], glob.M],
         ['+(!(.))', ['abc'], glob.M],
@@ -630,8 +630,8 @@ class TestGlobFilter:
         ['!(*)', [], glob.M],
         ['!([.])', ['abc'], glob.M],
         ['!(.)', ['..', '.abc', 'abc'], glob.M | glob.D],
-        [r'!(\.)', ['..', '.abc', 'abc'], glob.M | glob.D],
-        [r'!(\x2e)', ['..', '.abc', 'abc'], glob.M | glob.R | glob.D],
+        [R'!(\.)', ['..', '.abc', 'abc'], glob.M | glob.D],
+        [R'!(\x2e)', ['..', '.abc', 'abc'], glob.M | glob.R | glob.D],
         ['@(!(.))', ['..', '.abc', 'abc'], glob.M | glob.D],
         ['!(@(.))', ['..', '.abc', 'abc'], glob.M | glob.D],
         ['+(!(.))', ['..', '.abc', 'abc'], glob.M | glob.D],
@@ -645,13 +645,13 @@ class TestGlobFilter:
 
         # More extended pattern dot related tests
         ['*(.)', ['.', '..']],
-        [r'*(\.)', ['.', '..']],
+        [R'*(\.)', ['.', '..']],
         ['*([.])', []],
         ['*(?)', ['abc']],
         ['@(.?)', ['..']],
         ['@(?.)', []],
         ['*(.)', ['.', '..'], glob.D],
-        [r'*(\.)', ['.', '..'], glob.D],
+        [R'*(\.)', ['.', '..'], glob.D],
         ['*([.])', [], glob.D],
         ['*(?)', ['.abc', 'abc'], glob.D],
         ['@(.?)', ['..'], glob.D],
@@ -669,19 +669,19 @@ class TestGlobFilter:
         # Force Unix/Linux
         ['test/test', ['test/test'], glob.U],
         ['test\\/test', ['test/test'], glob.U],
-        [r'test\\/test', ['test\\/test'], glob.U],
+        [R'test\\/test', ['test\\/test'], glob.U],
         ['@(test/test)', [], glob.U],
-        [r'@(test\/test)', [], glob.U],
+        [R'@(test\/test)', [], glob.U],
         ['test[/]test', [], glob.U],
-        [r'test[\/]test', [], glob.U],
+        [R'test[\/]test', [], glob.U],
 
         # Force Windows
         ['test/test', ['test/test', 'test\\/test'], glob.W],
         ['test\\/test', ['test/test', 'test\\/test'], glob.W],
         ['@(test/test)', [], glob.W],
-        [r'@(test\/test)', [], glob.W],
+        [R'@(test\/test)', [], glob.W],
         ['test[/]test', [], glob.W],
-        [r'test[\/]test', [], glob.W],
+        [R'test[\/]test', [], glob.W],
 
         # Case
         ['TEST/test', ['test/test', 'test\\/test'], glob.W],
@@ -692,7 +692,7 @@ class TestGlobFilter:
         ['test\\/test', ['test/test', 'test\\/test'], glob.W | glob.C],
         ['TEST/test', ['test/test'], glob.U | glob.I],
         ['test\\/TEST', ['test/test'], glob.U | glob.I],
-        [r'test\\/TEST', ['test\\/test'], glob.U | glob.I],
+        [R'test\\/TEST', ['test\\/test'], glob.U | glob.I],
         ['TEST/test', [], glob.U],
         ['test\\/TEST', [], glob.U],
 
@@ -1057,42 +1057,42 @@ class TestGlobMatchSpecial(unittest.TestCase):
         self.assertTrue(
             glob.globmatch(
                 'some/name/with/na[\\]med/file/test.py',
-                r'**/na[\\]med/file/*.py',
+                R'**/na[\\]med/file/*.py',
                 flags=flags | glob.R
             )
         )
         self.assertTrue(
             glob.globmatch(
                 'some\\name\\with\\na[\\]med\\file\\test.py',
-                r'**/na[\\]med/file/*.py',
+                R'**/na[\\]med/file/*.py',
                 flags=flags | glob.R
             )
         )
         self.assertTrue(
             glob.globmatch(
                 'some\\name\\with\\na[\\]med\\file*.py',
-                r'**\\na[\\]med\\file\*.py',
+                R'**\\na[\\]med\\file\*.py',
                 flags=flags | glob.R
             )
         )
         self.assertTrue(
             glob.globmatch(
                 'some\\name\\with\\na[\\]med\\file\\test.py',
-                r'**\\na[\\]m\ed\\file\\*.py',
+                R'**\\na[\\]m\ed\\file\\*.py',
                 flags=flags | glob.R
             )
         )
         self.assertTrue(
             glob.globmatch(
                 'some\\name\\with\\na[\\]med\\\\file\\test.py',
-                r'**\\na[\\]m\ed\\/file\\*.py',
+                R'**\\na[\\]m\ed\\/file\\*.py',
                 flags=flags | glob.R
             )
         )
         self.assertTrue(
             glob.globmatch(
                 'some\\name\\with\\na[\\\\]med\\\\file\\test.py',
-                r'**\\na[\/]m\ed\/file\\*.py',
+                R'**\\na[\/]m\ed\/file\\*.py',
                 flags=flags | glob.R
             )
         )
@@ -1190,14 +1190,14 @@ class TestGlobMatchSpecial(unittest.TestCase):
         self.assertTrue(
             glob.globmatch(
                 'some/name/with/na\\med/file/test.py',
-                r'**/na[\\]med/file/*.py',
+                R'**/na[\\]med/file/*.py',
                 flags=flags | glob.R
             )
         )
         self.assertTrue(
             glob.globmatch(
                 'some/name/with/na[\\/]med\\/file/test.py',
-                r'**/na[\\/]med\\/file/*.py',
+                R'**/na[\\/]med\\/file/*.py',
                 flags=flags | glob.R
             )
         )
@@ -1620,6 +1620,32 @@ class TestGlobmatchSymlink(_TestGlobmatch):
 
         self.assertFalse(glob.globmatch(self.tempdir + '/sym1/a.txt', '**/{*.txt,*.t*}', flags=self.default_flags))
 
+    def test_globmatch_globstarlong(self):
+        """Test `***`."""
+
+        flags = glob.GL | glob.P
+        self.assertTrue(glob.globmatch(self.tempdir + '/sym1/a.txt', '***/*.txt', flags=flags))
+        self.assertFalse(glob.globmatch(self.tempdir + '/sym1/a.txt', '**/*.txt', flags=flags))
+
+    def test_globmatch_globstarlong_follow(self):
+        """Test `***` with `FOLLOW`."""
+
+        flags = glob.GL | glob.P | glob.L
+        self.assertTrue(glob.globmatch(self.tempdir + '/sym1/a.txt', '***/*.txt', flags=flags))
+        self.assertFalse(glob.globmatch(self.tempdir + '/sym1/a.txt', '**/*.txt', flags=flags))
+
+    def test_globmatch_globstarlong_matchbase(self):
+        """Test `***` with `MATCHBASE`."""
+
+        flags = glob.GL | glob.P | glob.X
+        self.assertFalse(glob.globmatch(self.tempdir + '/sym1/a.txt', '*.txt', flags=flags))
+
+    def test_globmatch_globstarlong_matchbase_follow(self):
+        """Test `***` with `MATCHBASE`."""
+
+        flags = glob.GL | glob.P | glob.X | glob.L
+        self.assertTrue(glob.globmatch(self.tempdir + '/sym1/a.txt', '*.txt', flags=flags))
+
 
 @unittest.skipUnless(os.path.expanduser('~') != '~', "Requires expand user functionality")
 class TestTilde(unittest.TestCase):
@@ -1821,7 +1847,7 @@ class TestIsMagic(unittest.TestCase):
 
         self.assertTrue(glob.is_magic('//?/UNC/server/*[]!|(){}|~-/', flags=flags | glob.BRACE))
         self.assertTrue(glob.is_magic('//?/UNC/server/*[]!(){}|~-/', flags=flags | glob.SPLIT))
-        self.assertTrue(glob.is_magic(r'\\\\server\\mount\\', flags=flags))
+        self.assertTrue(glob.is_magic(R'\\\\server\\mount\\', flags=flags))
 
 
 class TestExpansionLimit(unittest.TestCase):
diff --git a/tests/test_pathlib.py b/tests/test_pathlib.py
index 089bdf8..5e94ab0 100644
--- a/tests/test_pathlib.py
+++ b/tests/test_pathlib.py
@@ -7,6 +7,52 @@ from wcmatch import pathlib, glob, _wcparse
 import pathlib as pypathlib
 import pickle
 import warnings
+import shutil
+
+# Below is general helper stuff that Python uses in `unittests`.  As these
+# not meant for users, and could change without notice, include them
+# ourselves so we aren't surprised later.
+TESTFN = '@test'
+
+# Disambiguate `TESTFN` for parallel testing, while letting it remain a valid
+# module name.
+TESTFN = "{}_{}_tmp".format(TESTFN, os.getpid())
+
+
+def create_empty_file(filename):
+    """Create an empty file. If the file already exists, truncate it."""
+
+    fd = os.open(filename, os.O_WRONLY | os.O_CREAT | os.O_TRUNC)
+    os.close(fd)
+
+
+_can_symlink = None
+
+
+def can_symlink():
+    """Check if we can symlink."""
+
+    global _can_symlink
+    if _can_symlink is not None:
+        return _can_symlink
+    symlink_path = TESTFN + "can_symlink"
+    try:
+        os.symlink(TESTFN, symlink_path)
+        can = True
+    except (OSError, NotImplementedError, AttributeError):
+        can = False
+    else:
+        os.remove(symlink_path)
+    _can_symlink = can
+    return can
+
+
+def skip_unless_symlink(test):
+    """Skip decorator for tests that require functional symlink."""
+
+    ok = can_symlink()
+    msg = "Requires functional symlink implementation"
+    return test if ok else unittest.skip(msg)(test)
 
 
 @contextlib.contextmanager
@@ -144,7 +190,7 @@ class TestPathlibGlobmatch:
         ['.', '', True, 0, "pure"],
 
         # Force a specific platform with a specific `PurePath`.
-        ['//?/C:/**/file.log', r'\\?\C:\Path\path\file.log', True, pathlib.G, "windows"],
+        ['//?/C:/**/file.log', R'\\?\C:\Path\path\file.log', True, pathlib.G, "windows"],
         ['/usr/*/bin', '/usr/local/bin', True, pathlib.G, "unix"]
     ]
 
@@ -200,6 +246,11 @@ class TestPathlibGlobmatch:
 
         self.evaluate(case)
 
+    def test_full_match_alias(self):
+        """Test `full_match` alias."""
+
+        pathlib.PurePath('this/path/here').full_match('**/path/here', flags=pathlib.G)
+
 
 class TestPathlibMatch(TestPathlibGlobmatch):
     """
@@ -383,3 +434,122 @@ class TestExcludes(unittest.TestCase):
 
         self.assertTrue(pathlib.PurePath('path/name').globmatch('*/*', exclude='*/test'))
         self.assertFalse(pathlib.PurePath('path/test').globmatch('*/*', exclude='*/test'))
+
+
+@skip_unless_symlink
+class TestPathlibSymlink(unittest.TestCase):
+    """Test the `pathlib` symlink class."""
+
+    def mktemp(self, *parts):
+        """Make temp directory."""
+
+        filename = self.norm(*parts)
+        base, file = os.path.split(filename)
+        if not os.path.exists(base):
+            retry = 3
+            while retry:
+                try:
+                    os.makedirs(base)
+                    retry = 0
+                except Exception:  # noqa: PERF203
+                    retry -= 1
+        create_empty_file(filename)
+
+    def norm(self, *parts):
+        """Normalizes file path (in relation to temp directory)."""
+        tempdir = os.fsencode(self.tempdir) if isinstance(parts[0], bytes) else self.tempdir
+        return os.path.join(tempdir, *parts)
+
+    def mksymlink(self, original, link):
+        """Make symlink."""
+
+        if not os.path.lexists(link):
+            os.symlink(original, link)
+
+    def setUp(self):
+        """Setup."""
+
+        self.tempdir = TESTFN + "_dir"
+        self.mktemp('subfolder', 'a.txt')
+        self.mktemp('subfolder', 'b.file')
+        self.mktemp('a.txt')
+        self.mktemp('b.file')
+        self.mktemp('c.txt.bak')
+        self.can_symlink = can_symlink()
+        if self.can_symlink:
+            self.mksymlink('subfolder', self.norm('sym1'))
+            self.mksymlink(os.path.join('subfolder', 'a.txt'), self.norm('sym2'))
+
+        self.default_flags = glob.G | glob.P | glob.B
+
+    def tearDown(self):
+        """Cleanup."""
+
+        retry = 3
+        while retry:
+            try:
+                shutil.rmtree(self.tempdir)
+                while os.path.exists(self.tempdir):
+                    pass
+                retry = 0
+            except Exception:  # noqa: PERF203
+                retry -= 1
+
+    @staticmethod
+    def assert_equal(a, b):
+        """Assert equal."""
+
+        assert a == b, "Comparison between objects yielded false."
+
+    @classmethod
+    def assertSequencesEqual_noorder(cls, l1, l2):
+        """Verify lists match (unordered)."""
+
+        l1 = list(l1)
+        l2 = list(l2)
+        cls.assert_equal(set(l1), set(l2))
+        cls.assert_equal(sorted(l1), sorted(l2))
+
+    def test_rglob_globstar(self):
+        """Test `rglob` with `GLOBSTARLONG`."""
+
+        flags = pathlib.GL
+        expected = [pathlib.Path(self.tempdir + '/a.txt'), pathlib.Path(self.tempdir + '/subfolder/a.txt')]
+        result = pathlib.Path(self.tempdir).rglob('a.txt', flags=flags)
+        self.assertSequencesEqual_noorder(expected, result)
+
+    def test_rglob_globstarlong_symlink(self):
+        """Test `rglob` with `GLOBSTARLONG` and `FOLLOW`."""
+
+        flags = pathlib.GL | pathlib.L
+        expected = [
+            pathlib.Path(self.tempdir + '/a.txt'),
+            pathlib.Path(self.tempdir + '/subfolder/a.txt'),
+            pathlib.Path(self.tempdir + '/sym1/a.txt')
+        ]
+        result = pathlib.Path(self.tempdir).rglob('a.txt', flags=flags)
+        self.assertSequencesEqual_noorder(expected, result)
+
+    def test_match_globstarlong_no_follow(self):
+        """Test `match` with `GLOBSTARLONG`."""
+
+        flags = pathlib.GL
+        self.assertTrue(pathlib.Path(self.tempdir + '/sym1/a.txt').match('a.txt', flags=flags))
+
+    def test_match_globstarlong_symlink(self):
+        """Test `match` with `GLOBSTARLONG` and `FOLLOW`."""
+
+        flags = pathlib.GL | pathlib.L
+        self.assertTrue(pathlib.Path(self.tempdir + '/sym1/a.txt').match('a.txt', flags=flags))
+
+    def test_match_globstarlong_no_follow_real_path(self):
+        """Test `match` with `GLOBSTARLONG` and `REALPATH`."""
+
+        flags = pathlib.GL | pathlib.P
+        self.assertFalse(pathlib.Path(self.tempdir + '/sym1/a.txt').match('a.txt', flags=flags))
+
+    def test_match_globstarlong_symlink_real_path(self):
+        """Test `match` with `GLOBSTARLONG` and `FOLLOW` and `REALPATH`."""
+
+        flags = pathlib.GL | pathlib.L | pathlib.P
+        self.assertTrue(pathlib.Path(self.tempdir + '/sym1/a.txt').match('a.txt', flags=flags))