Bug 1319943 - Prototype |mach owners| command that gives information about file ownership
MozReview-Commit-ID: BjeyOPsfscu
--- a/build/mach_bootstrap.py
+++ b/build/mach_bootstrap.py
@@ -34,16 +34,17 @@ Press ENTER/RETURN to continue or CTRL+c
# TODO Bug 794506 Integrate with the in-tree virtualenv configuration.
SEARCH_PATHS = [
'python/mach',
'python/mozboot',
'python/mozbuild',
'python/mozlint',
'python/mozversioncontrol',
+ 'python/owners',
'python/blessings',
'python/compare-locales',
'python/configobj',
'python/futures',
'python/jsmin',
'python/psutil',
'python/pylru',
'python/which',
@@ -110,16 +111,17 @@ MACH_MODULES = [
'python/mach/mach/commands/commandinfo.py',
'python/mach/mach/commands/settings.py',
'python/compare-locales/mach_commands.py',
'python/mozboot/mozboot/mach_commands.py',
'python/mozbuild/mozbuild/mach_commands.py',
'python/mozbuild/mozbuild/backend/mach_commands.py',
'python/mozbuild/mozbuild/compilation/codecomplete.py',
'python/mozbuild/mozbuild/frontend/mach_commands.py',
+ 'python/owners/mach_commands.py',
'services/common/tests/mach_commands.py',
'taskcluster/mach_commands.py',
'testing/firefox-ui/mach_commands.py',
'testing/mach_commands.py',
'testing/marionette/mach_commands.py',
'testing/mochitest/mach_commands.py',
'testing/mozharness/mach_commands.py',
'testing/talos/mach_commands.py',
new file mode 100644
--- /dev/null
+++ b/python/owners/mach_commands.py
@@ -0,0 +1,49 @@
+import os
+import sys
+
+from mozbuild.base import (
+ MachCommandBase,
+ MozbuildObject,
+)
+
+from mach.decorators import (
+ CommandProvider,
+ Command,
+)
+
+
+class OwnersLookup(MozbuildObject):
+ def run(self, **kwargs):
+ import owners
+ for path in kwargs["paths"]:
+ users, can_upstream = owners.get_owner(path, root=self.topsrcdir)
+
+ print("[%s]" % path)
+ if users:
+ for user in users:
+ print(" %s" % user)
+ else:
+ print(" No OWNERS")
+
+ if not can_upstream:
+ print(" WARNING: path is vendored and patches cannot currently be upstreamed")
+
+
+def create_parser():
+ import argparse
+ p = argparse.ArgumentParser()
+ p.add_argument("paths", nargs="+", help="Paths to check ownership of")
+ return p
+
+
+@CommandProvider
+class MachCommands(MachCommandBase):
+ def setup(self):
+ self._activate_virtualenv()
+
+ @Command("owners",
+ category="devenv",
+ parser=create_parser)
+ def run(self, **params):
+ lookup = self._spawn(OwnersLookup)
+ lookup.run(**params)
new file mode 100644
--- /dev/null
+++ b/python/owners/owners.py
@@ -0,0 +1,101 @@
+# include:
+# - jgraham
+# - Ms2ger
+# exclude:
+# - jgriffin
+# paths:
+# tests:
+# vendor: true
+# paths:
+# dom:
+# include:
+# - annevk
+# exclude:
+# - jgraham
+# dom/events:
+# include:
+# - smaug
+# mach_commands.py:
+# exclude:
+# - Ms2ger
+
+import os
+import sys
+
+import yaml
+
+
+def get_owner(path, root):
+ rel_path = os.path.relpath(path, root)
+ owners = set()
+ can_upstream = True
+ if ".." in rel_path:
+ return owners
+
+ components = (os.path.sep + rel_path).split(os.path.sep)
+ test_path = ""
+ for i, part in enumerate(components):
+ test_path = os.path.join(test_path, part)
+ if os.path.isfile(test_path):
+ break
+
+ filename = os.path.join(test_path, "OWNERS.yml")
+ try:
+ with open(filename) as f:
+ try:
+ data = yaml.load(f)
+ except:
+ sys.stderr.write("Invalid yaml file %s\n" % filename)
+ sys.exit(1)
+ except IOError:
+ continue
+ if not isinstance(data, dict):
+ sys.stderr.write("Invalid yaml file %s\n" % filename)
+ sys.exit(1)
+
+ owners |= set(data.get("include", []))
+ owners -= set(data.get("exclude", []))
+
+ next_component = components[i+1] if i < len(components) - 1 else None
+ if next_component and "paths" in data:
+ if next_component in data["paths"]:
+ path_data = data["paths"][next_component]
+ if path_data.get("vendor"):
+ if not path_data.get("upstream", True):
+ can_upstream = False
+ process_vendored_path(path,
+ os.path.join(test_path, next_component),
+ path_data,
+ owners)
+ break
+ else:
+ owners |= set(path_data.get("include", []))
+ owners -= set(path_data.get("exclude", []))
+
+ return owners, can_upstream
+
+
+def process_vendored_path(path, base_path, vendor_data, owners):
+ path_data = {"paths": {}}
+ for sub_path, data in vendor_data.get("paths", {}).iteritems():
+ path_components = sub_path.split("/")
+ target = path_data
+ for i, component in enumerate(path_components):
+ if component not in target["paths"]:
+ target["paths"][component] = {"paths": {},
+ "include": set(),
+ "exclude": set()}
+ if i == len(path_components) - 1:
+ for rule in ["include", "exclude"]:
+ target["paths"][component][rule] = set(data.get(rule, []))
+ else:
+ target = target["paths"][component]
+ components = os.path.relpath(path, base_path).split(os.path.sep)
+ target = path_data
+ for component in components:
+ if "paths" in target and component in target["paths"]:
+ target = target["paths"][component]
+ owners |= target["include"]
+ owners -= target["exclude"]
+ else:
+ break
new file mode 100644
--- /dev/null
+++ b/testing/OWNERS.yml
@@ -0,0 +1,2 @@
+include:
+ - jgriffin
new file mode 100644
--- /dev/null
+++ b/testing/marionette/OWNERS.yml
@@ -0,0 +1,3 @@
+include:
+ - AutomatedTester
+ - ato
new file mode 100644
--- /dev/null
+++ b/testing/marionette/client/marionette_driver/OWNERS.yml
@@ -0,0 +1,4 @@
+paths:
+ marionette.py:
+ include:
+ - maja_zf
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/OWNERS.yml
@@ -0,0 +1,23 @@
+include:
+ - jgraham
+ - Ms2ger
+exclude:
+ - jgriffin
+paths:
+ tests:
+ vendor: true
+ paths:
+ dom:
+ include:
+ - annevk
+ exclude:
+ - jgraham
+ dom/events:
+ include:
+ - smaug
+ harness:
+ vendor: true
+ upstream: false
+ mach_commands.py:
+ exclude:
+ - Ms2ger