diff options
Diffstat (limited to 'testing/web-platform/tests/tools/manifest/tree.py')
-rw-r--r-- | testing/web-platform/tests/tools/manifest/tree.py | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/testing/web-platform/tests/tools/manifest/tree.py b/testing/web-platform/tests/tools/manifest/tree.py new file mode 100644 index 000000000..25a5f212f --- /dev/null +++ b/testing/web-platform/tests/tools/manifest/tree.py @@ -0,0 +1,168 @@ +import os +from six.moves import cStringIO as StringIO +from fnmatch import fnmatch + +from . import vcs +from .log import get_logger +from .utils import is_blacklisted, rel_path_to_url + +def chunks(data, n): + for i in range(0, len(data) - 1, n): + yield data[i:i+n] + +class TestTree(object): + def __init__(self, tests_root, url_base): + self.tests_root = tests_root + self.url_base = url_base + self.logger = get_logger() + + def current_rev(self): + pass + + def local_changes(self): + pass + + def committed_changes(self, base_rev=None): + pass + + +class GitTree(TestTree): + def __init__(self, tests_root, url_base): + TestTree.__init__(self, tests_root, url_base) + self.git = self.setup_git() + + def setup_git(self): + assert vcs.is_git_repo(self.tests_root) + return vcs.get_git_func(self.tests_root) + + def current_rev(self): + return self.git("rev-parse", "HEAD").strip() + + def local_changes(self, path=None): + # -z is stable like --porcelain; see the git status documentation for details + cmd = ["status", "-z", "--ignore-submodules=all"] + if path is not None: + cmd.extend(["--", path]) + + rv = {} + + data = self.git(*cmd) + if data == "": + return rv + + assert data[-1] == "\0" + f = StringIO(data) + + while f.tell() < len(data): + # First two bytes are the status in the stage (index) and working tree, respectively + staged = f.read(1) + worktree = f.read(1) + assert f.read(1) == " " + + if staged == "R": + # When a file is renamed, there are two files, the source and the destination + files = 2 + else: + files = 1 + + filenames = [] + + for i in range(files): + filenames.append("") + char = f.read(1) + while char != "\0": + filenames[-1] += char + char = f.read(1) + + if not is_blacklisted(rel_path_to_url(filenames[0], self.url_base)): + rv.update(self.local_status(staged, worktree, filenames)) + + return rv + + def committed_changes(self, base_rev=None): + if base_rev is None: + self.logger.debug("Adding all changesets to the manifest") + return [(item, "modified") for item in self.paths()] + + self.logger.debug("Updating the manifest from %s to %s" % (base_rev, self.current_rev())) + rv = [] + data = self.git("diff", "-z", "--name-status", base_rev + "..HEAD") + items = data.split("\0") + for status, filename in chunks(items, 2): + if is_blacklisted(rel_path_to_url(filename, self.url_base)): + continue + if status == "D": + rv.append((filename, "deleted")) + else: + rv.append((filename, "modified")) + return rv + + def paths(self): + data = self.git("ls-tree", "--name-only", "--full-tree", "-r", "HEAD") + return [item for item in data.split("\n") if not item.endswith(os.path.sep)] + + def local_status(self, staged, worktree, filenames): + # Convert the complex range of statuses that git can have to two values + # we care about; "modified" and "deleted" and return a dictionary mapping + # filenames to statuses + + rv = {} + + if (staged, worktree) in [("D", "D"), ("A", "U"), ("U", "D"), ("U", "A"), + ("D", "U"), ("A", "A"), ("U", "U")]: + raise Exception("Can't operate on tree containing unmerged paths") + + if staged == "R": + assert len(filenames) == 2 + dest, src = filenames + rv[dest] = "modified" + rv[src] = "deleted" + else: + assert len(filenames) == 1 + + filename = filenames[0] + + if staged == "D" or worktree == "D": + # Actually if something is deleted in the index but present in the worktree + # it will get included by having a status of both "D " and "??". + # It isn't clear whether that's a bug + rv[filename] = "deleted" + elif staged == "?" and worktree == "?": + # A new file. If it's a directory, recurse into it + if os.path.isdir(os.path.join(self.tests_root, filename)): + if filename[-1] != '/': + filename += '/' + rv.update(self.local_changes(filename)) + else: + rv[filename] = "modified" + else: + rv[filename] = "modified" + + return rv + +class NoVCSTree(TestTree): + """Subclass that doesn't depend on git""" + + ignore = ["*.py[c|0]", "*~", "#*"] + + def current_rev(self): + return None + + def local_changes(self): + # Put all files into local_changes and rely on Manifest.update to de-dupe + # changes that in fact committed at the base rev. + + rv = [] + for dir_path, dir_names, filenames in os.walk(self.tests_root): + for filename in filenames: + if any(fnmatch(filename, pattern) for pattern in self.ignore): + continue + rel_path = os.path.relpath(os.path.join(dir_path, filename), + self.tests_root) + if is_blacklisted(rel_path_to_url(rel_path, self.url_base)): + continue + rv.append((rel_path, "modified")) + return dict(rv) + + def committed_changes(self, base_rev=None): + return None |