Add a presubmit check for accidental checkins of files under a SVN modified directory.

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

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@23271 0039d316-1c4b-4281-b951-d872f2087c98
experimental/szager/collated-output
thestig@chromium.org 16 years ago
parent 913a842462
commit da8cddddfe

@ -88,6 +88,42 @@ def CheckChangeHasNoCR(input_api, output_api, source_file_filter=None):
return [] return []
def CheckSvnModifiedDirectories(input_api, output_api, source_file_filter=None):
"""Checks for files in svn modified directories.
They will get submitted on accident because svn commits recursively by
default, and that's very dangerous.
"""
if input_api.change.scm != 'svn':
return []
errors = []
current_cl_files = input_api.change.GetModifiedFiles()
all_modified_files = input_api.change.GetAllModifiedFiles()
# Filter out files in the current CL.
modified_files = [f for f in all_modified_files if f not in current_cl_files]
modified_abspaths = [input_api.os_path.abspath(f) for f in modified_files]
for f in input_api.AffectedFiles(source_file_filter):
if f.Action() == 'M' and f.IsDirectory():
curpath = f.AbsoluteLocalPath()
bad_files = []
# Check if any of the modified files in other CLs are under curpath.
for i in xrange(len(modified_files)):
abspath = modified_abspaths[i]
if input_api.os_path.commonprefix([curpath, abspath]) == curpath:
bad_files.append(modified_files[i])
if bad_files:
if input_api.is_committing:
error_type = output_api.PresubmitPromptWarning
else:
error_type = output_api.PresubmitNotifyResult
errors.append(error_type(
"Potential accidental commits in changelist %s:" % f.LocalPath(),
items=bad_files))
return errors
def CheckChangeHasOnlyOneEol(input_api, output_api, source_file_filter=None): def CheckChangeHasOnlyOneEol(input_api, output_api, source_file_filter=None):
"""Checks the files ends with one and only one \n (LF).""" """Checks the files ends with one and only one \n (LF)."""
eof_files = [] eof_files = []
@ -220,8 +256,10 @@ def CheckSvnForCommonMimeTypes(input_api, output_api):
def CheckSvnProperty(input_api, output_api, prop, expected, affected_files): def CheckSvnProperty(input_api, output_api, prop, expected, affected_files):
"""Checks that affected_files files have prop=expected.""" """Checks that affected_files files have prop=expected."""
bad = filter(lambda f: f.scm == 'svn' and f.Property(prop) != expected, if input_api.change.scm != 'svn':
affected_files) return []
bad = filter(lambda f: f.Property(prop) != expected, affected_files)
if bad: if bad:
if input_api.is_committing: if input_api.is_committing:
type = output_api.PresubmitError type = output_api.PresubmitError

