Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(257)

Unified Diff: presubmit_support.py

Issue 15898005: presubmit: Call 'git diff' once per change instead of once per file (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@master
Patch Set: Fix language in git_cl.py Created 7 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « presubmit_canned_checks.py ('k') | tests/presubmit_unittest.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: presubmit_support.py
diff --git a/presubmit_support.py b/presubmit_support.py
index 9baee8c5c0b90d51ada1fa560dc338b90b44a802..6f796b6bf11369c2797cf4b20111bf22034e282d 100755
--- a/presubmit_support.py
+++ b/presubmit_support.py
@@ -489,11 +489,76 @@ class InputApi(object):
return [m for m in msgs if m]
+class _DiffCache(object):
+ """Caches diffs retrieved from a particular SCM."""
+
+ def GetDiff(self, path, local_root):
+ """Get the diff for a particular path."""
+ raise NotImplementedError()
+
+
+class _SvnDiffCache(_DiffCache):
+ """DiffCache implementation for subversion."""
+ def __init__(self):
+ super(_SvnDiffCache, self).__init__()
+ self._diffs_by_file = {}
+
+ def GetDiff(self, path, local_root):
+ if path not in self._diffs_by_file:
+ self._diffs_by_file[path] = scm.SVN.GenerateDiff([path], local_root,
+ False, None)
+ return self._diffs_by_file[path]
+
+
+class _GitDiffCache(_DiffCache):
+ """DiffCache implementation for git; gets all file diffs at once."""
+ def __init__(self):
+ super(_GitDiffCache, self).__init__()
+ self._diffs_by_file = None
+
+ def GetDiff(self, path, local_root):
+ if not self._diffs_by_file:
+ # Compute a single diff for all files and parse the output; should
+ # with git this is much faster than computing one diff for each file.
+ diffs = {}
+
+ # Don't specify any filenames below, because there are command line length
+ # limits on some platforms and GenerateDiff would fail.
+ unified_diff = scm.GIT.GenerateDiff(local_root, files=[], full_move=True)
+
+ # This regex matches the path twice, separated by a space. Note that
+ # filename itself may contain spaces.
+ file_marker = re.compile('^diff --git (?P<filename>.*) (?P=filename)$')
+ current_diff = []
+ keep_line_endings = True
+ for x in unified_diff.splitlines(keep_line_endings):
+ match = file_marker.match(x)
+ if match:
+ # Marks the start of a new per-file section.
+ diffs[match.group('filename')] = current_diff = [x]
+ elif x.startswith('diff --git'):
+ raise PresubmitFailure('Unexpected diff line: %s' % x)
+ else:
+ current_diff.append(x)
+
+ self._diffs_by_file = dict(
+ (normpath(path), ''.join(diff)) for path, diff in diffs.items())
+
+ if path not in self._diffs_by_file:
+ raise PresubmitFailure(
+ 'Unified diff did not contain entry for file %s' % path)
+
+ return self._diffs_by_file[path]
+
+
class AffectedFile(object):
"""Representation of a file in a change."""
+
+ DIFF_CACHE = _DiffCache
+
# Method could be a function
# pylint: disable=R0201
- def __init__(self, path, action, repository_root):
+ def __init__(self, path, action, repository_root, diff_cache=None):
self._path = path
self._action = action
self._local_root = repository_root
@@ -501,6 +566,10 @@ class AffectedFile(object):
self._properties = {}
self._cached_changed_contents = None
self._cached_new_contents = None
+ if diff_cache:
+ self._diff_cache = diff_cache
+ else:
+ self._diff_cache = self.DIFF_CACHE()
logging.debug('%s(%s)' % (self.__class__.__name__, self._path))
def ServerPath(self):
@@ -508,7 +577,7 @@ class AffectedFile(object):
Returns the empty string if the file does not exist in SCM.
"""
- return ""
+ return ''
def LocalPath(self):
"""Returns the path of this file on the local disk relative to client root.
@@ -565,22 +634,6 @@ class AffectedFile(object):
pass # File not found? That's fine; maybe it was deleted.
return self._cached_new_contents[:]
- def OldContents(self):
- """Returns an iterator over the lines in the old version of file.
-
- The old version is the file in depot, i.e. the "left hand side".
- """
- raise NotImplementedError() # Implement when needed
-
- def OldFileTempPath(self):
- """Returns the path on local disk where the old contents resides.
-
- The old version is the file in depot, i.e. the "left hand side".
- This is a read-only cached copy of the old contents. *DO NOT* try to
- modify this file.
- """
- raise NotImplementedError() # Implement if/when needed.
-
def ChangedContents(self):
"""Returns a list of tuples (line number, line text) of all new lines.
@@ -612,7 +665,7 @@ class AffectedFile(object):
return self.LocalPath()
def GenerateScmDiff(self):
- raise NotImplementedError() # Implemented in derived classes.
+ return self._diff_cache.GetDiff(self.LocalPath(), self._local_root)
class SvnAffectedFile(AffectedFile):
@@ -620,11 +673,12 @@ class SvnAffectedFile(AffectedFile):
# Method 'NNN' is abstract in class 'NNN' but is not overridden
# pylint: disable=W0223
+ DIFF_CACHE = _SvnDiffCache
+
def __init__(self, *args, **kwargs):
AffectedFile.__init__(self, *args, **kwargs)
self._server_path = None
self._is_text_file = None
- self._diff = None
def ServerPath(self):
if self._server_path is None:
@@ -664,23 +718,18 @@ class SvnAffectedFile(AffectedFile):
self._is_text_file = (not mime_type or mime_type.startswith('text/'))
return self._is_text_file
- def GenerateScmDiff(self):
- if self._diff is None:
- self._diff = scm.SVN.GenerateDiff(
- [self.LocalPath()], self._local_root, False, None)
- return self._diff
-
class GitAffectedFile(AffectedFile):
"""Representation of a file in a change out of a git checkout."""
# Method 'NNN' is abstract in class 'NNN' but is not overridden
# pylint: disable=W0223
+ DIFF_CACHE = _GitDiffCache
+
def __init__(self, *args, **kwargs):
AffectedFile.__init__(self, *args, **kwargs)
self._server_path = None
self._is_text_file = None
- self._diff = None
def ServerPath(self):
if self._server_path is None:
@@ -714,12 +763,6 @@ class GitAffectedFile(AffectedFile):
self._is_text_file = os.path.isfile(self.AbsoluteLocalPath())
return self._is_text_file
- def GenerateScmDiff(self):
- if self._diff is None:
- self._diff = scm.GIT.GenerateDiff(
- self._local_root, files=[self.LocalPath(),])
- return self._diff
-
class Change(object):
"""Describe a change.
@@ -728,7 +771,7 @@ class Change(object):
tested.
Instance members:
- tags: Dictionnary of KEY=VALUE pairs found in the change description.
+ tags: Dictionary of KEY=VALUE pairs found in the change description.
self.KEY: equivalent to tags['KEY']
"""
@@ -769,9 +812,10 @@ class Change(object):
assert all(
(isinstance(f, (list, tuple)) and len(f) == 2) for f in files), files
+ diff_cache = self._AFFECTED_FILES.DIFF_CACHE()
self._affected_files = [
- self._AFFECTED_FILES(info[1], info[0].strip(), self._local_root)
- for info in files
+ self._AFFECTED_FILES(path, action.strip(), self._local_root, diff_cache)
+ for action, path in files
]
def Name(self):
« no previous file with comments | « presubmit_canned_checks.py ('k') | tests/presubmit_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698