| 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):
|
|
|