pre-upload: automatically reject (c) in newer files

So I don't have to keep telling people.

BUG=None
TEST=`./pre-upload_unittest.py` passes

Change-Id: I85a2e95ea13a633c621a4a73aa6e9099eef97c69
Reviewed-on: https://chromium-review.googlesource.com/214582
Reviewed-by: Yu-Ju Hong <yjhong@chromium.org>
Tested-by: Mike Frysinger <vapier@chromium.org>
diff --git a/pre-upload.py b/pre-upload.py
index adc4d82..9e6214c 100755
--- a/pre-upload.py
+++ b/pre-upload.py
@@ -63,6 +63,8 @@
 
     # ignore profiles data (like overlay-tegra2/profiles)
     r".*/overlay-.*/profiles/.*",
+    r"^profiles/.*$",
+
     # ignore minified js and jquery
     r".*\.min\.js",
     r".*jquery.*\.js",
@@ -154,38 +156,6 @@
   return filtered
 
 
-def _verify_header_content(commit, content, fail_msg):
-  """Verify that file headers contain specified content.
-
-  Args:
-    commit: the affected commit.
-    content: the content of the header to be verified.
-    fail_msg: the first message to display in case of failure.
-
-  Returns:
-    The return value of HookFailure().
-  """
-  license_re = re.compile(content, re.MULTILINE)
-  bad_files = []
-  files = _filter_files(_get_affected_files(commit),
-                        COMMON_INCLUDED_PATHS,
-                        COMMON_EXCLUDED_PATHS)
-
-  for f in files:
-    # Ignore non-existant files and symlinks
-    if os.path.exists(f) and not os.path.islink(f):
-      contents = open(f).read()
-      if not contents:
-        # Ignore empty files
-        continue
-      if not license_re.search(contents):
-        bad_files.append(f)
-  if bad_files:
-    msg = "%s:\n%s\n%s" % (fail_msg, license_re.pattern,
-                           "Found a bad header in these files:")
-    return HookFailure(msg, bad_files)
-
-
 # Git Helpers
 
 
@@ -736,18 +706,55 @@
 
 
 def _check_license(_project, commit):
-  """Verifies the license header."""
-  LICENSE_HEADER = (
-      r".* Copyright( \(c\))? 20[-0-9]{2,7} The Chromium OS Authors\. "
-          "All rights reserved\." "\n"
-      r".* Use of this source code is governed by a BSD-style license that can "
-          "be\n"
-      r".* found in the LICENSE file\."
-          "\n"
-  )
-  FAIL_MSG = "License must match"
+  """Verifies the license/copyright header.
 
-  return _verify_header_content(commit, LICENSE_HEADER, FAIL_MSG)
+  Should be following the spec:
+  http://dev.chromium.org/developers/coding-style#TOC-File-headers
+  """
+  # For older years, be a bit more flexible as our policy says leave them be.
+  LICENSE_HEADER = (
+      r'.* Copyright( \(c\))? 20[-0-9]{2,7} The Chromium OS Authors\. '
+          'All rights reserved\.' '\n'
+      r'.* Use of this source code is governed by a BSD-style license that can '
+          'be\n'
+      r'.* found in the LICENSE file\.'
+          '\n'
+  )
+  license_re = re.compile(LICENSE_HEADER, re.MULTILINE)
+
+  # For newer years, be stricter.
+  COPYRIGHT_LINE = (
+      r'.* Copyright \(c\) 20(1[5-9]|[2-9][0-9]) The Chromium OS Authors\. '
+          'All rights reserved\.' '\n'
+  )
+  copyright_re = re.compile(COPYRIGHT_LINE)
+
+  bad_files = []
+  bad_copyright_files = []
+  files = _filter_files(_get_affected_files(commit, relative=True),
+                        COMMON_INCLUDED_PATHS,
+                        COMMON_EXCLUDED_PATHS)
+
+  for f in files:
+    contents = _get_file_content(f, commit)
+    if not contents:
+      # Ignore empty files.
+      continue
+
+    if not license_re.search(contents):
+      bad_files.append(f)
+    elif copyright_re.search(contents):
+      bad_copyright_files.append(f)
+
+  if bad_files:
+    msg = '%s:\n%s\n%s' % (
+        'License must match', license_re.pattern,
+        'Found a bad header in these files:')
+    return HookFailure(msg, bad_files)
+
+  if bad_copyright_files:
+    msg = 'Do not use (c) in copyright headers in new files:'
+    return HookFailure(msg, bad_copyright_files)
 
 
 def _check_layout_conf(_project, commit):
diff --git a/pre-upload_unittest.py b/pre-upload_unittest.py
index feb4555..ffc4777 100755
--- a/pre-upload_unittest.py
+++ b/pre-upload_unittest.py
@@ -281,8 +281,10 @@
     ret = pre_upload._check_ebuild_virtual_pv(self.PRIVATE_VARIANT_OVERLAY, 'H')
     self.assertTrue(isinstance (ret, errors.HookFailure))
 
+
 class CheckGitOutputParsing(cros_test_lib.MockTestCase):
   """Tests for git output parsing."""
+
   def testParseAffectedFiles(self):
     """Test parsing git diff --raw output."""
     # Sample from git diff --raw.
@@ -304,5 +306,49 @@
                                               relative=True)
     self.assertEqual(result, expected_modified_files_no_deletes)
 
+
+class CheckLicenseCopyrightHeader(cros_test_lib.MockTestCase):
+  """Tests for _check_license."""
+
+  def setUp(self):
+    self.file_mock = self.PatchObject(pre_upload, '_get_affected_files')
+    self.content_mock = self.PatchObject(pre_upload, '_get_file_content')
+
+  def testOldHeaders(self):
+    """Accept old header styles."""
+    HEADERS = (
+        ('#!/bin/sh\n'
+         '# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.\n'
+         '# Use of this source code is governed by a BSD-style license that'
+         ' can be\n'
+         '# found in the LICENSE file.\n'),
+        ('// Copyright 2010-13 The Chromium OS Authors. All rights reserved.\n'
+         '// Use of this source code is governed by a BSD-style license that'
+         ' can be\n'
+         '// found in the LICENSE file.\n'),
+    )
+    self.file_mock.return_value = ['file']
+    for header in HEADERS:
+      self.content_mock.return_value = header
+      self.assertEqual(None, pre_upload._check_license('proj', 'sha1'))
+
+  def testRejectC(self):
+    """Reject the (c) in newer headers."""
+    HEADERS = (
+        ('// Copyright (c) 2015 The Chromium OS Authors. All rights reserved.\n'
+         '// Use of this source code is governed by a BSD-style license that'
+         ' can be\n'
+         '// found in the LICENSE file.\n'),
+        ('// Copyright (c) 2020 The Chromium OS Authors. All rights reserved.\n'
+         '// Use of this source code is governed by a BSD-style license that'
+         ' can be\n'
+         '// found in the LICENSE file.\n'),
+    )
+    self.file_mock.return_value = ['file']
+    for header in HEADERS:
+      self.content_mock.return_value = header
+      self.assertNotEqual(None, pre_upload._check_license('proj', 'sha1'))
+
+
 if __name__ == '__main__':
   cros_test_lib.main()