Show a list of directories missing OWNER reviewers on upload, and show directories rather than files missing OWNER approvals on commit.

BUG=117166
TEST=covered by presubmit unittests

Review URL: http://codereview.chromium.org/9621012

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@125588 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/owners.py b/owners.py
index fc5fa92..d8692c3 100644
--- a/owners.py
+++ b/owners.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2010 The Chromium Authors. All rights reserved.
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
@@ -75,29 +75,27 @@
     self._load_data_needed_for(files)
     return self._covering_set_of_owners_for(files)
 
-  def files_are_covered_by(self, files, reviewers):
-    """Returns whether every file is owned by at least one reviewer."""
-    return not self.files_not_covered_by(files, reviewers)
+  def directories_not_covered_by(self, files, reviewers):
+    """Returns the set of directories that are not owned by a reviewer.
 
-  def files_not_covered_by(self, files, reviewers):
-    """Returns the set of files that are not owned by at least one reviewer.
+    Determines which of the given files are not owned by at least one of the
+    reviewers, then returns a set containing the applicable enclosing
+    directories, i.e. the ones upward from the files that have OWNERS files.
 
     Args:
         files is a sequence of paths relative to (and under) self.root.
-        reviewers is a sequence of strings matching self.email_regexp."""
+        reviewers is a sequence of strings matching self.email_regexp.
+    """
     self._check_paths(files)
     self._check_reviewers(reviewers)
-    if not reviewers:
-      return files
-
     self._load_data_needed_for(files)
-    files_by_dir = self._files_by_dir(files)
+
+    dirs = set([self.os_path.dirname(f) for f in files])
     covered_dirs = self._dirs_covered_by(reviewers)
-    uncovered_files = []
-    for d, files_in_d in files_by_dir.iteritems():
-      if not self._is_dir_covered_by(d, covered_dirs):
-        uncovered_files.extend(files_in_d)
-    return set(uncovered_files)
+    uncovered_dirs = [self._enclosing_dir_with_owners(d) for d in dirs
+                      if not self._is_dir_covered_by(d, covered_dirs)]
+
+    return set(uncovered_dirs)
 
   def _check_paths(self, files):
     def _is_under(f, pfx):
@@ -109,12 +107,6 @@
     _assert_is_collection(reviewers)
     assert all(self.email_regexp.match(r) for r in reviewers)
 
-  def _files_by_dir(self, files):
-    dirs = {}
-    for f in files:
-      dirs.setdefault(self.os_path.dirname(f), []).append(f)
-    return dirs
-
   def _dirs_covered_by(self, reviewers):
     dirs = self.owned_by[EVERYONE]
     for r in reviewers:
@@ -129,6 +121,15 @@
       dirname = self.os_path.dirname(dirname)
     return dirname in covered_dirs
 
+  def _enclosing_dir_with_owners(self, directory):
+    """Returns the innermost enclosing directory that has an OWNERS file."""
+    dirpath = directory
+    while not dirpath in self.owners_for:
+      if self._stop_looking(dirpath):
+        break
+      dirpath = self.os_path.dirname(dirpath)
+    return dirpath
+
   def _load_data_needed_for(self, files):
     for f in files:
       dirpath = self.os_path.dirname(f)