mirror of
https://chromium.googlesource.com/chromium/tools/depot_tools.git
synced 2026-01-11 18:51:29 +00:00
Create scm.DIFF.GetAllFiles
For a ProvidedDiffChange, the AllFiles method naively returns all files
with rglob("*"). The returned list includes files in nested submodules.
This does not match the behavior of GitChange's AllFiles which uses
git ls-files to find all files.
Implement a new SCM that stops iterating recursively when it
sees a submodule from .gitmodules in the repo root.
Bug: b/323243527
Change-Id: I170d0f1bc4a838acea04779dee3df7fca0bce359
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/5648616
Reviewed-by: Josip Sokcevic <sokcevic@chromium.org>
Commit-Queue: Gavin Mak <gavinmak@google.com>
This commit is contained in:
@@ -23,7 +23,6 @@ import logging
|
|||||||
import mimetypes
|
import mimetypes
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import os # Somewhat exposed through the API.
|
import os # Somewhat exposed through the API.
|
||||||
import pathlib
|
|
||||||
import random
|
import random
|
||||||
import re # Exposed through the API.
|
import re # Exposed through the API.
|
||||||
import shutil
|
import shutil
|
||||||
@@ -1486,12 +1485,9 @@ class ProvidedDiffChange(Change):
|
|||||||
return self._AFFECTED_FILES.DIFF_CACHE(self._diff)
|
return self._AFFECTED_FILES.DIFF_CACHE(self._diff)
|
||||||
|
|
||||||
def AllFiles(self, root=None):
|
def AllFiles(self, root=None):
|
||||||
"""List all files under source control in the repo.
|
"""List all files under source control in the repo."""
|
||||||
|
|
||||||
There is no SCM, so return all files under the repo root.
|
|
||||||
"""
|
|
||||||
root = root or self.RepositoryRoot()
|
root = root or self.RepositoryRoot()
|
||||||
return [str(p) for p in pathlib.Path(root).rglob("*")]
|
return scm.DIFF.GetAllFiles(root)
|
||||||
|
|
||||||
|
|
||||||
def ListRelevantPresubmitFiles(files, root):
|
def ListRelevantPresubmitFiles(files, root):
|
||||||
@@ -2060,11 +2056,11 @@ def _parse_change(parser, options):
|
|||||||
parser.error(
|
parser.error(
|
||||||
'<diff_file> cannot be specified when <generate_diff> is set.')
|
'<diff_file> cannot be specified when <generate_diff> is set.')
|
||||||
|
|
||||||
# TODO(b/323243527): Consider adding a SCM for provided diff.
|
|
||||||
change_scm = scm.determine_scm(options.root)
|
change_scm = scm.determine_scm(options.root)
|
||||||
if change_scm != 'git' and not options.files and not options.diff_file:
|
if change_scm == 'diff' and not (options.files or options.all_files
|
||||||
parser.error(
|
or options.diff_file):
|
||||||
'unversioned directories must specify <files> or <diff_file>.')
|
parser.error('unversioned directories must specify '
|
||||||
|
'<files>, <all_files>, or <diff_file>.')
|
||||||
|
|
||||||
diff = None
|
diff = None
|
||||||
if options.files:
|
if options.files:
|
||||||
@@ -2091,7 +2087,11 @@ def _parse_change(parser, options):
|
|||||||
# Get the filtered set of files from a directory scan.
|
# Get the filtered set of files from a directory scan.
|
||||||
change_files = _parse_files(options.files, options.recursive)
|
change_files = _parse_files(options.files, options.recursive)
|
||||||
elif options.all_files:
|
elif options.all_files:
|
||||||
change_files = [('M', f) for f in scm.GIT.GetAllFiles(options.root)]
|
if change_scm == 'git':
|
||||||
|
all_files = scm.GIT.GetAllFiles(options.root)
|
||||||
|
else:
|
||||||
|
all_files = scm.DIFF.GetAllFiles(options.root)
|
||||||
|
change_files = [('M', f) for f in all_files]
|
||||||
elif options.diff_file:
|
elif options.diff_file:
|
||||||
diff, change_files = _process_diff_file(options.diff_file)
|
diff, change_files = _process_diff_file(options.diff_file)
|
||||||
else:
|
else:
|
||||||
|
|||||||
36
scm.py
36
scm.py
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import os
|
import os
|
||||||
|
import pathlib
|
||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
from typing import Mapping, List
|
from typing import Mapping, List
|
||||||
@@ -25,7 +26,7 @@ VERSIONED_SUBMODULE = 2
|
|||||||
def determine_scm(root):
|
def determine_scm(root):
|
||||||
"""Similar to upload.py's version but much simpler.
|
"""Similar to upload.py's version but much simpler.
|
||||||
|
|
||||||
Returns 'git' or None.
|
Returns 'git' or 'diff'.
|
||||||
"""
|
"""
|
||||||
if os.path.isdir(os.path.join(root, '.git')):
|
if os.path.isdir(os.path.join(root, '.git')):
|
||||||
return 'git'
|
return 'git'
|
||||||
@@ -37,7 +38,7 @@ def determine_scm(root):
|
|||||||
cwd=root)
|
cwd=root)
|
||||||
return 'git'
|
return 'git'
|
||||||
except (OSError, subprocess2.CalledProcessError):
|
except (OSError, subprocess2.CalledProcessError):
|
||||||
return None
|
return 'diff'
|
||||||
|
|
||||||
|
|
||||||
class GIT(object):
|
class GIT(object):
|
||||||
@@ -529,3 +530,34 @@ class GIT(object):
|
|||||||
if sha_only:
|
if sha_only:
|
||||||
return sha == rev.lower()
|
return sha == rev.lower()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class DIFF(object):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def GetAllFiles(cwd):
|
||||||
|
"""Return all files under the repo at cwd.
|
||||||
|
|
||||||
|
If .gitmodules exists in cwd, use it to determine which folders are
|
||||||
|
submodules and don't recurse into them. Submodule paths are returned.
|
||||||
|
"""
|
||||||
|
# `git config --file` works outside of a git workspace.
|
||||||
|
submodules = GIT.ListSubmodules(cwd)
|
||||||
|
if not submodules:
|
||||||
|
return [
|
||||||
|
str(p.relative_to(cwd)) for p in pathlib.Path(cwd).rglob("*")
|
||||||
|
if p.is_file()
|
||||||
|
]
|
||||||
|
|
||||||
|
full_path_submodules = {os.path.join(cwd, s) for s in submodules}
|
||||||
|
|
||||||
|
def should_recurse(dirpath, dirname):
|
||||||
|
full_path = os.path.join(dirpath, dirname)
|
||||||
|
return full_path not in full_path_submodules
|
||||||
|
|
||||||
|
paths = list(full_path_submodules)
|
||||||
|
for dirpath, dirnames, filenames in os.walk(cwd):
|
||||||
|
paths.extend([os.path.join(dirpath, f) for f in filenames])
|
||||||
|
dirnames[:] = [d for d in dirnames if should_recurse(dirpath, d)]
|
||||||
|
|
||||||
|
return [os.path.relpath(p, cwd) for p in paths]
|
||||||
|
|||||||
@@ -950,7 +950,7 @@ def CheckChangeOnCommit(input_api, output_api):
|
|||||||
'random_file.txt']))
|
'random_file.txt']))
|
||||||
|
|
||||||
def testMainUnversionedFail(self):
|
def testMainUnversionedFail(self):
|
||||||
scm.determine_scm.return_value = None
|
scm.determine_scm.return_value = 'diff'
|
||||||
|
|
||||||
with self.assertRaises(SystemExit) as e:
|
with self.assertRaises(SystemExit) as e:
|
||||||
presubmit.main(['--root', self.fake_root_dir])
|
presubmit.main(['--root', self.fake_root_dir])
|
||||||
@@ -960,7 +960,7 @@ def CheckChangeOnCommit(input_api, output_api):
|
|||||||
sys.stderr.getvalue(),
|
sys.stderr.getvalue(),
|
||||||
'usage: presubmit_unittest.py [options] <files...>\n'
|
'usage: presubmit_unittest.py [options] <files...>\n'
|
||||||
'presubmit_unittest.py: error: unversioned directories must '
|
'presubmit_unittest.py: error: unversioned directories must '
|
||||||
'specify <files> or <diff_file>.\n')
|
'specify <files>, <all_files>, or <diff_file>.\n')
|
||||||
|
|
||||||
@mock.patch('presubmit_support.Change', mock.Mock())
|
@mock.patch('presubmit_support.Change', mock.Mock())
|
||||||
def testParseChange_Files(self):
|
def testParseChange_Files(self):
|
||||||
@@ -979,9 +979,9 @@ def CheckChangeOnCommit(input_api, output_api):
|
|||||||
presubmit._parse_files.assert_called_once_with(options.files,
|
presubmit._parse_files.assert_called_once_with(options.files,
|
||||||
options.recursive)
|
options.recursive)
|
||||||
|
|
||||||
def testParseChange_NoFilesAndNoScm(self):
|
def testParseChange_NoFilesAndDiff(self):
|
||||||
presubmit._parse_files.return_value = []
|
presubmit._parse_files.return_value = []
|
||||||
scm.determine_scm.return_value = None
|
scm.determine_scm.return_value = 'diff'
|
||||||
parser = mock.Mock()
|
parser = mock.Mock()
|
||||||
parser.error.side_effect = [SystemExit]
|
parser.error.side_effect = [SystemExit]
|
||||||
options = mock.Mock(files=[], diff_file='', all_files=False)
|
options = mock.Mock(files=[], diff_file='', all_files=False)
|
||||||
@@ -989,7 +989,8 @@ def CheckChangeOnCommit(input_api, output_api):
|
|||||||
with self.assertRaises(SystemExit):
|
with self.assertRaises(SystemExit):
|
||||||
presubmit._parse_change(parser, options)
|
presubmit._parse_change(parser, options)
|
||||||
parser.error.assert_called_once_with(
|
parser.error.assert_called_once_with(
|
||||||
'unversioned directories must specify <files> or <diff_file>.')
|
'unversioned directories must specify '
|
||||||
|
'<files>, <all_files>, or <diff_file>.')
|
||||||
|
|
||||||
def testParseChange_FilesAndAllFiles(self):
|
def testParseChange_FilesAndAllFiles(self):
|
||||||
parser = mock.Mock()
|
parser = mock.Mock()
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
@@ -352,6 +353,48 @@ class RealGitTest(fake_repos.FakeReposTestBase):
|
|||||||
scm.GIT.Capture(['checkout', 'main'], cwd=self.cwd)
|
scm.GIT.Capture(['checkout', 'main'], cwd=self.cwd)
|
||||||
|
|
||||||
|
|
||||||
|
class DiffTestCase(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.root = tempfile.mkdtemp()
|
||||||
|
|
||||||
|
os.makedirs(os.path.join(self.root, "foo", "dir"))
|
||||||
|
with open(os.path.join(self.root, "foo", "file.txt"), "w") as f:
|
||||||
|
f.write("foo\n")
|
||||||
|
with open(os.path.join(self.root, "foo", "dir", "file.txt"), "w") as f:
|
||||||
|
f.write("foo dir\n")
|
||||||
|
|
||||||
|
os.makedirs(os.path.join(self.root, "baz_repo"))
|
||||||
|
with open(os.path.join(self.root, "baz_repo", "file.txt"), "w") as f:
|
||||||
|
f.write("baz\n")
|
||||||
|
|
||||||
|
@mock.patch('scm.GIT.ListSubmodules')
|
||||||
|
def testGetAllFiles_ReturnsAllFilesIfNoSubmodules(self, mockListSubmodules):
|
||||||
|
mockListSubmodules.return_value = []
|
||||||
|
files = scm.DIFF.GetAllFiles(self.root)
|
||||||
|
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
self.assertCountEqual(
|
||||||
|
files,
|
||||||
|
["foo\\file.txt", "foo\\dir\\file.txt", "baz_repo\\file.txt"])
|
||||||
|
else:
|
||||||
|
self.assertCountEqual(
|
||||||
|
files,
|
||||||
|
["foo/file.txt", "foo/dir/file.txt", "baz_repo/file.txt"])
|
||||||
|
|
||||||
|
@mock.patch('scm.GIT.ListSubmodules')
|
||||||
|
def testGetAllFiles_IgnoresFilesInSubmodules(self, mockListSubmodules):
|
||||||
|
mockListSubmodules.return_value = ['baz_repo']
|
||||||
|
files = scm.DIFF.GetAllFiles(self.root)
|
||||||
|
|
||||||
|
if sys.platform.startswith('win'):
|
||||||
|
self.assertCountEqual(
|
||||||
|
files, ["foo\\file.txt", "foo\\dir\\file.txt", "baz_repo"])
|
||||||
|
else:
|
||||||
|
self.assertCountEqual(
|
||||||
|
files, ["foo/file.txt", "foo/dir/file.txt", "baz_repo"])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
if '-v' in sys.argv:
|
if '-v' in sys.argv:
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|||||||
Reference in New Issue
Block a user