#! /usr/bin/env python3.3 """Path utilities broken out of p4gf_util's kitchen sink.""" import os import p4gf_const from p4gf_l10n import NTR def _cwd_to_dot_git(): """If cwd or one of its ancestors is a .git return that .git directory. If not, return None. """ path = os.getcwd() while path: (path2, tail) = os.path.split(path) if path2 == path: # Give up once split() stops changing the path: we've hit root. break path = path2 if tail == '.git': return os.path.join(path, tail) return None def cwd_to_repo_name(): """Derive the repo name from the current working directory's path. It's the 'foo' in 'foo/.git/'. """ # Fall back to using directory name as repo name. path = _cwd_to_dot_git() if path: git_path = os.path.dirname(path) repo_path = os.path.dirname(git_path) repo_name = os.path.basename(repo_path) return repo_name return None def find_ancestor(path, ancestor): """Walk up the path until you find a dir named 'ancestor', returning the path. Return None if no ancestor called 'ancestor'. """ path = path while path: (path2, tail) = os.path.split(path) if path2 == path: # Give up once split() stops changing the path: we've hit root. break if tail == ancestor: return path path = path2 return None def strip_trailing_delimiter(path): """Remove trailing /.""" if path.endswith('/'): return path[:-1] return path def strip_leading_delimiter(path): """Remove initial /. NOP if starts with // depot path prefix. """ if path.startswith("//"): return path if path.startswith('/'): return path[1:] return path def join(a, b): """Return "a/b". If either a or b is empty, return only a or b (whichever non-empty). """ if a and b: return strip_trailing_delimiter(a) + '/' + strip_leading_delimiter(b) elif b: return b else: return a def join_non_empty(grout, a, b): """Return a + grout + b, using grout only if both a and b.""" if a and b: return a + grout + b if a: return a return b def force_trailing_delimiter(path): """Make sure path ends with /.""" if not path.endswith('/'): return path + '/' else: return path def dequote(path): """Strip leading and trailing double-quotes if both present, NOP if not.""" if (2 <= len(path)) and path.startswith('"') and path.endswith('"'): return path[1:-1] return path def enquote(path): """Path with space char requires double-quotes, all others pass through unchanged.""" if ' ' not in path: return path # Already enquoted? return unchanged. if 2 <= len(path) and path[0] == path[-1] == '"': return path return '"' + path + '"' def dir_path_iter(file_path): """Iterator/generator that yields each directory path. //depot/main/bob/file.txt ==> //depot/main/bob //depot/main //depot """ rf = file_path.rfind('/', 0, len(file_path)) while 0 < rf: # Special case for initial "//" to not produce "/". if rf == 1 and file_path[0] == '/': break yield file_path[:rf] rf = file_path.rfind('/', 0, rf - 1) def greatest_common_dir(path_list): """Return the longest path ending in "/" that is a prefix for every path in the list. Returned string includes that trailing "/". Return empty string if no common directory. """ common = os.path.commonprefix(list(path_list)) # Stop at internal wildcards. for wildcard in ["*", "%%", "..."]: i = common.find(wildcard) if 0 <= i: common = common[:i] i = common.rfind("/") if i < 0: return "" else: # +1 to include trailing /. Need that # to avoid matching "dir2/" with "dir/". return common[:i+1] def slashify_sha1(sha1): """Convert a SHA1 to the path form for use in Perforce. For instance, 60eaf72224a34f592636271fa957b6c4acaee5f3 becomes 60/ea/f72224a34f592636271fa957b6c4acaee5f3 which can then be used to build a file path. """ if sha1 == '*': return '*/*/*' return sha1[:2] + "/" + sha1[2:4] + "/" + sha1[4:] def slashify_blob_sha1(sha1): """Convert a SHA1 to the path form for use in Perforce. This splits the value into four levels, suitable for storing a large number of objects, such as blobs. For instance, 60eaf72224a34f592636271fa957b6c4acaee5f3 becomes 60/ea/f7/22/24a34f592636271fa957b6c4acaee5f3 which can then be used to build a file path. """ if sha1 == '*': return '*/*/*/*/*' return os.path.join(sha1[:2], sha1[2:4], sha1[4:6], sha1[6:8], sha1[8:]) def tree_p4_path(tree_sha1): """Return depot path to a tree.""" return (NTR('{objects_root}/trees/{slashed}') .format(objects_root=p4gf_const.objects_root(), slashed=slashify_sha1(tree_sha1))) def blob_p4_path(blob_sha1): """Return depot path to a blob.""" return (NTR('{objects_root}/blobs/{slashed}') .format(objects_root=p4gf_const.objects_root(), slashed=slashify_blob_sha1(blob_sha1)))