@ -343,7 +343,7 @@ class InputApi(object):
def ReadFile(self, file, mode='r'): def ReadFile(self, file, mode='r'):
"""Reads an arbitrary file. """Reads an arbitrary file.
Deny reading anything outside the repository. Deny reading anything outside the repository.
""" """
if isinstance(file, AffectedFile): if isinstance(file, AffectedFile):
@ -372,7 +372,6 @@ class AffectedFile(object):
self._local_root = repository_root self._local_root = repository_root
self._is_directory = None self._is_directory = None
self._properties = {} self._properties = {}
self.scm = ''
def ServerPath(self): def ServerPath(self):
"""Returns a path string that identifies the file in the SCM system. """Returns a path string that identifies the file in the SCM system.
@ -413,7 +412,7 @@ class AffectedFile(object):
def IsTextFile(self): def IsTextFile(self):
"""Returns True if the file is a text file and not a binary file. """Returns True if the file is a text file and not a binary file.
Deleted files are not text file.""" Deleted files are not text file."""
raise NotImplementedError() # Implement when needed raise NotImplementedError() # Implement when needed
@ -458,7 +457,6 @@ class SvnAffectedFile(AffectedFile):
AffectedFile.__init__(self, *args, **kwargs) AffectedFile.__init__(self, *args, **kwargs)
self._server_path = None self._server_path = None
self._is_text_file = None self._is_text_file = None
self.scm = 'svn'
def ServerPath(self): def ServerPath(self):
if self._server_path is None: if self._server_path is None:
@ -505,7 +503,6 @@ class GitAffectedFile(AffectedFile):
AffectedFile.__init__(self, *args, **kwargs) AffectedFile.__init__(self, *args, **kwargs)
self._server_path = None self._server_path = None
self._is_text_file = None self._is_text_file = None
self.scm = 'git'
def ServerPath(self): def ServerPath(self):
if self._server_path is None: if self._server_path is None:
@ -547,7 +544,7 @@ class Change(object):
Used directly by the presubmit scripts to query the current change being Used directly by the presubmit scripts to query the current change being
tested. tested.
Instance members: Instance members:
tags: Dictionnary of KEY=VALUE pairs found in the change description. tags: Dictionnary of KEY=VALUE pairs found in the change description.
self.KEY: equivalent to tags['KEY'] self.KEY: equivalent to tags['KEY']
@ -567,6 +564,7 @@ class Change(object):
self._local_root = local_root self._local_root = local_root
self.issue = issue self.issue = issue
self.patchset = patchset self.patchset = patchset
self.scm = ''
# From the description text, build up a dictionary of key/value pairs # From the description text, build up a dictionary of key/value pairs
# plus the description minus all key/value or "tag" lines. # plus the description minus all key/value or "tag" lines.
@ -681,10 +679,31 @@ class Change(object):
class SvnChange(Change): class SvnChange(Change):
_AFFECTED_FILES = SvnAffectedFile _AFFECTED_FILES = SvnAffectedFile
def __init__(self, *args, **kwargs):
Change.__init__(self, *args, **kwargs)
self.scm = 'svn'
def GetAllModifiedFiles(self):
"""Get all modified files."""
changelists = gcl.GetModifiedFiles()
all_modified_files = []
for cl in changelists.values():
all_modified_files.extend([f[1] for f in cl])
return all_modified_files
def GetModifiedFiles(self):
"""Get modified files in the current CL."""
changelists = gcl.GetModifiedFiles()
return [f[1] for f in changelists[self.Name()]]
class GitChange(Change): class GitChange(Change):
_AFFECTED_FILES = GitAffectedFile _AFFECTED_FILES = GitAffectedFile
def __init__(self, *args, **kwargs):
Change.__init__(self, *args, **kwargs)
self.scm = 'git'
def ListRelevantPresubmitFiles(files, root): def ListRelevantPresubmitFiles(files, root):
"""Finds all presubmit files that apply to a given set of source files. """Finds all presubmit files that apply to a given set of source files.

