We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
add: Path does not overlap with base path
## Description

Using `dvc add` on Windows can produce the error 

```
ERROR: Path: C:\Users\my_user\Desktop\DVC-2.4-TEST does not overlap with base path: C:\Users\my_user\Desktop\dvc-2.4-test
```

I believe this is because paths in Windows are case insensitive (note the difference in case between `DVC-2.4-TEST` and `dvc-2.4-test`)


### Reproduce

```
mkdir my_test
cd MY_TEST
git init
dvc init
```

Then, move data into the project folder.

```
dvc add mnist --verbose
```

Which outputs

```console
2021-06-23 08:22:44,410 DEBUG: Check for update is enabled.
2021-06-23 08:22:44,491 DEBUG: Trying to spawn '['daemon', '-q', 'updater']'
2021-06-23 08:22:44,536 DEBUG: Spawned '['daemon', '-q', 'updater']'
Adding...
2021-06-23 08:22:44,558 ERROR: Path: C:\Users\my_user\Desktop\MY_TEST does not overlap with base path: C:\Users\my_user\Desktop\my_test
------------------------------------------------------------
Traceback (most recent call last):
  File "c:\users\my_user\anaconda3\envs\dvc-2.4\lib\site-packages\dvc\command\add.py", line 21, in run
    self.repo.add(
  File "c:\users\my_user\anaconda3\envs\dvc-2.4\lib\site-packages\dvc\repo\__init__.py", line 51, in wrapper
    return f(repo, *args, **kwargs)
  File "c:\users\my_user\anaconda3\envs\dvc-2.4\lib\site-packages\dvc\repo\scm_context.py", line 14, in run
    return method(repo, *args, **kw)
  File "c:\users\my_user\anaconda3\envs\dvc-2.4\lib\site-packages\dvc\repo\add.py", line 92, in add
    stages = _create_stages(
  File "c:\users\my_user\anaconda3\envs\dvc-2.4\lib\site-packages\dvc\repo\add.py", line 250, in _create_stages
    path, wdir, out = resolve_paths(
  File "c:\users\my_user\anaconda3\envs\dvc-2.4\lib\site-packages\dvc\utils\__init__.py", line 386, in resolve_paths
    elif contains_symlink_up_to(dirname, repo.root_dir) or (
  File "c:\users\my_user\anaconda3\envs\dvc-2.4\lib\site-packages\dvc\utils\fs.py", line 81, in contains_symlink_up_to
    raise BasePathNotInCheckedPathException(path, base_path)
dvc.utils.fs.BasePathNotInCheckedPathException: Path: C:\Users\my_user\Desktop\MY_TEST does not overlap with base path: C:\Users\my_user\Desktop\my_test
------------------------------------------------------------
2021-06-23 08:22:44,563 DEBUG: Analytics is enabled.
2021-06-23 08:22:44,565 DEBUG: Trying to spawn '['daemon', '-q', 'analytics', 'C:\\Users\\my_user\\AppData\\Local\\Temp\\tmpgejvwwr6']'
2021-06-23 08:22:44,610 
```


### Expected

```
dvc add mnist
```

Which would output

```
100% Add|█████████████████████████████████████████████████████████████████████████████████████|1/1 [00:00,  1.76file/s]

To track the changes with git, run:

        git add mnist.dvc .gitignore
```

### Environment information

**Output of `dvc doctor`:**

```console
DVC version: 2.4.0+b7c6df
---------------------------------
Platform: Python 3.8.10 on Windows-10-10.0.19041-SP0
Supports: http, https
Cache types: <https://error.dvc.org/no-dvc-cache>
Caches: local
Remotes: local
Workspace directory: NTFS on C:\
Repo: dvc, git
```

**Additional Information (if any):**

