summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/tools/manifest/tree.py
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/tools/manifest/tree.py')
-rw-r--r--testing/web-platform/tests/tools/manifest/tree.py168
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