From 3ab2f212894b99de34cc5b5745d35307ee4e98a0 Mon Sep 17 00:00:00 2001 From: Joanna Wang Date: Wed, 9 Aug 2023 01:25:15 +0000 Subject: [PATCH] gclient: add gitmodules command to update/add submodules based on DEPS. Bug: 1421776 Change-Id: Id1ac48c4b65c17027fa24d0ba350a1a7f2eec64d Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/4665787 Commit-Queue: Joanna Wang Reviewed-by: Josip Sokcevic --- gclient.py | 62 ++++++++++++++++++++++++++++++ testing_support/fake_repos.py | 69 +++++++++++++++++++++++++++++++++- tests/gclient_git_smoketest.py | 38 +++++++++++++++++++ 3 files changed, 168 insertions(+), 1 deletion(-) diff --git a/gclient.py b/gclient.py index ddc36acd5..8b5606cb1 100755 --- a/gclient.py +++ b/gclient.py @@ -115,6 +115,7 @@ from third_party.repo.progress import Progress import subcommand import subprocess2 import setup_color +import git_cl from third_party import six @@ -2654,6 +2655,67 @@ class Flattener(object): self._flatten_dep(d) +@metrics.collector.collect_metrics('gclient gitmodules') +def CMDgitmodules(parser, args): + """Adds or updates Git Submodules based on the contents of the DEPS file. + + This command should be run in the root director of the repo. + It will create or update the .gitmodules file and include + `gclient-condition` values. Commits in gitlinks will also be updated. + """ + parser.add_option('--output-gitmodules', + help='name of the .gitmodules file to write to', + default='.gitmodules') + parser.add_option( + '--deps-file', + help= + 'name of the deps file to parse for git dependency paths and commits.', + default='DEPS') + parser.add_option( + '--skip-dep', + action="append", + help='skip adding gitmodules for the git dependency at the given path', + default=[]) + options, args = parser.parse_args(args) + + deps_dir = os.path.dirname(os.path.abspath(options.deps_file)) + gclient_path = gclient_paths.FindGclientRoot(deps_dir) + if not gclient_path: + logging.error( + '.gclient not found\n' + 'Make sure you are running this script from a gclient workspace.') + sys.exit(1) + + deps_content = gclient_utils.FileRead(options.deps_file) + ls = gclient_eval.Parse(deps_content, options.deps_file, None, None) + + prefix_length = 0 + if not 'use_relative_paths' in ls or ls['use_relative_paths'] != True: + delta_path = os.path.relpath(deps_dir, os.path.abspath(gclient_path)) + if delta_path: + prefix_length = len(delta_path.replace(os.path.sep, '/')) + 1 + + with open(options.output_gitmodules, 'w') as f: + for path, dep in ls.get('deps').items(): + if path in options.skip_dep: + continue + if dep.get('dep_type') == 'cipd': + continue + try: + url, commit = dep['url'].split('@', maxsplit=1) + except ValueError: + logging.error('error on %s; %s, not adding it', path, dep["url"]) + continue + if prefix_length: + path = path[prefix_length:] + + git_cl.RunGit( + ['update-index', '--add', '--cacheinfo', '160000', commit, path]) + f.write(f'[submodule "{path}"]\n\tpath = {path}\n\turl = {url}\n') + if 'condition' in dep: + f.write(f'\tgclient-condition = {dep["condition"]}\n') + + @metrics.collector.collect_metrics('gclient flatten') def CMDflatten(parser, args): """Flattens the solutions into a single DEPS file.""" diff --git a/testing_support/fake_repos.py b/testing_support/fake_repos.py index e2cdf7b89..72bab68f2 100755 --- a/testing_support/fake_repos.py +++ b/testing_support/fake_repos.py @@ -212,7 +212,7 @@ class FakeReposBase(object): class FakeRepos(FakeReposBase): """Implements populateGit().""" - NB_GIT_REPOS = 18 + NB_GIT_REPOS = 20 def populateGit(self): # Testing: @@ -759,6 +759,73 @@ hooks = [{ 'git/repo_18@2\n' }) + # a relative path repo + self._commit_git( + 'repo_19', { + 'DEPS': """ + +git_dependencies = "SUBMODULES" +use_relative_paths = True +vars = { + 'foo_checkout': True, +} +deps = { + "some_repo": { + "url": '/repo_2@%(hash_2)s', + "condition": "not foo_checkout", + }, + "chicken/dickens": { + "url": '/repo_3@%(hash_3)s', + }, + "weird/deps": { + "url": '/repo_1' + }, + "bar": { + "packages": [{ + "package": "lemur", + "version": "version:1234", + }], + "dep_type": "cipd", + }, +}""" % { + 'hash_2': self.git_hashes['repo_2'][1][0], + 'hash_3': self.git_hashes['repo_3'][1][0], + }, + }) + + # a non-relative_path repo + self._commit_git( + 'repo_20', { + 'DEPS': """ + +git_dependencies = "SUBMODULES" +vars = { + 'foo_checkout': True, +} +deps = { + "foo/some_repo": { + "url": '/repo_2@%(hash_2)s', + "condition": "not foo_checkout", + }, + "foo/chicken/dickens": { + "url": '/repo_3@%(hash_3)s', + }, + "foo/weird/deps": { + "url": '/repo_1' + }, + "foo/bar": { + "packages": [{ + "package": "lemur", + "version": "version:1234", + }], + "dep_type": "cipd", + }, +}""" % { + 'hash_2': self.git_hashes['repo_2'][1][0], + 'hash_3': self.git_hashes['repo_3'][1][0], + }, + }) + class FakeRepoSkiaDEPS(FakeReposBase): """Simulates the Skia DEPS transition in Chrome.""" diff --git a/tests/gclient_git_smoketest.py b/tests/gclient_git_smoketest.py index c3b9c7b7b..c4415da2e 100755 --- a/tests/gclient_git_smoketest.py +++ b/tests/gclient_git_smoketest.py @@ -32,6 +32,44 @@ class GClientSmokeGIT(gclient_smoketest_base.GClientSmokeBase): if not self.enabled: self.skipTest('git fake repos not available') + def testGitmodules_relative(self): + self.gclient(['config', self.git_base + 'repo_19', '--name', 'dir'], + cwd=self.git_base + 'repo_19') + self.gclient(['sync'], cwd=self.git_base + 'repo_19') + self.gclient(['gitmodules'], + cwd=self.git_base + os.path.join('repo_19', 'dir')) + + gitmodules = os.path.join(self.git_base, 'repo_19', 'dir', '.gitmodules') + with open(gitmodules) as f: + contents = f.read().splitlines() + self.assertEqual([ + '[submodule "some_repo"]', '\tpath = some_repo', '\turl = /repo_2', + '\tgclient-condition = not foo_checkout', + '[submodule "chicken/dickens"]', '\tpath = chicken/dickens', + '\turl = /repo_3' + ], contents) + + def testGitmodules_not_relative(self): + self.gclient(['config', self.git_base + 'repo_20', '--name', 'foo'], + cwd=self.git_base + 'repo_20') + self.gclient(['sync'], cwd=self.git_base + 'repo_20') + self.gclient(['gitmodules'], + cwd=self.git_base + os.path.join('repo_20', 'foo')) + + gitmodules = os.path.join(self.git_base, 'repo_20', 'foo', '.gitmodules') + with open(gitmodules) as f: + contents = f.read().splitlines() + self.assertEqual([ + '[submodule "some_repo"]', '\tpath = some_repo', '\turl = /repo_2', + '\tgclient-condition = not foo_checkout', + '[submodule "chicken/dickens"]', '\tpath = chicken/dickens', + '\turl = /repo_3' + ], contents) + + def testGitmodules_not_in_gclient(self): + with self.assertRaisesRegex(AssertionError, 'from a gclient workspace'): + self.gclient(['gitmodules'], cwd=self.root_dir) + def testSync(self): self.gclient(['config', self.git_base + 'repo_1', '--name', 'src']) # Test unversioned checkout.