diff options
author | Mike Frysinger <vapier@google.com> | 2021-09-28 11:27:24 -0400 |
---|---|---|
committer | Mike Frysinger <vapier@google.com> | 2021-09-28 16:06:50 +0000 |
commit | 9d96f58f5fcec101c612e61c3e2526ca071d89ea (patch) | |
tree | 63bc9e73e3b7d74a2cf5352239bf4f2e9695b507 | |
parent | 7a1e7e772f3bbc67660e824c98f527b5f608ac24 (diff) | |
download | git-repo-9d96f58f5fcec101c612e61c3e2526ca071d89ea.tar.gz |
make file removal a bit more robust
Some of the file removal calls are subject to race conditions (if
something else deletes the file), so extend our remove API to have
an option to ignore ENOENT errors. Then update a bunch of random
call sites to use this new functionality.
Change-Id: I31a9090e135452033135337a202a4fc2dbf8b63c
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/319195
Reviewed-by: Sean McAllister <smcallis@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
-rw-r--r-- | git_config.py | 7 | ||||
-rw-r--r-- | manifest_xml.py | 3 | ||||
-rw-r--r-- | platform_utils.py | 31 | ||||
-rw-r--r-- | project.py | 20 | ||||
-rw-r--r-- | subcmds/sync.py | 20 | ||||
-rw-r--r-- | tests/test_platform_utils.py | 50 |
6 files changed, 80 insertions, 51 deletions
diff --git a/git_config.py b/git_config.py index 778e81a4..bc70d160 100644 --- a/git_config.py +++ b/git_config.py | |||
@@ -352,8 +352,8 @@ class GitConfig(object): | |||
352 | Trace(': parsing %s', self.file) | 352 | Trace(': parsing %s', self.file) |
353 | with open(self._json) as fd: | 353 | with open(self._json) as fd: |
354 | return json.load(fd) | 354 | return json.load(fd) |
355 | except (IOError, ValueError): | 355 | except (IOError, ValueErrorl): |
356 | platform_utils.remove(self._json) | 356 | platform_utils.remove(self._json, missing_ok=True) |
357 | return None | 357 | return None |
358 | 358 | ||
359 | def _SaveJson(self, cache): | 359 | def _SaveJson(self, cache): |
@@ -361,8 +361,7 @@ class GitConfig(object): | |||
361 | with open(self._json, 'w') as fd: | 361 | with open(self._json, 'w') as fd: |
362 | json.dump(cache, fd, indent=2) | 362 | json.dump(cache, fd, indent=2) |
363 | except (IOError, TypeError): | 363 | except (IOError, TypeError): |
364 | if os.path.exists(self._json): | 364 | platform_utils.remove(self._json, missing_ok=True) |
365 | platform_utils.remove(self._json) | ||
366 | 365 | ||
367 | def _ReadGit(self): | 366 | def _ReadGit(self): |
368 | """ | 367 | """ |
diff --git a/manifest_xml.py b/manifest_xml.py index 135c91fb..86f20202 100644 --- a/manifest_xml.py +++ b/manifest_xml.py | |||
@@ -270,8 +270,7 @@ class XmlManifest(object): | |||
270 | self.Override(name) | 270 | self.Override(name) |
271 | 271 | ||
272 | # Old versions of repo would generate symlinks we need to clean up. | 272 | # Old versions of repo would generate symlinks we need to clean up. |
273 | if os.path.lexists(self.manifestFile): | 273 | platform_utils.remove(self.manifestFile, missing_ok=True) |
274 | platform_utils.remove(self.manifestFile) | ||
275 | # This file is interpreted as if it existed inside the manifest repo. | 274 | # This file is interpreted as if it existed inside the manifest repo. |
276 | # That allows us to use <include> with the relative file name. | 275 | # That allows us to use <include> with the relative file name. |
277 | with open(self.manifestFile, 'w') as fp: | 276 | with open(self.manifestFile, 'w') as fp: |
diff --git a/platform_utils.py b/platform_utils.py index 5741f4d3..0203249a 100644 --- a/platform_utils.py +++ b/platform_utils.py | |||
@@ -127,28 +127,27 @@ def rename(src, dst): | |||
127 | shutil.move(src, dst) | 127 | shutil.move(src, dst) |
128 | 128 | ||
129 | 129 | ||
130 | def remove(path): | 130 | def remove(path, missing_ok=False): |
131 | """Remove (delete) the file path. This is a replacement for os.remove that | 131 | """Remove (delete) the file path. This is a replacement for os.remove that |
132 | allows deleting read-only files on Windows, with support for long paths and | 132 | allows deleting read-only files on Windows, with support for long paths and |
133 | for deleting directory symbolic links. | 133 | for deleting directory symbolic links. |
134 | 134 | ||
135 | Availability: Unix, Windows.""" | 135 | Availability: Unix, Windows.""" |
136 | if isWindows(): | 136 | longpath = _makelongpath(path) if isWindows() else path |
137 | longpath = _makelongpath(path) | 137 | try: |
138 | try: | 138 | os.remove(longpath) |
139 | os.remove(longpath) | 139 | except OSError as e: |
140 | except OSError as e: | 140 | if e.errno == errno.EACCES: |
141 | if e.errno == errno.EACCES: | 141 | os.chmod(longpath, stat.S_IWRITE) |
142 | os.chmod(longpath, stat.S_IWRITE) | 142 | # Directory symbolic links must be deleted with 'rmdir'. |
143 | # Directory symbolic links must be deleted with 'rmdir'. | 143 | if islink(longpath) and isdir(longpath): |
144 | if islink(longpath) and isdir(longpath): | 144 | os.rmdir(longpath) |
145 | os.rmdir(longpath) | ||
146 | else: | ||
147 | os.remove(longpath) | ||
148 | else: | 145 | else: |
149 | raise | 146 | os.remove(longpath) |
150 | else: | 147 | elif missing_ok and e.errno == errno.ENOENT: |
151 | os.remove(path) | 148 | pass |
149 | else: | ||
150 | raise | ||
152 | 151 | ||
153 | 152 | ||
154 | def walk(top, topdown=True, onerror=None, followlinks=False): | 153 | def walk(top, topdown=True, onerror=None, followlinks=False): |
@@ -1182,10 +1182,8 @@ class Project(object): | |||
1182 | self._InitMRef() | 1182 | self._InitMRef() |
1183 | else: | 1183 | else: |
1184 | self._InitMirrorHead() | 1184 | self._InitMirrorHead() |
1185 | try: | 1185 | platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD'), |
1186 | platform_utils.remove(os.path.join(self.gitdir, 'FETCH_HEAD')) | 1186 | missing_ok=True) |
1187 | except OSError: | ||
1188 | pass | ||
1189 | return True | 1187 | return True |
1190 | 1188 | ||
1191 | def PostRepoUpgrade(self): | 1189 | def PostRepoUpgrade(self): |
@@ -2307,15 +2305,12 @@ class Project(object): | |||
2307 | cmd.append('+refs/tags/*:refs/tags/*') | 2305 | cmd.append('+refs/tags/*:refs/tags/*') |
2308 | 2306 | ||
2309 | ok = GitCommand(self, cmd, bare=True).Wait() == 0 | 2307 | ok = GitCommand(self, cmd, bare=True).Wait() == 0 |
2310 | if os.path.exists(bundle_dst): | 2308 | platform_utils.remove(bundle_dst, missing_ok=True) |
2311 | platform_utils.remove(bundle_dst) | 2309 | platform_utils.remove(bundle_tmp, missing_ok=True) |
2312 | if os.path.exists(bundle_tmp): | ||
2313 | platform_utils.remove(bundle_tmp) | ||
2314 | return ok | 2310 | return ok |
2315 | 2311 | ||
2316 | def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose): | 2312 | def _FetchBundle(self, srcUrl, tmpPath, dstPath, quiet, verbose): |
2317 | if os.path.exists(dstPath): | 2313 | platform_utils.remove(dstPath, missing_ok=True) |
2318 | platform_utils.remove(dstPath) | ||
2319 | 2314 | ||
2320 | cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location'] | 2315 | cmd = ['curl', '--fail', '--output', tmpPath, '--netrc', '--location'] |
2321 | if quiet: | 2316 | if quiet: |
@@ -2739,10 +2734,7 @@ class Project(object): | |||
2739 | # If the source file doesn't exist, ensure the destination | 2734 | # If the source file doesn't exist, ensure the destination |
2740 | # file doesn't either. | 2735 | # file doesn't either. |
2741 | if name in symlink_files and not os.path.lexists(src): | 2736 | if name in symlink_files and not os.path.lexists(src): |
2742 | try: | 2737 | platform_utils.remove(dst, missing_ok=True) |
2743 | platform_utils.remove(dst) | ||
2744 | except OSError: | ||
2745 | pass | ||
2746 | 2738 | ||
2747 | except OSError as e: | 2739 | except OSError as e: |
2748 | if e.errno == errno.EPERM: | 2740 | if e.errno == errno.EPERM: |
diff --git a/subcmds/sync.py b/subcmds/sync.py index c99b06ca..3211cbb1 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py | |||
@@ -767,13 +767,9 @@ later is required to fix a server side protocol bug. | |||
767 | set(new_copyfile_paths)) | 767 | set(new_copyfile_paths)) |
768 | 768 | ||
769 | for need_remove_file in need_remove_files: | 769 | for need_remove_file in need_remove_files: |
770 | try: | 770 | # Try to remove the updated copyfile or linkfile. |
771 | platform_utils.remove(need_remove_file) | 771 | # So, if the file is not exist, nothing need to do. |
772 | except OSError as e: | 772 | platform_utils.remove(need_remove_file, missing_ok=True) |
773 | if e.errno == errno.ENOENT: | ||
774 | # Try to remove the updated copyfile or linkfile. | ||
775 | # So, if the file is not exist, nothing need to do. | ||
776 | pass | ||
777 | 773 | ||
778 | # Create copy-link-files.json, save dest path of "copyfile" and "linkfile". | 774 | # Create copy-link-files.json, save dest path of "copyfile" and "linkfile". |
779 | with open(copylinkfile_path, 'w', encoding='utf-8') as fp: | 775 | with open(copylinkfile_path, 'w', encoding='utf-8') as fp: |
@@ -1171,10 +1167,7 @@ class _FetchTimes(object): | |||
1171 | with open(self._path) as f: | 1167 | with open(self._path) as f: |
1172 | self._times = json.load(f) | 1168 | self._times = json.load(f) |
1173 | except (IOError, ValueError): | 1169 | except (IOError, ValueError): |
1174 | try: | 1170 | platform_utils.remove(self._path, missing_ok=True) |
1175 | platform_utils.remove(self._path) | ||
1176 | except OSError: | ||
1177 | pass | ||
1178 | self._times = {} | 1171 | self._times = {} |
1179 | 1172 | ||
1180 | def Save(self): | 1173 | def Save(self): |
@@ -1192,10 +1185,7 @@ class _FetchTimes(object): | |||
1192 | with open(self._path, 'w') as f: | 1185 | with open(self._path, 'w') as f: |
1193 | json.dump(self._times, f, indent=2) | 1186 | json.dump(self._times, f, indent=2) |
1194 | except (IOError, TypeError): | 1187 | except (IOError, TypeError): |
1195 | try: | 1188 | platform_utils.remove(self._path, missing_ok=True) |
1196 | platform_utils.remove(self._path) | ||
1197 | except OSError: | ||
1198 | pass | ||
1199 | 1189 | ||
1200 | # This is a replacement for xmlrpc.client.Transport using urllib2 | 1190 | # This is a replacement for xmlrpc.client.Transport using urllib2 |
1201 | # and supporting persistent-http[s]. It cannot change hosts from | 1191 | # and supporting persistent-http[s]. It cannot change hosts from |
diff --git a/tests/test_platform_utils.py b/tests/test_platform_utils.py new file mode 100644 index 00000000..55b7805c --- /dev/null +++ b/tests/test_platform_utils.py | |||
@@ -0,0 +1,50 @@ | |||
1 | # Copyright 2021 The Android Open Source Project | ||
2 | # | ||
3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
4 | # you may not use this file except in compliance with the License. | ||
5 | # You may obtain a copy of the License at | ||
6 | # | ||
7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
8 | # | ||
9 | # Unless required by applicable law or agreed to in writing, software | ||
10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
12 | # See the License for the specific language governing permissions and | ||
13 | # limitations under the License. | ||
14 | |||
15 | """Unittests for the platform_utils.py module.""" | ||
16 | |||
17 | import os | ||
18 | import tempfile | ||
19 | import unittest | ||
20 | |||
21 | import platform_utils | ||
22 | |||
23 | |||
24 | class RemoveTests(unittest.TestCase): | ||
25 | """Check remove() helper.""" | ||
26 | |||
27 | def testMissingOk(self): | ||
28 | """Check missing_ok handling.""" | ||
29 | with tempfile.TemporaryDirectory() as tmpdir: | ||
30 | path = os.path.join(tmpdir, 'test') | ||
31 | |||
32 | # Should not fail. | ||
33 | platform_utils.remove(path, missing_ok=True) | ||
34 | |||
35 | # Should fail. | ||
36 | self.assertRaises(OSError, platform_utils.remove, path) | ||
37 | self.assertRaises(OSError, platform_utils.remove, path, missing_ok=False) | ||
38 | |||
39 | # Should not fail if it exists. | ||
40 | open(path, 'w').close() | ||
41 | platform_utils.remove(path, missing_ok=True) | ||
42 | self.assertFalse(os.path.exists(path)) | ||
43 | |||
44 | open(path, 'w').close() | ||
45 | platform_utils.remove(path) | ||
46 | self.assertFalse(os.path.exists(path)) | ||
47 | |||
48 | open(path, 'w').close() | ||
49 | platform_utils.remove(path, missing_ok=False) | ||
50 | self.assertFalse(os.path.exists(path)) | ||