diff options
Diffstat (limited to 'project.py')
-rwxr-xr-x | project.py | 146 |
1 files changed, 108 insertions, 38 deletions
@@ -36,7 +36,7 @@ from git_command import GitCommand, git_require | |||
36 | from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \ | 36 | from git_config import GitConfig, IsId, GetSchemeFromUrl, GetUrlCookieFile, \ |
37 | ID_RE | 37 | ID_RE |
38 | from error import GitError, HookError, UploadError, DownloadError | 38 | from error import GitError, HookError, UploadError, DownloadError |
39 | from error import ManifestInvalidRevisionError | 39 | from error import ManifestInvalidRevisionError, ManifestInvalidPathError |
40 | from error import NoManifestException | 40 | from error import NoManifestException |
41 | import platform_utils | 41 | import platform_utils |
42 | import progress | 42 | import progress |
@@ -261,17 +261,70 @@ class _Annotation(object): | |||
261 | self.keep = keep | 261 | self.keep = keep |
262 | 262 | ||
263 | 263 | ||
264 | def _SafeExpandPath(base, subpath, skipfinal=False): | ||
265 | """Make sure |subpath| is completely safe under |base|. | ||
266 | |||
267 | We make sure no intermediate symlinks are traversed, and that the final path | ||
268 | is not a special file (e.g. not a socket or fifo). | ||
269 | |||
270 | NB: We rely on a number of paths already being filtered out while parsing the | ||
271 | manifest. See the validation logic in manifest_xml.py for more details. | ||
272 | """ | ||
273 | components = subpath.split(os.path.sep) | ||
274 | if skipfinal: | ||
275 | # Whether the caller handles the final component itself. | ||
276 | finalpart = components.pop() | ||
277 | |||
278 | path = base | ||
279 | for part in components: | ||
280 | if part in {'.', '..'}: | ||
281 | raise ManifestInvalidPathError( | ||
282 | '%s: "%s" not allowed in paths' % (subpath, part)) | ||
283 | |||
284 | path = os.path.join(path, part) | ||
285 | if platform_utils.islink(path): | ||
286 | raise ManifestInvalidPathError( | ||
287 | '%s: traversing symlinks not allow' % (path,)) | ||
288 | |||
289 | if os.path.exists(path): | ||
290 | if not os.path.isfile(path) and not platform_utils.isdir(path): | ||
291 | raise ManifestInvalidPathError( | ||
292 | '%s: only regular files & directories allowed' % (path,)) | ||
293 | |||
294 | if skipfinal: | ||
295 | path = os.path.join(path, finalpart) | ||
296 | |||
297 | return path | ||
298 | |||
299 | |||
264 | class _CopyFile(object): | 300 | class _CopyFile(object): |
301 | """Container for <copyfile> manifest element.""" | ||
302 | |||
303 | def __init__(self, git_worktree, src, topdir, dest): | ||
304 | """Register a <copyfile> request. | ||
265 | 305 | ||
266 | def __init__(self, src, dest, abssrc, absdest): | 306 | Args: |
307 | git_worktree: Absolute path to the git project checkout. | ||
308 | src: Relative path under |git_worktree| of file to read. | ||
309 | topdir: Absolute path to the top of the repo client checkout. | ||
310 | dest: Relative path under |topdir| of file to write. | ||
311 | """ | ||
312 | self.git_worktree = git_worktree | ||
313 | self.topdir = topdir | ||
267 | self.src = src | 314 | self.src = src |
268 | self.dest = dest | 315 | self.dest = dest |
269 | self.abs_src = abssrc | ||
270 | self.abs_dest = absdest | ||
271 | 316 | ||
272 | def _Copy(self): | 317 | def _Copy(self): |
273 | src = self.abs_src | 318 | src = _SafeExpandPath(self.git_worktree, self.src) |
274 | dest = self.abs_dest | 319 | dest = _SafeExpandPath(self.topdir, self.dest) |
320 | |||
321 | if platform_utils.isdir(src): | ||
322 | raise ManifestInvalidPathError( | ||
323 | '%s: copying from directory not supported' % (self.src,)) | ||
324 | if platform_utils.isdir(dest): | ||
325 | raise ManifestInvalidPathError( | ||
326 | '%s: copying to directory not allowed' % (self.dest,)) | ||
327 | |||
275 | # copy file if it does not exist or is out of date | 328 | # copy file if it does not exist or is out of date |
276 | if not os.path.exists(dest) or not filecmp.cmp(src, dest): | 329 | if not os.path.exists(dest) or not filecmp.cmp(src, dest): |
277 | try: | 330 | try: |
@@ -292,13 +345,21 @@ class _CopyFile(object): | |||
292 | 345 | ||
293 | 346 | ||
294 | class _LinkFile(object): | 347 | class _LinkFile(object): |
348 | """Container for <linkfile> manifest element.""" | ||
295 | 349 | ||
296 | def __init__(self, git_worktree, src, dest, relsrc, absdest): | 350 | def __init__(self, git_worktree, src, topdir, dest): |
351 | """Register a <linkfile> request. | ||
352 | |||
353 | Args: | ||
354 | git_worktree: Absolute path to the git project checkout. | ||
355 | src: Target of symlink relative to path under |git_worktree|. | ||
356 | topdir: Absolute path to the top of the repo client checkout. | ||
357 | dest: Relative path under |topdir| of symlink to create. | ||
358 | """ | ||
297 | self.git_worktree = git_worktree | 359 | self.git_worktree = git_worktree |
360 | self.topdir = topdir | ||
298 | self.src = src | 361 | self.src = src |
299 | self.dest = dest | 362 | self.dest = dest |
300 | self.src_rel_to_dest = relsrc | ||
301 | self.abs_dest = absdest | ||
302 | 363 | ||
303 | def __linkIt(self, relSrc, absDest): | 364 | def __linkIt(self, relSrc, absDest): |
304 | # link file if it does not exist or is out of date | 365 | # link file if it does not exist or is out of date |
@@ -316,35 +377,37 @@ class _LinkFile(object): | |||
316 | _error('Cannot link file %s to %s', relSrc, absDest) | 377 | _error('Cannot link file %s to %s', relSrc, absDest) |
317 | 378 | ||
318 | def _Link(self): | 379 | def _Link(self): |
319 | """Link the self.rel_src_to_dest and self.abs_dest. Handles wild cards | 380 | """Link the self.src & self.dest paths. |
320 | on the src linking all of the files in the source in to the destination | 381 | |
321 | directory. | 382 | Handles wild cards on the src linking all of the files in the source in to |
383 | the destination directory. | ||
322 | """ | 384 | """ |
323 | # We use the absSrc to handle the situation where the current directory | 385 | src = _SafeExpandPath(self.git_worktree, self.src) |
324 | # is not the root of the repo | 386 | |
325 | absSrc = os.path.join(self.git_worktree, self.src) | 387 | if os.path.exists(src): |
326 | if os.path.exists(absSrc): | 388 | # Entity exists so just a simple one to one link operation. |
327 | # Entity exists so just a simple one to one link operation | 389 | dest = _SafeExpandPath(self.topdir, self.dest, skipfinal=True) |
328 | self.__linkIt(self.src_rel_to_dest, self.abs_dest) | 390 | # dest & src are absolute paths at this point. Make sure the target of |
391 | # the symlink is relative in the context of the repo client checkout. | ||
392 | relpath = os.path.relpath(src, os.path.dirname(dest)) | ||
393 | self.__linkIt(relpath, dest) | ||
329 | else: | 394 | else: |
395 | dest = _SafeExpandPath(self.topdir, self.dest) | ||
330 | # Entity doesn't exist assume there is a wild card | 396 | # Entity doesn't exist assume there is a wild card |
331 | absDestDir = self.abs_dest | 397 | if os.path.exists(dest) and not platform_utils.isdir(dest): |
332 | if os.path.exists(absDestDir) and not platform_utils.isdir(absDestDir): | 398 | _error('Link error: src with wildcard, %s must be a directory', dest) |
333 | _error('Link error: src with wildcard, %s must be a directory', | ||
334 | absDestDir) | ||
335 | else: | 399 | else: |
336 | absSrcFiles = glob.glob(absSrc) | 400 | for absSrcFile in glob.glob(src): |
337 | for absSrcFile in absSrcFiles: | ||
338 | # Create a releative path from source dir to destination dir | 401 | # Create a releative path from source dir to destination dir |
339 | absSrcDir = os.path.dirname(absSrcFile) | 402 | absSrcDir = os.path.dirname(absSrcFile) |
340 | relSrcDir = os.path.relpath(absSrcDir, absDestDir) | 403 | relSrcDir = os.path.relpath(absSrcDir, dest) |
341 | 404 | ||
342 | # Get the source file name | 405 | # Get the source file name |
343 | srcFile = os.path.basename(absSrcFile) | 406 | srcFile = os.path.basename(absSrcFile) |
344 | 407 | ||
345 | # Now form the final full paths to srcFile. They will be | 408 | # Now form the final full paths to srcFile. They will be |
346 | # absolute for the desintaiton and relative for the srouce. | 409 | # absolute for the desintaiton and relative for the srouce. |
347 | absDest = os.path.join(absDestDir, srcFile) | 410 | absDest = os.path.join(dest, srcFile) |
348 | relSrc = os.path.join(relSrcDir, srcFile) | 411 | relSrc = os.path.join(relSrcDir, srcFile) |
349 | self.__linkIt(relSrc, absDest) | 412 | self.__linkIt(relSrc, absDest) |
350 | 413 | ||
@@ -1712,18 +1775,25 @@ class Project(object): | |||
1712 | if submodules: | 1775 | if submodules: |
1713 | syncbuf.later1(self, _dosubmodules) | 1776 | syncbuf.later1(self, _dosubmodules) |
1714 | 1777 | ||
1715 | def AddCopyFile(self, src, dest, absdest): | 1778 | def AddCopyFile(self, src, dest, topdir): |
1716 | # dest should already be an absolute path, but src is project relative | 1779 | """Mark |src| for copying to |dest| (relative to |topdir|). |
1717 | # make src an absolute path | 1780 | |
1718 | abssrc = os.path.join(self.worktree, src) | 1781 | No filesystem changes occur here. Actual copying happens later on. |
1719 | self.copyfiles.append(_CopyFile(src, dest, abssrc, absdest)) | 1782 | |
1720 | 1783 | Paths should have basic validation run on them before being queued. | |
1721 | def AddLinkFile(self, src, dest, absdest): | 1784 | Further checking will be handled when the actual copy happens. |
1722 | # dest should already be an absolute path, but src is project relative | 1785 | """ |
1723 | # make src relative path to dest | 1786 | self.copyfiles.append(_CopyFile(self.worktree, src, topdir, dest)) |
1724 | absdestdir = os.path.dirname(absdest) | 1787 | |
1725 | relsrc = os.path.relpath(os.path.join(self.worktree, src), absdestdir) | 1788 | def AddLinkFile(self, src, dest, topdir): |
1726 | self.linkfiles.append(_LinkFile(self.worktree, src, dest, relsrc, absdest)) | 1789 | """Mark |dest| to create a symlink (relative to |topdir|) pointing to |src|. |
1790 | |||
1791 | No filesystem changes occur here. Actual linking happens later on. | ||
1792 | |||
1793 | Paths should have basic validation run on them before being queued. | ||
1794 | Further checking will be handled when the actual link happens. | ||
1795 | """ | ||
1796 | self.linkfiles.append(_LinkFile(self.worktree, src, topdir, dest)) | ||
1727 | 1797 | ||
1728 | def AddAnnotation(self, name, value, keep): | 1798 | def AddAnnotation(self, name, value, keep): |
1729 | self.annotations.append(_Annotation(name, value, keep)) | 1799 | self.annotations.append(_Annotation(name, value, keep)) |