diff options
-rw-r--r-- | git_config.py | 2 | ||||
-rw-r--r-- | git_refs.py | 5 | ||||
-rw-r--r-- | manifest_xml.py | 2 | ||||
-rw-r--r-- | platform_utils.py | 126 | ||||
-rwxr-xr-x | project.py | 16 | ||||
-rw-r--r-- | subcmds/status.py | 3 | ||||
-rw-r--r-- | subcmds/sync.py | 26 |
7 files changed, 138 insertions, 42 deletions
diff --git a/git_config.py b/git_config.py index 70b22ce1..aac08855 100644 --- a/git_config.py +++ b/git_config.py | |||
@@ -503,7 +503,7 @@ def close_ssh(): | |||
503 | d = ssh_sock(create=False) | 503 | d = ssh_sock(create=False) |
504 | if d: | 504 | if d: |
505 | try: | 505 | try: |
506 | os.rmdir(os.path.dirname(d)) | 506 | platform_utils.rmdir(os.path.dirname(d)) |
507 | except OSError: | 507 | except OSError: |
508 | pass | 508 | pass |
509 | 509 | ||
diff --git a/git_refs.py b/git_refs.py index 7feaffb1..e0a85d7a 100644 --- a/git_refs.py +++ b/git_refs.py | |||
@@ -15,6 +15,7 @@ | |||
15 | 15 | ||
16 | import os | 16 | import os |
17 | from trace import Trace | 17 | from trace import Trace |
18 | import platform_utils | ||
18 | 19 | ||
19 | HEAD = 'HEAD' | 20 | HEAD = 'HEAD' |
20 | R_CHANGES = 'refs/changes/' | 21 | R_CHANGES = 'refs/changes/' |
@@ -127,9 +128,9 @@ class GitRefs(object): | |||
127 | 128 | ||
128 | def _ReadLoose(self, prefix): | 129 | def _ReadLoose(self, prefix): |
129 | base = os.path.join(self._gitdir, prefix) | 130 | base = os.path.join(self._gitdir, prefix) |
130 | for name in os.listdir(base): | 131 | for name in platform_utils.listdir(base): |
131 | p = os.path.join(base, name) | 132 | p = os.path.join(base, name) |
132 | if os.path.isdir(p): | 133 | if platform_utils.isdir(p): |
133 | self._mtime[prefix] = os.path.getmtime(base) | 134 | self._mtime[prefix] = os.path.getmtime(base) |
134 | self._ReadLoose(prefix + name + '/') | 135 | self._ReadLoose(prefix + name + '/') |
135 | elif name.endswith('.lock'): | 136 | elif name.endswith('.lock'): |
diff --git a/manifest_xml.py b/manifest_xml.py index 81a6a858..f37732cd 100644 --- a/manifest_xml.py +++ b/manifest_xml.py | |||
@@ -446,7 +446,7 @@ class XmlManifest(object): | |||
446 | 446 | ||
447 | local_dir = os.path.abspath(os.path.join(self.repodir, LOCAL_MANIFESTS_DIR_NAME)) | 447 | local_dir = os.path.abspath(os.path.join(self.repodir, LOCAL_MANIFESTS_DIR_NAME)) |
448 | try: | 448 | try: |
449 | for local_file in sorted(os.listdir(local_dir)): | 449 | for local_file in sorted(platform_utils.listdir(local_dir)): |
450 | if local_file.endswith('.xml'): | 450 | if local_file.endswith('.xml'): |
451 | local = os.path.join(local_dir, local_file) | 451 | local = os.path.join(local_dir, local_file) |
452 | nodes.append(self._ParseManifestXml(local, self.repodir)) | 452 | nodes.append(self._ParseManifestXml(local, self.repodir)) |
diff --git a/platform_utils.py b/platform_utils.py index a3e96531..b2cc2459 100644 --- a/platform_utils.py +++ b/platform_utils.py | |||
@@ -187,10 +187,10 @@ def symlink(source, link_name): | |||
187 | source = _validate_winpath(source) | 187 | source = _validate_winpath(source) |
188 | link_name = _validate_winpath(link_name) | 188 | link_name = _validate_winpath(link_name) |
189 | target = os.path.join(os.path.dirname(link_name), source) | 189 | target = os.path.join(os.path.dirname(link_name), source) |
190 | if os.path.isdir(target): | 190 | if isdir(target): |
191 | platform_utils_win32.create_dirsymlink(source, link_name) | 191 | platform_utils_win32.create_dirsymlink(_makelongpath(source), link_name) |
192 | else: | 192 | else: |
193 | platform_utils_win32.create_filesymlink(source, link_name) | 193 | platform_utils_win32.create_filesymlink(_makelongpath(source), link_name) |
194 | else: | 194 | else: |
195 | return os.symlink(source, link_name) | 195 | return os.symlink(source, link_name) |
196 | 196 | ||
@@ -220,9 +220,32 @@ def _winpath_is_valid(path): | |||
220 | return not drive # "x:" is invalid | 220 | return not drive # "x:" is invalid |
221 | 221 | ||
222 | 222 | ||
223 | def _makelongpath(path): | ||
224 | """Return the input path normalized to support the Windows long path syntax | ||
225 | ("\\\\?\\" prefix) if needed, i.e. if the input path is longer than the | ||
226 | MAX_PATH limit. | ||
227 | """ | ||
228 | if isWindows(): | ||
229 | # Note: MAX_PATH is 260, but, for directories, the maximum value is actually 246. | ||
230 | if len(path) < 246: | ||
231 | return path | ||
232 | if path.startswith(u"\\\\?\\"): | ||
233 | return path | ||
234 | if not os.path.isabs(path): | ||
235 | return path | ||
236 | # Append prefix and ensure unicode so that the special longpath syntax | ||
237 | # is supported by underlying Win32 API calls | ||
238 | return u"\\\\?\\" + os.path.normpath(path) | ||
239 | else: | ||
240 | return path | ||
241 | |||
242 | |||
223 | def rmtree(path): | 243 | def rmtree(path): |
244 | """shutil.rmtree(path) wrapper with support for long paths on Windows. | ||
245 | |||
246 | Availability: Unix, Windows.""" | ||
224 | if isWindows(): | 247 | if isWindows(): |
225 | shutil.rmtree(path, onerror=handle_rmtree_error) | 248 | shutil.rmtree(_makelongpath(path), onerror=handle_rmtree_error) |
226 | else: | 249 | else: |
227 | shutil.rmtree(path) | 250 | shutil.rmtree(path) |
228 | 251 | ||
@@ -234,15 +257,18 @@ def handle_rmtree_error(function, path, excinfo): | |||
234 | 257 | ||
235 | 258 | ||
236 | def rename(src, dst): | 259 | def rename(src, dst): |
260 | """os.rename(src, dst) wrapper with support for long paths on Windows. | ||
261 | |||
262 | Availability: Unix, Windows.""" | ||
237 | if isWindows(): | 263 | if isWindows(): |
238 | # On Windows, rename fails if destination exists, see | 264 | # On Windows, rename fails if destination exists, see |
239 | # https://docs.python.org/2/library/os.html#os.rename | 265 | # https://docs.python.org/2/library/os.html#os.rename |
240 | try: | 266 | try: |
241 | os.rename(src, dst) | 267 | os.rename(_makelongpath(src), _makelongpath(dst)) |
242 | except OSError as e: | 268 | except OSError as e: |
243 | if e.errno == errno.EEXIST: | 269 | if e.errno == errno.EEXIST: |
244 | os.remove(dst) | 270 | os.remove(_makelongpath(dst)) |
245 | os.rename(src, dst) | 271 | os.rename(_makelongpath(src), _makelongpath(dst)) |
246 | else: | 272 | else: |
247 | raise | 273 | raise |
248 | else: | 274 | else: |
@@ -250,30 +276,98 @@ def rename(src, dst): | |||
250 | 276 | ||
251 | 277 | ||
252 | def remove(path): | 278 | def remove(path): |
253 | """Remove (delete) the file path. This is a replacement for os.remove, but | 279 | """Remove (delete) the file path. This is a replacement for os.remove that |
254 | allows deleting read-only files on Windows. | 280 | allows deleting read-only files on Windows, with support for long paths and |
255 | """ | 281 | for deleting directory symbolic links. |
282 | |||
283 | Availability: Unix, Windows.""" | ||
256 | if isWindows(): | 284 | if isWindows(): |
285 | longpath = _makelongpath(path) | ||
257 | try: | 286 | try: |
258 | os.remove(path) | 287 | os.remove(longpath) |
259 | except OSError as e: | 288 | except OSError as e: |
260 | if e.errno == errno.EACCES: | 289 | if e.errno == errno.EACCES: |
261 | os.chmod(path, stat.S_IWRITE) | 290 | os.chmod(longpath, stat.S_IWRITE) |
262 | os.remove(path) | 291 | # Directory symbolic links must be deleted with 'rmdir'. |
292 | if islink(longpath) and isdir(longpath): | ||
293 | os.rmdir(longpath) | ||
294 | else: | ||
295 | os.remove(longpath) | ||
263 | else: | 296 | else: |
264 | raise | 297 | raise |
265 | else: | 298 | else: |
266 | os.remove(path) | 299 | os.remove(path) |
267 | 300 | ||
268 | 301 | ||
302 | def walk(top, topdown=True, onerror=None, followlinks=False): | ||
303 | """os.walk(path) wrapper with support for long paths on Windows. | ||
304 | |||
305 | Availability: Windows, Unix. | ||
306 | """ | ||
307 | if isWindows(): | ||
308 | return _walk_windows_impl(top, topdown, onerror, followlinks) | ||
309 | else: | ||
310 | return os.walk(top, topdown, onerror, followlinks) | ||
311 | |||
312 | |||
313 | def _walk_windows_impl(top, topdown, onerror, followlinks): | ||
314 | try: | ||
315 | names = listdir(top) | ||
316 | except error, err: | ||
317 | if onerror is not None: | ||
318 | onerror(err) | ||
319 | return | ||
320 | |||
321 | dirs, nondirs = [], [] | ||
322 | for name in names: | ||
323 | if isdir(os.path.join(top, name)): | ||
324 | dirs.append(name) | ||
325 | else: | ||
326 | nondirs.append(name) | ||
327 | |||
328 | if topdown: | ||
329 | yield top, dirs, nondirs | ||
330 | for name in dirs: | ||
331 | new_path = os.path.join(top, name) | ||
332 | if followlinks or not islink(new_path): | ||
333 | for x in _walk_windows_impl(new_path, topdown, onerror, followlinks): | ||
334 | yield x | ||
335 | if not topdown: | ||
336 | yield top, dirs, nondirs | ||
337 | |||
338 | |||
339 | def listdir(path): | ||
340 | """os.listdir(path) wrapper with support for long paths on Windows. | ||
341 | |||
342 | Availability: Windows, Unix. | ||
343 | """ | ||
344 | return os.listdir(_makelongpath(path)) | ||
345 | |||
346 | |||
347 | def rmdir(path): | ||
348 | """os.rmdir(path) wrapper with support for long paths on Windows. | ||
349 | |||
350 | Availability: Windows, Unix. | ||
351 | """ | ||
352 | os.rmdir(_makelongpath(path)) | ||
353 | |||
354 | |||
355 | def isdir(path): | ||
356 | """os.path.isdir(path) wrapper with support for long paths on Windows. | ||
357 | |||
358 | Availability: Windows, Unix. | ||
359 | """ | ||
360 | return os.path.isdir(_makelongpath(path)) | ||
361 | |||
362 | |||
269 | def islink(path): | 363 | def islink(path): |
270 | """Test whether a path is a symbolic link. | 364 | """os.path.islink(path) wrapper with support for long paths on Windows. |
271 | 365 | ||
272 | Availability: Windows, Unix. | 366 | Availability: Windows, Unix. |
273 | """ | 367 | """ |
274 | if isWindows(): | 368 | if isWindows(): |
275 | import platform_utils_win32 | 369 | import platform_utils_win32 |
276 | return platform_utils_win32.islink(path) | 370 | return platform_utils_win32.islink(_makelongpath(path)) |
277 | else: | 371 | else: |
278 | return os.path.islink(path) | 372 | return os.path.islink(path) |
279 | 373 | ||
@@ -288,7 +382,7 @@ def readlink(path): | |||
288 | """ | 382 | """ |
289 | if isWindows(): | 383 | if isWindows(): |
290 | import platform_utils_win32 | 384 | import platform_utils_win32 |
291 | return platform_utils_win32.readlink(path) | 385 | return platform_utils_win32.readlink(_makelongpath(path)) |
292 | else: | 386 | else: |
293 | return os.readlink(path) | 387 | return os.readlink(path) |
294 | 388 | ||
@@ -103,7 +103,7 @@ def _ProjectHooks(): | |||
103 | if _project_hook_list is None: | 103 | if _project_hook_list is None: |
104 | d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__))) | 104 | d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__))) |
105 | d = os.path.join(d, 'hooks') | 105 | d = os.path.join(d, 'hooks') |
106 | _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)] | 106 | _project_hook_list = [os.path.join(d, x) for x in platform_utils.listdir(d)] |
107 | return _project_hook_list | 107 | return _project_hook_list |
108 | 108 | ||
109 | 109 | ||
@@ -253,7 +253,7 @@ class _CopyFile(object): | |||
253 | platform_utils.remove(dest) | 253 | platform_utils.remove(dest) |
254 | else: | 254 | else: |
255 | dest_dir = os.path.dirname(dest) | 255 | dest_dir = os.path.dirname(dest) |
256 | if not os.path.isdir(dest_dir): | 256 | if not platform_utils.isdir(dest_dir): |
257 | os.makedirs(dest_dir) | 257 | os.makedirs(dest_dir) |
258 | shutil.copy(src, dest) | 258 | shutil.copy(src, dest) |
259 | # make the file read-only | 259 | # make the file read-only |
@@ -282,7 +282,7 @@ class _LinkFile(object): | |||
282 | platform_utils.remove(absDest) | 282 | platform_utils.remove(absDest) |
283 | else: | 283 | else: |
284 | dest_dir = os.path.dirname(absDest) | 284 | dest_dir = os.path.dirname(absDest) |
285 | if not os.path.isdir(dest_dir): | 285 | if not platform_utils.isdir(dest_dir): |
286 | os.makedirs(dest_dir) | 286 | os.makedirs(dest_dir) |
287 | platform_utils.symlink(relSrc, absDest) | 287 | platform_utils.symlink(relSrc, absDest) |
288 | except IOError: | 288 | except IOError: |
@@ -302,7 +302,7 @@ class _LinkFile(object): | |||
302 | else: | 302 | else: |
303 | # Entity doesn't exist assume there is a wild card | 303 | # Entity doesn't exist assume there is a wild card |
304 | absDestDir = self.abs_dest | 304 | absDestDir = self.abs_dest |
305 | if os.path.exists(absDestDir) and not os.path.isdir(absDestDir): | 305 | if os.path.exists(absDestDir) and not platform_utils.isdir(absDestDir): |
306 | _error('Link error: src with wildcard, %s must be a directory', | 306 | _error('Link error: src with wildcard, %s must be a directory', |
307 | absDestDir) | 307 | absDestDir) |
308 | else: | 308 | else: |
@@ -750,7 +750,7 @@ class Project(object): | |||
750 | 750 | ||
751 | @property | 751 | @property |
752 | def Exists(self): | 752 | def Exists(self): |
753 | return os.path.isdir(self.gitdir) and os.path.isdir(self.objdir) | 753 | return platform_utils.isdir(self.gitdir) and platform_utils.isdir(self.objdir) |
754 | 754 | ||
755 | @property | 755 | @property |
756 | def CurrentBranch(self): | 756 | def CurrentBranch(self): |
@@ -931,7 +931,7 @@ class Project(object): | |||
931 | quiet: If True then only print the project name. Do not print | 931 | quiet: If True then only print the project name. Do not print |
932 | the modified files, branch name, etc. | 932 | the modified files, branch name, etc. |
933 | """ | 933 | """ |
934 | if not os.path.isdir(self.worktree): | 934 | if not platform_utils.isdir(self.worktree): |
935 | if output_redir is None: | 935 | if output_redir is None: |
936 | output_redir = sys.stdout | 936 | output_redir = sys.stdout |
937 | print(file=output_redir) | 937 | print(file=output_redir) |
@@ -2510,7 +2510,7 @@ class Project(object): | |||
2510 | 2510 | ||
2511 | to_copy = [] | 2511 | to_copy = [] |
2512 | if copy_all: | 2512 | if copy_all: |
2513 | to_copy = os.listdir(gitdir) | 2513 | to_copy = platform_utils.listdir(gitdir) |
2514 | 2514 | ||
2515 | dotgit = platform_utils.realpath(dotgit) | 2515 | dotgit = platform_utils.realpath(dotgit) |
2516 | for name in set(to_copy).union(to_symlink): | 2516 | for name in set(to_copy).union(to_symlink): |
@@ -2529,7 +2529,7 @@ class Project(object): | |||
2529 | platform_utils.symlink( | 2529 | platform_utils.symlink( |
2530 | os.path.relpath(src, os.path.dirname(dst)), dst) | 2530 | os.path.relpath(src, os.path.dirname(dst)), dst) |
2531 | elif copy_all and not platform_utils.islink(dst): | 2531 | elif copy_all and not platform_utils.islink(dst): |
2532 | if os.path.isdir(src): | 2532 | if platform_utils.isdir(src): |
2533 | shutil.copytree(src, dst) | 2533 | shutil.copytree(src, dst) |
2534 | elif os.path.isfile(src): | 2534 | elif os.path.isfile(src): |
2535 | shutil.copy(src, dst) | 2535 | shutil.copy(src, dst) |
diff --git a/subcmds/status.py b/subcmds/status.py index b47c8736..773f22d4 100644 --- a/subcmds/status.py +++ b/subcmds/status.py | |||
@@ -26,6 +26,7 @@ import itertools | |||
26 | import os | 26 | import os |
27 | 27 | ||
28 | from color import Coloring | 28 | from color import Coloring |
29 | import platform_utils | ||
29 | 30 | ||
30 | class Status(PagedCommand): | 31 | class Status(PagedCommand): |
31 | common = True | 32 | common = True |
@@ -115,7 +116,7 @@ the following meanings: | |||
115 | """find 'dirs' that are present in 'proj_dirs_parents' but not in 'proj_dirs'""" | 116 | """find 'dirs' that are present in 'proj_dirs_parents' but not in 'proj_dirs'""" |
116 | status_header = ' --\t' | 117 | status_header = ' --\t' |
117 | for item in dirs: | 118 | for item in dirs: |
118 | if not os.path.isdir(item): | 119 | if not platform_utils.isdir(item): |
119 | outstring.append(''.join([status_header, item])) | 120 | outstring.append(''.join([status_header, item])) |
120 | continue | 121 | continue |
121 | if item in proj_dirs: | 122 | if item in proj_dirs: |
diff --git a/subcmds/sync.py b/subcmds/sync.py index 943a0264..f6bd983d 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py | |||
@@ -474,8 +474,8 @@ later is required to fix a server side protocol bug. | |||
474 | # so rmtree works. | 474 | # so rmtree works. |
475 | try: | 475 | try: |
476 | platform_utils.rmtree(os.path.join(path, '.git')) | 476 | platform_utils.rmtree(os.path.join(path, '.git')) |
477 | except OSError: | 477 | except OSError as e: |
478 | print('Failed to remove %s' % os.path.join(path, '.git'), file=sys.stderr) | 478 | print('Failed to remove %s (%s)' % (os.path.join(path, '.git'), str(e)), file=sys.stderr) |
479 | print('error: Failed to delete obsolete path %s' % path, file=sys.stderr) | 479 | print('error: Failed to delete obsolete path %s' % path, file=sys.stderr) |
480 | print(' remove manually, then run sync again', file=sys.stderr) | 480 | print(' remove manually, then run sync again', file=sys.stderr) |
481 | return -1 | 481 | return -1 |
@@ -484,12 +484,12 @@ later is required to fix a server side protocol bug. | |||
484 | # another git project | 484 | # another git project |
485 | dirs_to_remove = [] | 485 | dirs_to_remove = [] |
486 | failed = False | 486 | failed = False |
487 | for root, dirs, files in os.walk(path): | 487 | for root, dirs, files in platform_utils.walk(path): |
488 | for f in files: | 488 | for f in files: |
489 | try: | 489 | try: |
490 | platform_utils.remove(os.path.join(root, f)) | 490 | platform_utils.remove(os.path.join(root, f)) |
491 | except OSError: | 491 | except OSError as e: |
492 | print('Failed to remove %s' % os.path.join(root, f), file=sys.stderr) | 492 | print('Failed to remove %s (%s)' % (os.path.join(root, f), str(e)), file=sys.stderr) |
493 | failed = True | 493 | failed = True |
494 | dirs[:] = [d for d in dirs | 494 | dirs[:] = [d for d in dirs |
495 | if not os.path.lexists(os.path.join(root, d, '.git'))] | 495 | if not os.path.lexists(os.path.join(root, d, '.git'))] |
@@ -499,14 +499,14 @@ later is required to fix a server side protocol bug. | |||
499 | if platform_utils.islink(d): | 499 | if platform_utils.islink(d): |
500 | try: | 500 | try: |
501 | platform_utils.remove(d) | 501 | platform_utils.remove(d) |
502 | except OSError: | 502 | except OSError as e: |
503 | print('Failed to remove %s' % os.path.join(root, d), file=sys.stderr) | 503 | print('Failed to remove %s (%s)' % (os.path.join(root, d), str(e)), file=sys.stderr) |
504 | failed = True | 504 | failed = True |
505 | elif len(os.listdir(d)) == 0: | 505 | elif len(platform_utils.listdir(d)) == 0: |
506 | try: | 506 | try: |
507 | os.rmdir(d) | 507 | platform_utils.rmdir(d) |
508 | except OSError: | 508 | except OSError as e: |
509 | print('Failed to remove %s' % os.path.join(root, d), file=sys.stderr) | 509 | print('Failed to remove %s (%s)' % (os.path.join(root, d), str(e)), file=sys.stderr) |
510 | failed = True | 510 | failed = True |
511 | continue | 511 | continue |
512 | if failed: | 512 | if failed: |
@@ -517,8 +517,8 @@ later is required to fix a server side protocol bug. | |||
517 | # Try deleting parent dirs if they are empty | 517 | # Try deleting parent dirs if they are empty |
518 | project_dir = path | 518 | project_dir = path |
519 | while project_dir != self.manifest.topdir: | 519 | while project_dir != self.manifest.topdir: |
520 | if len(os.listdir(project_dir)) == 0: | 520 | if len(platform_utils.listdir(project_dir)) == 0: |
521 | os.rmdir(project_dir) | 521 | platform_utils.rmdir(project_dir) |
522 | else: | 522 | else: |
523 | break | 523 | break |
524 | project_dir = os.path.dirname(project_dir) | 524 | project_dir = os.path.dirname(project_dir) |