123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322 |
- #!/usr/bin/env python3
- # Copyright (c) 2023 The Chromium Authors. All rights reserved.
- # Use of this source code is governed by a BSD-style license that can be
- # found in the LICENSE file.
- import os
- import os.path
- import sys
- import tempfile
- import unittest
- import unittest.mock
- from unittest.mock import patch
- ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
- sys.path.insert(0, ROOT_DIR)
- from gclient import PRECOMMIT_HOOK_VAR
- import gclient_utils
- from gclient_eval import SYNC, SUBMODULES
- import git_common as git
- class HooksTest(unittest.TestCase):
- def setUp(self):
- super(HooksTest, self).setUp()
- self.repo = tempfile.mkdtemp()
- self.env = os.environ.copy()
- self.env['SKIP_GITLINK_PRECOMMIT'] = '0'
- self.env['TESTING_ANSWER'] = 'n'
- self.populate()
- def tearDown(self):
- gclient_utils.rmtree(self.repo)
- def write(self, repo, path, content):
- with open(os.path.join(repo, path), 'w') as f:
- f.write(content)
- def populate(self):
- git.run('init', cwd=self.repo)
- deps_content = '\n'.join((
- f'git_dependencies = "{SYNC}"',
- 'deps = {',
- f' "dep_a": "host://dep_a@{"a"*40}",',
- f' "dep_b": "host://dep_b@{"b"*40}",',
- '}',
- ))
- self.write(self.repo, 'DEPS', deps_content)
- self.dep_a_repo = os.path.join(self.repo, 'dep_a')
- os.mkdir(self.dep_a_repo)
- git.run('init', cwd=self.dep_a_repo)
- os.mkdir(os.path.join(self.repo, 'dep_b'))
- gitmodules_content = '\n'.join((
- '[submodule "dep_a"]'
- '\tpath = dep_a',
- '\turl = host://dep_a',
- '[submodule "dep_b"]'
- '\tpath = dep_b',
- '\turl = host://dep_b',
- ))
- self.write(self.repo, '.gitmodules', gitmodules_content)
- git.run('update-index',
- '--add',
- '--cacheinfo',
- f'160000,{"a"*40},dep_a',
- cwd=self.repo)
- git.run('update-index',
- '--add',
- '--cacheinfo',
- f'160000,{"b"*40},dep_b',
- cwd=self.repo)
- git.run('add', '.', cwd=self.repo)
- git.run('commit', '-m', 'init', cwd=self.repo)
- # On Windows, this path is written to the file as
- # "root_dir\hooks\pre-commit.py", but it gets interpreted as
- # "root_dirhookspre-commit.py".
- precommit_path = os.path.join(ROOT_DIR, 'hooks',
- 'pre-commit.py').replace('\\', '\\\\')
- precommit_content = '\n'.join((
- '#!/bin/sh',
- f'{PRECOMMIT_HOOK_VAR}={precommit_path}',
- f'if [ -f "${PRECOMMIT_HOOK_VAR}" ]; then',
- f' python3 "${PRECOMMIT_HOOK_VAR}" || exit 1',
- 'fi',
- ))
- self.write(self.repo, os.path.join('.git', 'hooks', 'pre-commit'),
- precommit_content)
- os.chmod(os.path.join(self.repo, '.git', 'hooks', 'pre-commit'), 0o755)
- def testPreCommit_NoGitlinkOrDEPS(self):
- # Sanity check. Neither gitlinks nor DEPS are touched.
- self.write(self.repo, 'foo', 'foo')
- git.run('add', '.', cwd=self.repo)
- expected_diff = git.run('diff', '--cached', cwd=self.repo)
- git.run('commit', '-m', 'foo', cwd=self.repo)
- self.assertEqual(expected_diff,
- git.run('diff', 'HEAD^', 'HEAD', cwd=self.repo))
- def testPreCommit_GitlinkWithoutDEPS(self):
- # Gitlink changes were staged without a corresponding DEPS change.
- self.write(self.repo, 'foo', 'foo')
- git.run('add', '.', cwd=self.repo)
- git.run('update-index',
- '--replace',
- '--cacheinfo',
- f'160000,{"b"*40},dep_a',
- cwd=self.repo)
- git.run('update-index',
- '--replace',
- '--cacheinfo',
- f'160000,{"a"*40},dep_b',
- cwd=self.repo)
- diff_before_commit = git.run('diff',
- '--cached',
- '--name-only',
- cwd=self.repo)
- _, stderr = git.run_with_stderr('commit',
- '-m',
- 'regular file and gitlinks',
- cwd=self.repo,
- env=self.env)
- self.assertIn('dep_a', diff_before_commit)
- self.assertIn('dep_b', diff_before_commit)
- # Gitlinks should be dropped.
- self.assertIn(
- 'Found no change to DEPS, but found staged gitlink(s) in diff',
- stderr)
- diff_after_commit = git.run('diff',
- '--name-only',
- 'HEAD^',
- 'HEAD',
- cwd=self.repo)
- self.assertNotIn('dep_a', diff_after_commit)
- self.assertNotIn('dep_b', diff_after_commit)
- self.assertIn('foo', diff_after_commit)
- def testPreCommit_IntentionalGitlinkWithoutDEPS(self):
- # Intentional Gitlink changes staged without a DEPS change.
- self.write(self.repo, 'foo', 'foo')
- git.run('add', '.', cwd=self.repo)
- git.run('update-index',
- '--replace',
- '--cacheinfo',
- f'160000,{"b"*40},dep_a',
- cwd=self.repo)
- git.run('update-index',
- '--replace',
- '--cacheinfo',
- f'160000,{"a"*40},dep_b',
- cwd=self.repo)
- diff_before_commit = git.run('diff',
- '--cached',
- '--name-only',
- cwd=self.repo)
- self.env['TESTING_ANSWER'] = ''
- _, stderr = git.run_with_stderr('commit',
- '-m',
- 'regular file and gitlinks',
- cwd=self.repo,
- env=self.env)
- self.assertIn('dep_a', diff_before_commit)
- self.assertIn('dep_b', diff_before_commit)
- # Gitlinks should be dropped.
- self.assertIn(
- 'Found no change to DEPS, but found staged gitlink(s) in diff',
- stderr)
- diff_after_commit = git.run('diff',
- '--name-only',
- 'HEAD^',
- 'HEAD',
- cwd=self.repo)
- self.assertIn('dep_a', diff_after_commit)
- self.assertIn('dep_b', diff_after_commit)
- self.assertIn('foo', diff_after_commit)
- def testPreCommit_OnlyGitlinkWithoutDEPS(self):
- # Gitlink changes were staged without a corresponding DEPS change but
- # no other files were included in the commit.
- git.run('update-index',
- '--replace',
- '--cacheinfo',
- f'160000,{"b"*40},dep_a',
- cwd=self.repo)
- diff_before_commit = git.run('diff',
- '--cached',
- '--name-only',
- cwd=self.repo)
- ret = git.run_with_retcode('commit',
- '-m',
- 'gitlink only',
- cwd=self.repo,
- env=self.env)
- self.assertIn('dep_a', diff_before_commit)
- # Gitlinks should be droppped and the empty commit should be aborted.
- self.assertEqual(ret, 1)
- diff_after_commit = git.run('diff',
- '--cached',
- '--name-only',
- cwd=self.repo)
- self.assertNotIn('dep_a', diff_after_commit)
- def testPreCommit_CommitAll(self):
- self.write(self.repo, 'foo', 'foo')
- git.run('add', '.', cwd=self.repo)
- git.run('commit', '-m', 'add foo', cwd=self.repo)
- self.write(self.repo, 'foo', 'foo2')
- # Create a new commit in dep_a.
- self.write(self.dep_a_repo, 'sub_foo', 'sub_foo')
- git.run('add', '.', cwd=self.dep_a_repo)
- git.run('commit', '-m', 'sub_foo', cwd=self.dep_a_repo)
- diff_before_commit = git.run('status',
- cwd=self.repo)
- self.assertIn('foo', diff_before_commit)
- self.assertIn('dep_a', diff_before_commit)
- ret = git.run_with_retcode('commit',
- '--all',
- '-m',
- 'commit all',
- cwd=self.repo,
- env=self.env)
- self.assertIn('dep_a', diff_before_commit)
- self.assertEqual(ret, 0)
- diff_after_commit = git.run('diff',
- '--cached',
- '--name-only',
- cwd=self.repo)
- self.assertNotIn('dep_a', diff_after_commit)
- diff_from_commit = git.run('diff',
- '--name-only',
- 'HEAD^',
- 'HEAD',
- cwd=self.repo)
- self.assertIn('foo', diff_from_commit)
- def testPreCommit_GitlinkWithDEPS(self):
- # A gitlink was staged with a corresponding DEPS change.
- updated_deps = '\n'.join((
- f'git_dependencies = "{SYNC}"',
- 'deps = {',
- f' "dep_a": "host://dep_a@{"b"*40}",',
- f' "dep_b": "host://dep_b@{"b"*40}",',
- '}',
- ))
- self.write(self.repo, 'DEPS', updated_deps)
- git.run('add', '.', cwd=self.repo)
- git.run('update-index',
- '--replace',
- '--cacheinfo',
- f'160000,{"b"*40},dep_a',
- cwd=self.repo)
- diff_before_commit = git.run('diff', '--cached', cwd=self.repo)
- git.run('commit', '-m', 'gitlink and DEPS', cwd=self.repo)
- # There should be no changes to the commit.
- diff_after_commit = git.run('diff', 'HEAD^', 'HEAD', cwd=self.repo)
- self.assertEqual(diff_before_commit, diff_after_commit)
- def testPreCommit_SkipPrecommit(self):
- # A gitlink was staged without a corresponding DEPS change but the
- # SKIP_GITLINK_PRECOMMIT envvar was set.
- git.run('update-index',
- '--replace',
- '--cacheinfo',
- f'160000,{"b"*40},dep_a',
- cwd=self.repo)
- diff_before_commit = git.run('diff',
- '--cached',
- '--name-only',
- cwd=self.repo)
- self.env['SKIP_GITLINK_PRECOMMIT'] = '1'
- git.run('commit',
- '-m',
- 'gitlink only, skipping precommit',
- cwd=self.repo,
- env=self.env)
- # Gitlink should be kept.
- self.assertIn('dep_a', diff_before_commit)
- diff_after_commit = git.run('diff',
- '--name-only',
- 'HEAD^',
- 'HEAD',
- cwd=self.repo)
- self.assertIn('dep_a', diff_after_commit)
- def testPreCommit_OtherDEPSState(self):
- # DEPS is set to a git_dependencies state other than SYNC.
- deps_content = '\n'.join((
- f'git_dependencies = \'{SUBMODULES}\'',
- 'deps = {',
- f' "dep_a": "host://dep_a@{"a"*40}",',
- f' "dep_b": "host://dep_b@{"b"*40}",',
- '}',
- ))
- self.write(self.repo, 'DEPS', deps_content)
- git.run('add', '.', cwd=self.repo)
- git.run('commit', '-m', 'change git_dependencies', cwd=self.repo)
- git.run('update-index',
- '--replace',
- '--cacheinfo',
- f'160000,{"b"*40},dep_a',
- cwd=self.repo)
- diff_before_commit = git.run('diff', '--cached', cwd=self.repo)
- git.run('commit', '-m', 'update dep_a', cwd=self.repo)
- # There should be no changes to the commit.
- diff_after_commit = git.run('diff', 'HEAD^', 'HEAD', cwd=self.repo)
- self.assertEqual(diff_before_commit, diff_after_commit)
- if __name__ == '__main__':
- unittest.main()
|