Presubmit OEM check

Add presubmit check to `repo upload` that checks to see
if any OEM names are present in the CL description.

BUG=chromium:982868
TEST=presubmit unit tests

Change-Id: I9e83164b2e1e5a0e303f6b8cbc8fa410f540cf58
Reviewed-on: https://chromium-review.googlesource.com/1692080
Tested-by: Jack Neus <jackneus@google.com>
Commit-Ready: Jack Neus <jackneus@google.com>
Legacy-Commit-Queue: Commit Bot <commit-bot@chromium.org>
Reviewed-by: Paul Fagerburg <pfagerburg@chromium.org>
Reviewed-by: Jack Rosenthal <jrosenth@chromium.org>
Reviewed-by: Nick Crews <ncrews@chromium.org>
diff --git a/pre-upload.py b/pre-upload.py
index 2aedc8f..ed69623 100755
--- a/pre-upload.py
+++ b/pre-upload.py
@@ -634,6 +634,60 @@
       return HookFailure(msg)
 
 
+def _check_change_no_include_oem(project, commit):
+  """Check that the change does not reference OEMs."""
+  ALLOWLIST = {
+      'chromiumos/platform/ec',
+      # Used by unit tests.
+      'project',
+  }
+  if project.name not in ALLOWLIST:
+    return None
+
+  TAGS = (
+      'Reviewed-on',
+      'Reviewed-by',
+      'Signed-off-by',
+      'Commit-Ready',
+      'Tested-by',
+      'Commit-Queue',
+      'Legacy-Commit-Queue',
+      'Acked-by',
+      'Modified-by',
+      'CC',
+      'Suggested-by',
+      'Reported-by',
+      'Acked-for-chrome-by',
+  )
+
+  # Ignore tags, which could reasonably contain OEM names
+  # (e.g. Reviewed-by: foo@oem.corp-partner.google.com).
+  def tag_filter(x):
+    return not any(x.lower().startswith(h.lower()) for h in TAGS)
+
+  commit_message = ' '.join(
+      filter(tag_filter, _get_commit_desc(commit).splitlines()))
+  commit_message = re.sub(r'[\s_-]+', ' ', commit_message)
+
+  # Exercise caution when expanding these lists. Adding a name
+  # could indicate a new relationship with a company!
+  OEMS = ['hp', 'hewlett packard', 'dell', 'lenovo', 'acer', 'asus', 'samsung']
+  ODMS = [
+      'bitland', 'compal', 'haier', 'huaqin', 'inventec', 'lg', 'pegatron',
+      'pegatron(ems)', 'quanta', 'samsung', 'wistron'
+  ]
+
+  for name_type, name_list in [('OEM', OEMS), ('ODM', ODMS)]:
+    # Construct regex
+    name_re = r'\b(%s)\b' % '|'.join([re.escape(x) for x in name_list])
+    matches = [x[0] for x in re.findall(name_re, commit_message, re.IGNORECASE)]
+    if len(matches):
+      # If there's a match, throw an error.
+      error_msg = ('Changelist description contains the name of an'
+                   ' %s: "%s".' % (name_type, '","'.join(matches)))
+      return HookFailure(error_msg)
+
+
 def _check_for_uprev(project, commit, project_top=None):
   """Check that we're not missing a revbump of an ebuild in the given commit.
 
@@ -1640,9 +1694,9 @@
     _check_change_has_proper_changeid,
     _check_commit_message_style,
     _check_change_is_contribution,
+    _check_change_no_include_oem,
 ]
 
-
 # A list of hooks that are not project-specific
 _COMMON_HOOKS = [
     _check_cq_ini_well_formed,
diff --git a/pre-upload_unittest.py b/pre-upload_unittest.py
index 1cb9fe0..1e55864 100755
--- a/pre-upload_unittest.py
+++ b/pre-upload_unittest.py
@@ -1233,6 +1233,54 @@
     self.assertMessageAccepted('foo\n\nChange-Id: I1234\nSigned-off-by: Hi\n')
 
 
+class CheckCommitMessageNoOEM(CommitMessageTestCase):
+  """Tests for _check_change_no_include_oem."""
+
+  @staticmethod
+  def CheckMessage(project, commit):
+    return pre_upload._check_change_no_include_oem(project, commit)
+
+  def testNormal(self):
+    """Accept a commit message w/o reference to an OEM/ODM."""
+    self.assertMessageAccepted('foo\n')
+
+  def testHasOEM(self):
+    """Catch commit messages referencing OEMs."""
+    self.assertMessageRejected('HP Project\n\n')
+    self.assertMessageRejected('hewlett-packard\n')
+    self.assertMessageRejected('Hewlett\nPackard\n')
+    self.assertMessageRejected('Dell Chromebook\n\n\n')
+    self.assertMessageRejected('product@acer.com\n')
+    self.assertMessageRejected('This is related to Asus\n')
+    self.assertMessageRejected('lenovo machine\n')
+
+  def testHasODM(self):
+    """Catch commit messages referencing ODMs."""
+    self.assertMessageRejected('new samsung laptop\n\n')
+    self.assertMessageRejected('pegatron(ems) project\n')
+    self.assertMessageRejected('new Wistron device\n')
+
+  def testContainsOEM(self):
+    """Check that the check handles word boundaries properly."""
+    self.assertMessageAccepted('oheahpohea')
+    self.assertMessageAccepted('Play an Asus7 guitar chord.\n\n')
+
+  def testTag(self):
+    """Check that the check ignores tags."""
+    self.assertMessageAccepted(
+        'Harmless project\n'
+        'Reviewed-by: partner@asus.corp-partner.google.com\n'
+        'Tested-by: partner@hp.corp-partner.google.com\n'
+        'Signed-off-by: partner@dell.corp-partner.google.com\n'
+        'Commit-Queue: partner@lenovo.corp-partner.google.com\n'
+        'Legacy-Commit-Queue: partner@acer.corp-partner.google.com\n'
+        'CC: partner@acer.corp-partner.google.com\n'
+        'Acked-by: partner@hewlett-packard.corp-partner.google.com\n')
+    self.assertMessageRejected(
+        'Asus project\n'
+        'Reviewed-by: partner@asus.corp-partner.google.com')
+
+
 class CheckCommitMessageStyle(CommitMessageTestCase):
   """Tests for _check_commit_message_style."""