This error does not happen if you do `cd my_test` instead of `cd MY_TEST`, or in general, if you specify the path with exactly the same case as the true file path.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `dvc/utils/fs.py`
Content:
```
1 import errno
2 import logging
3 import os
4 import shutil
5 import stat
6 import sys
7 
8 from dvc.exceptions import DvcException
9 from dvc.system import System
10 from dvc.utils import dict_md5
11 
12 logger = logging.getLogger(__name__)
13 
14 LOCAL_CHUNK_SIZE = 2 ** 20  # 1 MB
15 
16 umask = os.umask(0)
17 os.umask(umask)
18 
19 
20 def fs_copy(src, dst, ignore=None):
21     if os.path.isdir(src):
22         shutil.copytree(src, dst, ignore=ignore)
23     else:
24         shutil.copy2(src, dst)
25 
26 
27 def get_inode(path):
28     inode = System.inode(path)
29     logger.trace("Path '%s' inode '%d'", path, inode)
30     return inode
31 
32 
33 def get_mtime_and_size(path, fs, dvcignore=None):
34     import nanotime
35 
36     if fs.isdir(path):
37         size = 0
38         files_mtimes = {}
39         if dvcignore:
40             walk_iterator = dvcignore.walk_files(fs, path)
41         else:
42             walk_iterator = fs.walk_files(path)
43         for file_path in walk_iterator:
44             try:
45                 stats = fs.stat(file_path)
46             except OSError as exc:
47                 # NOTE: broken symlink case.
48                 if exc.errno != errno.ENOENT:
49                     raise
50                 continue
51             size += stats.st_size
52             files_mtimes[os.fspath(file_path)] = stats.st_mtime
53 
54         # We track file changes and moves, which cannot be detected with simply
55         # max(mtime(f) for f in non_ignored_files)
56         mtime = dict_md5(files_mtimes)
57     else:
58         base_stat = fs.stat(path)
59         size = base_stat.st_size
60         mtime = base_stat.st_mtime
61         mtime = int(nanotime.timestamp(mtime))
62 
63     # State of files handled by dvc is stored in db as TEXT.
64     # We cast results to string for later comparisons with stored values.
65     return str(mtime), str(size)
66 
67 
68 class BasePathNotInCheckedPathException(DvcException):
69     def __init__(self, path, base_path):
70         msg = "Path: {} does not overlap with base path: {}".format(
71             path, base_path
72         )
73         super().__init__(msg)
74 
75 
76 def contains_symlink_up_to(path, base_path):
77     base_path = os.fspath(base_path)
78     path = os.fspath(path)
79 
80     if base_path not in path:
81         raise BasePathNotInCheckedPathException(path, base_path)
82 
83     if path == base_path:
84         return False
85     if System.is_symlink(path):
86         return True
87     if os.path.dirname(path) == path:
88         return False
89     return contains_symlink_up_to(os.path.dirname(path), base_path)
90 
91 
92 def move(src, dst):
93     """Atomically move src to dst and chmod it with mode.
94 
95     Moving is performed in two stages to make the whole operation atomic in
96     case src and dst are on different filesystems and actual physical copying
97     of data is happening.
98     """
99     from shortuuid import uuid
100 
101     dst = os.path.abspath(dst)
102     tmp = f"{dst}.{uuid()}"
103 
104     if os.path.islink(src):
105         shutil.copy(src, tmp)
106         _unlink(src, _chmod)
107     else:
108         shutil.move(src, tmp)
109 
110     shutil.move(tmp, dst)
111 
112 
113 def _chmod(func, p, excinfo):  # pylint: disable=unused-argument
114     perm = os.lstat(p).st_mode
115     perm |= stat.S_IWRITE
116 
117     try:
118         os.chmod(p, perm)
119     except OSError as exc:
120         # broken symlink or file is not owned by us
121         if exc.errno not in [errno.ENOENT, errno.EPERM]:
122             raise
123 
124     func(p)
125 
126 
127 def _unlink(path, onerror):
128     try:
129         os.unlink(path)
130     except OSError:
131         onerror(os.unlink, path, sys.exc_info())
132 
133 
134 def remove(path):
135     logger.debug("Removing '%s'", path)
136 
137     try:
138         if os.path.isdir(path):
139             shutil.rmtree(path, onerror=_chmod)
140         else:
141             _unlink(path, _chmod)
142     except OSError as exc:
143         if exc.errno != errno.ENOENT:
144             raise
145 
146 
147 def path_isin(child, parent):
148     """Check if given `child` path is inside `parent`."""
149 
150     def normalize_path(path):
151         return os.path.normpath(path)
152 
153     parent = os.path.join(normalize_path(parent), "")
154     child = normalize_path(child)
155     return child != parent and child.startswith(parent)
156 
157 
158 def makedirs(path, exist_ok=False, mode=None):
159     if mode is None:
160         os.makedirs(path, exist_ok=exist_ok)
161         return
162 
163     # Modified version of os.makedirs() with support for extended mode
164     # (e.g. S_ISGID)
165     head, tail = os.path.split(path)
166     if not tail:
167         head, tail = os.path.split(head)
168     if head and tail and not os.path.exists(head):
169         try:
170             makedirs(head, exist_ok=exist_ok, mode=mode)
171         except FileExistsError:
172             # Defeats race condition when another thread created the path
173             pass
174         cdir = os.curdir
175         if isinstance(tail, bytes):
176             cdir = bytes(os.curdir, "ASCII")
177         if tail == cdir:  # xxx/newdir/. exists if xxx/newdir exists
178             return
179     try:
180         os.mkdir(path, mode)
181     except OSError:
182         # Cannot rely on checking for EEXIST, since the operating system
183         # could give priority to other errors like EACCES or EROFS
184         if not exist_ok or not os.path.isdir(path):
185             raise
186 
187     try:
188         os.chmod(path, mode)
189     except OSError:
190         logger.trace("failed to chmod '%o' '%s'", mode, path, exc_info=True)
191 
192 
193 def copyfile(src, dest, no_progress_bar=False, name=None):
194     """Copy file with progress bar"""
195     from dvc.progress import Tqdm
196 
197     name = name if name else os.path.basename(dest)
198     total = os.stat(src).st_size
199 
200     if os.path.isdir(dest):
201         dest = os.path.join(dest, os.path.basename(src))
202 
203     try:
204         System.reflink(src, dest)
205     except DvcException:
206         with open(src, "rb") as fsrc, open(dest, "wb+") as fdest:
207             with Tqdm.wrapattr(
208                 fdest,
209                 "write",
210                 desc=name,
211                 disable=no_progress_bar,
212                 total=total,
213                 bytes=True,
214             ) as fdest_wrapped:
215                 while True:
216                     buf = fsrc.read(LOCAL_CHUNK_SIZE)
217                     if not buf:
218                         break
219                     fdest_wrapped.write(buf)
220 
221 
222 def copy_fobj_to_file(fsrc, dest):
223     """Copy contents of open file object to destination path."""
224     with open(dest, "wb+") as fdest:
225         shutil.copyfileobj(fsrc, fdest)
226 
227 
228 def walk_files(directory):
229     for root, _, files in os.walk(directory):
230         for f in files:
231             yield os.path.join(root, f)
232
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/dvc/utils/fs.py b/dvc/utils/fs.py
--- a/dvc/utils/fs.py
+++ b/dvc/utils/fs.py
@@ -4,11 +4,15 @@
 import shutil
 import stat
 import sys
+from typing import TYPE_CHECKING
 
 from dvc.exceptions import DvcException
 from dvc.system import System
 from dvc.utils import dict_md5
 
+if TYPE_CHECKING:
+    from dvc.types import StrPath
+
 logger = logging.getLogger(__name__)
 
 LOCAL_CHUNK_SIZE = 2 ** 20  # 1 MB
@@ -73,9 +77,9 @@
         super().__init__(msg)
 
 
-def contains_symlink_up_to(path, base_path):
-    base_path = os.fspath(base_path)
-    path = os.fspath(path)
+def contains_symlink_up_to(path: "StrPath", base_path: "StrPath"):
+    base_path = os.path.normcase(os.fspath(base_path))
+    path = os.path.normcase(os.fspath(path))
 
     if base_path not in path:
         raise BasePathNotInCheckedPathException(path, base_path)
@@ -144,11 +148,11 @@
             raise
 
 
-def path_isin(child, parent):
+def path_isin(child: "StrPath", parent: "StrPath"):
     """Check if given `child` path is inside `parent`."""
 
-    def normalize_path(path):
-        return os.path.normpath(path)
+    def normalize_path(path) -> str:
+        return os.path.normcase(os.path.normpath(path))
 
     parent = os.path.join(normalize_path(parent), "")
     child = normalize_path(child)

{"golden_diff": "diff --git a/dvc/utils/fs.py b/dvc/utils/fs.py\n--- a/dvc/utils/fs.py\n+++ b/dvc/utils/fs.py\n@@ -4,11 +4,15 @@\n import shutil\n import stat\n import sys\n+from typing import TYPE_CHECKING\n \n from dvc.exceptions import DvcException\n from dvc.system import System\n from dvc.utils import dict_md5\n \n+if TYPE_CHECKING:\n+    from dvc.types import StrPath\n+\n logger = logging.getLogger(__name__)\n \n LOCAL_CHUNK_SIZE = 2 ** 20  # 1 MB\n@@ -73,9 +77,9 @@\n         super().__init__(msg)\n \n \n-def contains_symlink_up_to(path, base_path):\n-    base_path = os.fspath(base_path)\n-    path = os.fspath(path)\n+def contains_symlink_up_to(path: \"StrPath\", base_path: \"StrPath\"):\n+    base_path = os.path.normcase(os.fspath(base_path))\n+    path = os.path.normcase(os.fspath(path))\n \n     if base_path not in path:\n         raise BasePathNotInCheckedPathException(path, base_path)\n@@ -144,11 +148,11 @@\n             raise\n \n \n-def path_isin(child, parent):\n+def path_isin(child: \"StrPath\", parent: \"StrPath\"):\n     \"\"\"Check if given `child` path is inside `parent`.\"\"\"\n \n-    def normalize_path(path):\n-        return os.path.normpath(path)\n+    def normalize_path(path) -> str:\n+        return os.path.normcase(os.path.normpath(path))\n \n     parent = os.path.join(normalize_path(parent), \"\")\n     child = normalize_path(child)\n", "issue": "add: Path does not overlap with base path\n## Description\r\n\r\nUsing `dvc add` on Windows can produce the error \r\n\r\n```\r\nERROR: Path: C:\\Users\\my_user\\Desktop\\DVC-2.4-TEST does not overlap with base path: C:\\Users\\my_user\\Desktop\\dvc-2.4-test\r\n```\r\n\r\nI believe this is because paths in Windows are case insensitive (note the difference in case between `DVC-2.4-TEST` and `dvc-2.4-test`)\r\n\r\n\r\n### Reproduce\r\n\r\n```\r\nmkdir my_test\r\ncd MY_TEST\r\ngit init\r\ndvc init\r\n```\r\n\r\nThen, move data into the project folder.\r\n\r\n```\r\ndvc add mnist --verbose\r\n```\r\n\r\nWhich outputs\r\n\r\n```console\r\n2021-06-23 08:22:44,410 DEBUG: Check for update is enabled.\r\n2021-06-23 08:22:44,491 DEBUG: Trying to spawn '['daemon', '-q', 'updater']'\r\n2021-06-23 08:22:44,536 DEBUG: Spawned '['daemon', '-q', 'updater']'\r\nAdding...\r\n2021-06-23 08:22:44,558 ERROR: Path: C:\\Users\\my_user\\Desktop\\MY_TEST does not overlap with base path: C:\\Users\\my_user\\Desktop\\my_test\r\n------------------------------------------------------------\r\nTraceback (most recent call last):\r\n  File \"c:\\users\\my_user\\anaconda3\\envs\\dvc-2.4\\lib\\site-packages\\dvc\\command\\add.py\", line 21, in run\r\n    self.repo.add(\r\n  File \"c:\\users\\my_user\\anaconda3\\envs\\dvc-2.4\\lib\\site-packages\\dvc\\repo\\__init__.py\", line 51, in wrapper\r\n    return f(repo, *args, **kwargs)\r\n  File \"c:\\users\\my_user\\anaconda3\\envs\\dvc-2.4\\lib\\site-packages\\dvc\\repo\\scm_context.py\", line 14, in run\r\n    return method(repo, *args, **kw)\r\n  File \"c:\\users\\my_user\\anaconda3\\envs\\dvc-2.4\\lib\\site-packages\\dvc\\repo\\add.py\", line 92, in add\r\n    stages = _create_stages(\r\n  File \"c:\\users\\my_user\\anaconda3\\envs\\dvc-2.4\\lib\\site-packages\\dvc\\repo\\add.py\", line 250, in _create_stages\r\n    path, wdir, out = resolve_paths(\r\n  File \"c:\\users\\my_user\\anaconda3\\envs\\dvc-2.4\\lib\\site-packages\\dvc\\utils\\__init__.py\", line 386, in resolve_paths\r\n    elif contains_symlink_up_to(dirname, repo.root_dir) or (\r\n  File \"c:\\users\\my_user\\anaconda3\\envs\\dvc-2.4\\lib\\site-packages\\dvc\\utils\\fs.py\", line 81, in contains_symlink_up_to\r\n    raise BasePathNotInCheckedPathException(path, base_path)\r\ndvc.utils.fs.BasePathNotInCheckedPathException: Path: C:\\Users\\my_user\\Desktop\\MY_TEST does not overlap with base path: C:\\Users\\my_user\\Desktop\\my_test\r\n------------------------------------------------------------\r\n2021-06-23 08:22:44,563 DEBUG: Analytics is enabled.\r\n2021-06-23 08:22:44,565 DEBUG: Trying to spawn '['daemon', '-q', 'analytics', 'C:\\\\Users\\\\my_user\\\\AppData\\\\Local\\\\Temp\\\\tmpgejvwwr6']'\r\n2021-06-23 08:22:44,610 \r\n```\r\n\r\n\r\n### Expected\r\n\r\n```\r\ndvc add mnist\r\n```\r\n\r\nWhich would output\r\n\r\n```\r\n100% Add|\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588|1/1 [00:00,  1.76file/s]\r\n\r\nTo track the changes with git, run:\r\n\r\n        git add mnist.dvc .gitignore\r\n```\r\n\r\n### Environment information\r\n\r\n**Output of `dvc doctor`:**\r\n\r\n```console\r\nDVC version: 2.4.0+b7c6df\r\n---------------------------------\r\nPlatform: Python 3.8.10 on Windows-10-10.0.19041-SP0\r\nSupports: http, https\r\nCache types: <https://error.dvc.org/no-dvc-cache>\r\nCaches: local\r\nRemotes: local\r\nWorkspace directory: NTFS on C:\\\r\nRepo: dvc, git\r\n```\r\n\r\n**Additional Information (if any):**\r\n\r\nThis error does not happen if you do `cd my_test` instead of `cd MY_TEST`, or in general, if you specify the path with exactly the same case as the true file path.\r\n\n", "before_files": [{"content": "import errno\nimport logging\nimport os\nimport shutil\nimport stat\nimport sys\n\nfrom dvc.exceptions import DvcException\nfrom dvc.system import System\nfrom dvc.utils import dict_md5\n\nlogger = logging.getLogger(__name__)\n\nLOCAL_CHUNK_SIZE = 2 ** 20  # 1 MB\n\numask = os.umask(0)\nos.umask(umask)\n\n\ndef fs_copy(src, dst, ignore=None):\n    if os.path.isdir(src):\n        shutil.copytree(src, dst, ignore=ignore)\n    else:\n        shutil.copy2(src, dst)\n\n\ndef get_inode(path):\n    inode = System.inode(path)\n    logger.trace(\"Path '%s' inode '%d'\", path, inode)\n    return inode\n\n\ndef get_mtime_and_size(path, fs, dvcignore=None):\n    import nanotime\n\n    if fs.isdir(path):\n        size = 0\n        files_mtimes = {}\n        if dvcignore:\n            walk_iterator = dvcignore.walk_files(fs, path)\n        else:\n            walk_iterator = fs.walk_files(path)\n        for file_path in walk_iterator:\n            try:\n                stats = fs.stat(file_path)\n            except OSError as exc:\n                # NOTE: broken symlink case.\n                if exc.errno != errno.ENOENT:\n                    raise\n                continue\n            size += stats.st_size\n            files_mtimes[os.fspath(file_path)] = stats.st_mtime\n\n        # We track file changes and moves, which cannot be detected with simply\n        # max(mtime(f) for f in non_ignored_files)\n        mtime = dict_md5(files_mtimes)\n    else:\n        base_stat = fs.stat(path)\n        size = base_stat.st_size\n        mtime = base_stat.st_mtime\n        mtime = int(nanotime.timestamp(mtime))\n\n    # State of files handled by dvc is stored in db as TEXT.\n    # We cast results to string for later comparisons with stored values.\n    return str(mtime), str(size)\n\n\nclass BasePathNotInCheckedPathException(DvcException):\n    def __init__(self, path, base_path):\n        msg = \"Path: {} does not overlap with base path: {}\".format(\n            path, base_path\n        )\n        super().__init__(msg)\n\n\ndef contains_symlink_up_to(path, base_path):\n    base_path = os.fspath(base_path)\n    path = os.fspath(path)\n\n    if base_path not in path:\n        raise BasePathNotInCheckedPathException(path, base_path)\n\n    if path == base_path:\n        return False\n    if System.is_symlink(path):\n        return True\n    if os.path.dirname(path) == path:\n        return False\n    return contains_symlink_up_to(os.path.dirname(path), base_path)\n\n\ndef move(src, dst):\n    \"\"\"Atomically move src to dst and chmod it with mode.\n\n    Moving is performed in two stages to make the whole operation atomic in\n    case src and dst are on different filesystems and actual physical copying\n    of data is happening.\n    \"\"\"\n    from shortuuid import uuid\n\n    dst = os.path.abspath(dst)\n    tmp = f\"{dst}.{uuid()}\"\n\n    if os.path.islink(src):\n        shutil.copy(src, tmp)\n        _unlink(src, _chmod)\n    else:\n        shutil.move(src, tmp)\n\n    shutil.move(tmp, dst)\n\n\ndef _chmod(func, p, excinfo):  # pylint: disable=unused-argument\n    perm = os.lstat(p).st_mode\n    perm |= stat.S_IWRITE\n\n    try:\n        os.chmod(p, perm)\n    except OSError as exc:\n        # broken symlink or file is not owned by us\n        if exc.errno not in [errno.ENOENT, errno.EPERM]:\n            raise\n\n    func(p)\n\n\ndef _unlink(path, onerror):\n    try:\n        os.unlink(path)\n    except OSError:\n        onerror(os.unlink, path, sys.exc_info())\n\n\ndef remove(path):\n    logger.debug(\"Removing '%s'\", path)\n\n    try:\n        if os.path.isdir(path):\n            shutil.rmtree(path, onerror=_chmod)\n        else:\n            _unlink(path, _chmod)\n    except OSError as exc:\n        if exc.errno != errno.ENOENT:\n            raise\n\n\ndef path_isin(child, parent):\n    \"\"\"Check if given `child` path is inside `parent`.\"\"\"\n\n    def normalize_path(path):\n        return os.path.normpath(path)\n\n    parent = os.path.join(normalize_path(parent), \"\")\n    child = normalize_path(child)\n    return child != parent and child.startswith(parent)\n\n\ndef makedirs(path, exist_ok=False, mode=None):\n    if mode is None:\n        os.makedirs(path, exist_ok=exist_ok)\n        return\n\n    # Modified version of os.makedirs() with support for extended mode\n    # (e.g. S_ISGID)\n    head, tail = os.path.split(path)\n    if not tail:\n        head, tail = os.path.split(head)\n    if head and tail and not os.path.exists(head):\n        try:\n            makedirs(head, exist_ok=exist_ok, mode=mode)\n        except FileExistsError:\n            # Defeats race condition when another thread created the path\n            pass\n        cdir = os.curdir\n        if isinstance(tail, bytes):\n            cdir = bytes(os.curdir, \"ASCII\")\n        if tail == cdir:  # xxx/newdir/. exists if xxx/newdir exists\n            return\n    try:\n        os.mkdir(path, mode)\n    except OSError:\n        # Cannot rely on checking for EEXIST, since the operating system\n        # could give priority to other errors like EACCES or EROFS\n        if not exist_ok or not os.path.isdir(path):\n            raise\n\n    try:\n        os.chmod(path, mode)\n    except OSError:\n        logger.trace(\"failed to chmod '%o' '%s'\", mode, path, exc_info=True)\n\n\ndef copyfile(src, dest, no_progress_bar=False, name=None):\n    \"\"\"Copy file with progress bar\"\"\"\n    from dvc.progress import Tqdm\n\n    name = name if name else os.path.basename(dest)\n    total = os.stat(src).st_size\n\n    if os.path.isdir(dest):\n        dest = os.path.join(dest, os.path.basename(src))\n\n    try:\n        System.reflink(src, dest)\n    except DvcException:\n        with open(src, \"rb\") as fsrc, open(dest, \"wb+\") as fdest:\n            with Tqdm.wrapattr(\n                fdest,\n                \"write\",\n                desc=name,\n                disable=no_progress_bar,\n                total=total,\n                bytes=True,\n            ) as fdest_wrapped:\n                while True:\n                    buf = fsrc.read(LOCAL_CHUNK_SIZE)\n                    if not buf:\n                        break\n                    fdest_wrapped.write(buf)\n\n\ndef copy_fobj_to_file(fsrc, dest):\n    \"\"\"Copy contents of open file object to destination path.\"\"\"\n    with open(dest, \"wb+\") as fdest:\n        shutil.copyfileobj(fsrc, fdest)\n\n\ndef walk_files(directory):\n    for root, _, files in os.walk(directory):\n        for f in files:\n            yield os.path.join(root, f)\n", "path": "dvc/utils/fs.py"}], "after_files": [{"content": "import errno\nimport logging\nimport os\nimport shutil\nimport stat\nimport sys\nfrom typing import TYPE_CHECKING\n\nfrom dvc.exceptions import DvcException\nfrom dvc.system import System\nfrom dvc.utils import dict_md5\n\nif TYPE_CHECKING:\n    from dvc.types import StrPath\n\nlogger = logging.getLogger(__name__)\n\nLOCAL_CHUNK_SIZE = 2 ** 20  # 1 MB\n\numask = os.umask(0)\nos.umask(umask)\n\n\ndef fs_copy(src, dst, ignore=None):\n    if os.path.isdir(src):\n        shutil.copytree(src, dst, ignore=ignore)\n    else:\n        shutil.copy2(src, dst)\n\n\ndef get_inode(path):\n    inode = System.inode(path)\n    logger.trace(\"Path '%s' inode '%d'\", path, inode)\n    return inode\n\n\ndef get_mtime_and_size(path, fs, dvcignore=None):\n    import nanotime\n\n    if fs.isdir(path):\n        size = 0\n        files_mtimes = {}\n        if dvcignore:\n            walk_iterator = dvcignore.walk_files(fs, path)\n        else:\n            walk_iterator = fs.walk_files(path)\n        for file_path in walk_iterator:\n            try:\n                stats = fs.stat(file_path)\n            except OSError as exc:\n                # NOTE: broken symlink case.\n                if exc.errno != errno.ENOENT:\n                    raise\n                continue\n            size += stats.st_size\n            files_mtimes[os.fspath(file_path)] = stats.st_mtime\n\n        # We track file changes and moves, which cannot be detected with simply\n        # max(mtime(f) for f in non_ignored_files)\n        mtime = dict_md5(files_mtimes)\n    else:\n        base_stat = fs.stat(path)\n        size = base_stat.st_size\n        mtime = base_stat.st_mtime\n        mtime = int(nanotime.timestamp(mtime))\n\n    # State of files handled by dvc is stored in db as TEXT.\n    # We cast results to string for later comparisons with stored values.\n    return str(mtime), str(size)\n\n\nclass BasePathNotInCheckedPathException(DvcException):\n    def __init__(self, path, base_path):\n        msg = \"Path: {} does not overlap with base path: {}\".format(\n            path, base_path\n        )\n        super().__init__(msg)\n\n\ndef contains_symlink_up_to(path: \"StrPath\", base_path: \"StrPath\"):\n    base_path = os.path.normcase(os.fspath(base_path))\n    path = os.path.normcase(os.fspath(path))\n\n    if base_path not in path:\n        raise BasePathNotInCheckedPathException(path, base_path)\n\n    if path == base_path:\n        return False\n    if System.is_symlink(path):\n        return True\n    if os.path.dirname(path) == path:\n        return False\n    return contains_symlink_up_to(os.path.dirname(path), base_path)\n\n\ndef move(src, dst):\n    \"\"\"Atomically move src to dst and chmod it with mode.\n\n    Moving is performed in two stages to make the whole operation atomic in\n    case src and dst are on different filesystems and actual physical copying\n    of data is happening.\n    \"\"\"\n    from shortuuid import uuid\n\n    dst = os.path.abspath(dst)\n    tmp = f\"{dst}.{uuid()}\"\n\n    if os.path.islink(src):\n        shutil.copy(src, tmp)\n        _unlink(src, _chmod)\n    else:\n        shutil.move(src, tmp)\n\n    shutil.move(tmp, dst)\n\n\ndef _chmod(func, p, excinfo):  # pylint: disable=unused-argument\n    perm = os.lstat(p).st_mode\n    perm |= stat.S_IWRITE\n\n    try:\n        os.chmod(p, perm)\n    except OSError as exc:\n        # broken symlink or file is not owned by us\n        if exc.errno not in [errno.ENOENT, errno.EPERM]:\n            raise\n\n    func(p)\n\n\ndef _unlink(path, onerror):\n    try:\n        os.unlink(path)\n    except OSError:\n        onerror(os.unlink, path, sys.exc_info())\n\n\ndef remove(path):\n    logger.debug(\"Removing '%s'\", path)\n\n    try:\n        if os.path.isdir(path):\n            shutil.rmtree(path, onerror=_chmod)\n        else:\n            _unlink(path, _chmod)\n    except OSError as exc:\n        if exc.errno != errno.ENOENT:\n            raise\n\n\ndef path_isin(child: \"StrPath\", parent: \"StrPath\"):\n    \"\"\"Check if given `child` path is inside `parent`.\"\"\"\n\n    def normalize_path(path) -> str:\n        return os.path.normcase(os.path.normpath(path))\n\n    parent = os.path.join(normalize_path(parent), \"\")\n    child = normalize_path(child)\n    return child != parent and child.startswith(parent)\n\n\ndef makedirs(path, exist_ok=False, mode=None):\n    if mode is None:\n        os.makedirs(path, exist_ok=exist_ok)\n        return\n\n    # Modified version of os.makedirs() with support for extended mode\n    # (e.g. S_ISGID)\n    head, tail = os.path.split(path)\n    if not tail:\n        head, tail = os.path.split(head)\n    if head and tail and not os.path.exists(head):\n        try:\n            makedirs(head, exist_ok=exist_ok, mode=mode)\n        except FileExistsError:\n            # Defeats race condition when another thread created the path\n            pass\n        cdir = os.curdir\n        if isinstance(tail, bytes):\n            cdir = bytes(os.curdir, \"ASCII\")\n        if tail == cdir:  # xxx/newdir/. exists if xxx/newdir exists\n            return\n    try:\n        os.mkdir(path, mode)\n    except OSError:\n        # Cannot rely on checking for EEXIST, since the operating system\n        # could give priority to other errors like EACCES or EROFS\n        if not exist_ok or not os.path.isdir(path):\n            raise\n\n    try:\n        os.chmod(path, mode)\n    except OSError:\n        logger.trace(\"failed to chmod '%o' '%s'\", mode, path, exc_info=True)\n\n\ndef copyfile(src, dest, no_progress_bar=False, name=None):\n    \"\"\"Copy file with progress bar\"\"\"\n    from dvc.progress import Tqdm\n\n    name = name if name else os.path.basename(dest)\n    total = os.stat(src).st_size\n\n    if os.path.isdir(dest):\n        dest = os.path.join(dest, os.path.basename(src))\n\n    try:\n        System.reflink(src, dest)\n    except DvcException:\n        with open(src, \"rb\") as fsrc, open(dest, \"wb+\") as fdest:\n            with Tqdm.wrapattr(\n                fdest,\n                \"write\",\n                desc=name,\n                disable=no_progress_bar,\n                total=total,\n                bytes=True,\n            ) as fdest_wrapped:\n                while True:\n                    buf = fsrc.read(LOCAL_CHUNK_SIZE)\n                    if not buf:\n                        break\n                    fdest_wrapped.write(buf)\n\n\ndef copy_fobj_to_file(fsrc, dest):\n    \"\"\"Copy contents of open file object to destination path.\"\"\"\n    with open(dest, \"wb+\") as fdest:\n        shutil.copyfileobj(fsrc, fdest)\n\n\ndef walk_files(directory):\n    for root, _, files in os.walk(directory):\n        for f in files:\n            yield os.path.join(root, f)\n", "path": "dvc/utils/fs.py"}]}