@ -35,10 +35,12 @@ def CheckChangeOnUpload(input_api, output_api):
def setUp(self): def setUp(self):
super_mox.SuperMoxTestBase.setUp(self) super_mox.SuperMoxTestBase.setUp(self)
self.mox.StubOutWithMock(presubmit, 'warnings') self.mox.StubOutWithMock(presubmit, 'warnings')
# Stub out 'os' but keep os.path.dirname/join/normpath/splitext and os.sep. # Stub out 'os' but keep os.path.commonprefix/dirname/join/normpath/splitext
# and os.sep.
os_sep = presubmit.os.sep os_sep = presubmit.os.sep
os_path_join = presubmit.os.path.join os_path_commonprefix = presubmit.os.path.commonprefix
os_path_dirname = presubmit.os.path.dirname os_path_dirname = presubmit.os.path.dirname
os_path_join = presubmit.os.path.join
os_path_normpath = presubmit.os.path.normpath os_path_normpath = presubmit.os.path.normpath
os_path_splitext = presubmit.os.path.splitext os_path_splitext = presubmit.os.path.splitext
self.mox.StubOutWithMock(presubmit, 'os') self.mox.StubOutWithMock(presubmit, 'os')
@ -55,6 +57,7 @@ def CheckChangeOnUpload(input_api, output_api):
def MockAbsPath(f): def MockAbsPath(f):
return f return f
presubmit.os.path.abspath = MockAbsPath presubmit.os.path.abspath = MockAbsPath
presubmit.os.path.commonprefix = os_path_commonprefix
self.fake_root_dir = self.RootDir() self.fake_root_dir = self.RootDir()
self.mox.StubOutWithMock(presubmit.gclient, 'CaptureSVNInfo') self.mox.StubOutWithMock(presubmit.gclient, 'CaptureSVNInfo')
self.mox.StubOutWithMock(presubmit.gcl, 'GetSVNFileProperty') self.mox.StubOutWithMock(presubmit.gcl, 'GetSVNFileProperty')
@ -394,7 +397,7 @@ def CheckChangeOnCommit(input_api, output_api):
'PRESUBMIT.py')).AndReturn(False) 'PRESUBMIT.py')).AndReturn(False)
presubmit.random.randint(0, 4).AndReturn(0) presubmit.random.randint(0, 4).AndReturn(0)
self.mox.ReplayAll() self.mox.ReplayAll()
output = StringIO.StringIO() output = StringIO.StringIO()
input = StringIO.StringIO('y\n') input = StringIO.StringIO('y\n')
# Always fail. # Always fail.
@ -451,7 +454,7 @@ def CheckChangeOnUpload(input_api, output_api):
if 'TEST' in input_api.change.tags: if 'TEST' in input_api.change.tags:
return [output_api.PresubmitError('Tag parsing failed. 3')] return [output_api.PresubmitError('Tag parsing failed. 3')]
if input_api.change.DescriptionText() != 'Blah Blah': if input_api.change.DescriptionText() != 'Blah Blah':
return [output_api.PresubmitError('Tag parsing failed. 4 ' + return [output_api.PresubmitError('Tag parsing failed. 4 ' +
input_api.change.DescriptionText())] input_api.change.DescriptionText())]
if (input_api.change.FullDescriptionText() != if (input_api.change.FullDescriptionText() !=
'Blah Blah\\n\\nSTORY=http://tracker.com/42\\nBUG=boo\\n'): 'Blah Blah\\n\\nSTORY=http://tracker.com/42\\nBUG=boo\\n'):
@ -490,7 +493,7 @@ def CheckChangeOnCommit(input_api, output_api):
mox.IgnoreArg(), mox.IgnoreArg(),
None, False).AndReturn(False) None, False).AndReturn(False)
self.mox.ReplayAll() self.mox.ReplayAll()
self.assertEquals(True, self.assertEquals(True,
presubmit.Main(['presubmit', '--root', presubmit.Main(['presubmit', '--root',
self.fake_root_dir])) self.fake_root_dir]))
@ -897,7 +900,6 @@ class AffectedFileUnittest(PresubmitTestsBase):
members = [ members = [
'AbsoluteLocalPath', 'Action', 'IsDirectory', 'IsTextFile', 'LocalPath', 'AbsoluteLocalPath', 'Action', 'IsDirectory', 'IsTextFile', 'LocalPath',
'NewContents', 'OldContents', 'OldFileTempPath', 'Property', 'ServerPath', 'NewContents', 'OldContents', 'OldFileTempPath', 'Property', 'ServerPath',
'scm',
] ]
# If this test fails, you should add the relevant test. # If this test fails, you should add the relevant test.
self.compareMembers(presubmit.AffectedFile('a', 'b'), members) self.compareMembers(presubmit.AffectedFile('a', 'b'), members)
@ -974,7 +976,7 @@ class GclChangeUnittest(PresubmitTestsBase):
'AbsoluteLocalPaths', 'AffectedFiles', 'AffectedTextFiles', 'AbsoluteLocalPaths', 'AffectedFiles', 'AffectedTextFiles',
'DescriptionText', 'FullDescriptionText', 'LocalPaths', 'Name', 'DescriptionText', 'FullDescriptionText', 'LocalPaths', 'Name',
'RepositoryRoot', 'RightHandSideLines', 'ServerPaths', 'RepositoryRoot', 'RightHandSideLines', 'ServerPaths',
'issue', 'patchset', 'tags', 'issue', 'patchset', 'scm', 'tags',
] ]
# If this test fails, you should add the relevant test. # If this test fails, you should add the relevant test.
self.mox.ReplayAll() self.mox.ReplayAll()
@ -993,6 +995,7 @@ class CannedChecksUnittest(PresubmitTestsBase):
def MockInputApi(self, change, committing): def MockInputApi(self, change, committing):
input_api = self.mox.CreateMock(presubmit.InputApi) input_api = self.mox.CreateMock(presubmit.InputApi)
input_api.cStringIO = presubmit.cStringIO input_api.cStringIO = presubmit.cStringIO
input_api.os_path = presubmit.os.path
input_api.re = presubmit.re input_api.re = presubmit.re
input_api.traceback = presubmit.traceback input_api.traceback = presubmit.traceback
input_api.urllib2 = self.mox.CreateMock(presubmit.urllib2) input_api.urllib2 = self.mox.CreateMock(presubmit.urllib2)
@ -1013,6 +1016,7 @@ class CannedChecksUnittest(PresubmitTestsBase):
'CheckChangeHasNoCrAndHasOnlyOneEol', 'CheckChangeHasNoTabs', 'CheckChangeHasNoCrAndHasOnlyOneEol', 'CheckChangeHasNoTabs',
'CheckChangeHasQaField', 'CheckChangeHasTestedField', 'CheckChangeHasQaField', 'CheckChangeHasTestedField',
'CheckChangeHasTestField', 'CheckChangeSvnEolStyle', 'CheckChangeHasTestField', 'CheckChangeSvnEolStyle',
'CheckSvnModifiedDirectories',
'CheckSvnForCommonMimeTypes', 'CheckSvnProperty', 'CheckSvnForCommonMimeTypes', 'CheckSvnProperty',
'CheckDoNotSubmit', 'CheckDoNotSubmit',
'CheckDoNotSubmitInDescription', 'CheckDoNotSubmitInFiles', 'CheckDoNotSubmitInDescription', 'CheckDoNotSubmitInFiles',
@ -1091,7 +1095,8 @@ class CannedChecksUnittest(PresubmitTestsBase):
def SvnPropertyTest(self, check, property, value1, value2, committing, def SvnPropertyTest(self, check, property, value1, value2, committing,
error_type, use_source_file): error_type, use_source_file):
input_api1 = self.MockInputApi(None, committing) change1 = presubmit.SvnChange('mychange', '', self.fake_root_dir, [], 0, 0)
input_api1 = self.MockInputApi(change1, committing)
files1 = [ files1 = [
presubmit.SvnAffectedFile('foo/bar.cc', 'A'), presubmit.SvnAffectedFile('foo/bar.cc', 'A'),
presubmit.SvnAffectedFile('foo.cc', 'M'), presubmit.SvnAffectedFile('foo.cc', 'M'),
@ -1104,7 +1109,8 @@ class CannedChecksUnittest(PresubmitTestsBase):
property).AndReturn(value1) property).AndReturn(value1)
presubmit.gcl.GetSVNFileProperty(presubmit.normpath('foo.cc'), presubmit.gcl.GetSVNFileProperty(presubmit.normpath('foo.cc'),
property).AndReturn(value1) property).AndReturn(value1)
input_api2 = self.MockInputApi(None, committing) change2 = presubmit.SvnChange('mychange', '', self.fake_root_dir, [], 0, 0)
input_api2 = self.MockInputApi(change2, committing)
files2 = [ files2 = [
presubmit.SvnAffectedFile('foo/bar.cc', 'A'), presubmit.SvnAffectedFile('foo/bar.cc', 'A'),
presubmit.SvnAffectedFile('foo.cc', 'M'), presubmit.SvnAffectedFile('foo.cc', 'M'),
@ -1113,7 +1119,7 @@ class CannedChecksUnittest(PresubmitTestsBase):
input_api2.AffectedSourceFiles(None).AndReturn(files2) input_api2.AffectedSourceFiles(None).AndReturn(files2)
else: else:
input_api2.AffectedFiles(include_deleted=False).AndReturn(files2) input_api2.AffectedFiles(include_deleted=False).AndReturn(files2)
presubmit.gcl.GetSVNFileProperty(presubmit.normpath('foo/bar.cc'), presubmit.gcl.GetSVNFileProperty(presubmit.normpath('foo/bar.cc'),
property).AndReturn(value2) property).AndReturn(value2)
presubmit.gcl.GetSVNFileProperty(presubmit.normpath('foo.cc'), presubmit.gcl.GetSVNFileProperty(presubmit.normpath('foo.cc'),
@ -1142,7 +1148,7 @@ class CannedChecksUnittest(PresubmitTestsBase):
'Bleh', '', 'Bleh', '',
presubmit.OutputApi.PresubmitError, presubmit.OutputApi.PresubmitError,
True) True)
def testCannedCheckChangeHasTestField(self): def testCannedCheckChangeHasTestField(self):
self.DescriptionTest(presubmit_canned_checks.CheckChangeHasTestField, self.DescriptionTest(presubmit_canned_checks.CheckChangeHasTestField,
'Foo\nTEST=did some stuff', 'Foo\n', 'Foo\nTEST=did some stuff', 'Foo\n',
@ -1179,7 +1185,7 @@ class CannedChecksUnittest(PresubmitTestsBase):
presubmit_canned_checks.CheckChangeHasNoStrayWhitespace(x, y), presubmit_canned_checks.CheckChangeHasNoStrayWhitespace(x, y),
'Foo', 'Foo ', 'Foo', 'Foo ',
presubmit.OutputApi.PresubmitPromptWarning) presubmit.OutputApi.PresubmitPromptWarning)
def testCheckChangeHasOnlyOneEol(self): def testCheckChangeHasOnlyOneEol(self):
self.ReadFileTest(presubmit_canned_checks.CheckChangeHasOnlyOneEol, self.ReadFileTest(presubmit_canned_checks.CheckChangeHasOnlyOneEol,
@ -1190,7 +1196,7 @@ class CannedChecksUnittest(PresubmitTestsBase):
self.ReadFileTest(presubmit_canned_checks.CheckChangeHasNoCR, self.ReadFileTest(presubmit_canned_checks.CheckChangeHasNoCR,
"Hey!\nHo!\n", "Hey!\r\nHo!\r\n", "Hey!\nHo!\n", "Hey!\r\nHo!\r\n",
presubmit.OutputApi.PresubmitPromptWarning) presubmit.OutputApi.PresubmitPromptWarning)
def testCheckChangeHasNoCrAndHasOnlyOneEol(self): def testCheckChangeHasNoCrAndHasOnlyOneEol(self):
self.ReadFileTest( self.ReadFileTest(
presubmit_canned_checks.CheckChangeHasNoCrAndHasOnlyOneEol, presubmit_canned_checks.CheckChangeHasNoCrAndHasOnlyOneEol,
@ -1223,6 +1229,32 @@ class CannedChecksUnittest(PresubmitTestsBase):
'svn:eol-style', 'LF', '', False, 'svn:eol-style', 'LF', '', False,
presubmit.OutputApi.PresubmitNotifyResult, True) presubmit.OutputApi.PresubmitNotifyResult, True)
def testCannedCheckSvnAccidentalSubmission(self):
modified_dir_file = 'foo/'
accidental_submssion_file = 'foo/bar.cc'
change = self.mox.CreateMock(presubmit.SvnChange)
change.scm = 'svn'
change.GetModifiedFiles().AndReturn([modified_dir_file])
change.GetAllModifiedFiles().AndReturn([modified_dir_file,
accidental_submssion_file])
input_api = self.MockInputApi(change, True)
affected_file = self.mox.CreateMock(presubmit.SvnAffectedFile)
affected_file.Action().AndReturn('M')
affected_file.IsDirectory().AndReturn(True)
affected_file.AbsoluteLocalPath().AndReturn(accidental_submssion_file)
affected_file.LocalPath().AndReturn(accidental_submssion_file)
input_api.AffectedFiles(None).AndReturn([affected_file])
self.mox.ReplayAll()
check = presubmit_canned_checks.CheckSvnModifiedDirectories
results = check(input_api, presubmit.OutputApi, None)
self.assertEquals(len(results), 1)
self.assertEquals(results[0].__class__,
presubmit.OutputApi.PresubmitPromptWarning)
def testCheckSvnForCommonMimeTypes(self): def testCheckSvnForCommonMimeTypes(self):
self.mox.StubOutWithMock(presubmit_canned_checks, 'CheckSvnProperty') self.mox.StubOutWithMock(presubmit_canned_checks, 'CheckSvnProperty')
input_api = self.MockInputApi(None, False) input_api = self.MockInputApi(None, False)

Loading…
Cancel
Save