Add gclient installhooks to add pre-commit hook
With submodules, users can accidentally stage and commit gitlink
changes. Add a new gclient command to install a pre-commit hook to
automatically drop gitlink changes that don't correspond to a DEPS
change.
Dropping gitlinks can be bypassed by setting
SKIP_GITLINK_PRECOMMIT=1.
Bug: 1481266
Change-Id: Idd8b273e7d8e37d52627964e8ed6004d068b6b7a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/4863221
Reviewed-by: Joanna Wang <jojwang@chromium.org>
Commit-Queue: Gavin Mak <gavinmak@google.com>
Reviewed-by: Josip Sokcevic <sokcevic@chromium.org>
diff --git a/gclient.py b/gclient.py
index 659fdb4..6a47709 100755
--- a/gclient.py
+++ b/gclient.py
@@ -128,6 +128,8 @@
NO_SYNC_EXPERIMENT = 'no-sync'
+PRECOMMIT_HOOK_VAR = 'GCLIENT_PRECOMMIT'
+
class GNException(Exception):
pass
@@ -1982,6 +1984,46 @@
patch_refs[patch_repo] = patch_ref
return patch_refs, target_branches
+ def _InstallPreCommitHook(self):
+ # On Windows, this path is written to the file as
+ # "dir\hooks\pre-commit.py" but it gets interpreted as
+ # "dirhookspre-commit.py".
+ gclient_hook_path = os.path.join(DEPOT_TOOLS_DIR, 'hooks',
+ 'pre-commit.py').replace('\\', '\\\\')
+ gclient_hook_content = '\n'.join((
+ f'{PRECOMMIT_HOOK_VAR}={gclient_hook_path}',
+ f'if [ -f "${PRECOMMIT_HOOK_VAR}" ]; then',
+ f' python3 "${PRECOMMIT_HOOK_VAR}" || exit 1',
+ 'fi',
+ ))
+
+ soln = gclient_paths.GetPrimarySolutionPath()
+ if not soln:
+ print('Could not find gclient solution.')
+ return
+
+ git_dir = os.path.join(soln, '.git')
+ if not os.path.exists(git_dir):
+ return
+
+ hook = os.path.join(git_dir, 'hooks', 'pre-commit')
+ if os.path.exists(hook):
+ with open(hook, 'r') as f:
+ content = f.read()
+ if PRECOMMIT_HOOK_VAR in content:
+ print(f'{hook} already contains the gclient pre-commit hook.')
+ else:
+ print(f'A pre-commit hook already exists at {hook}.\n'
+ f'Please append the following lines to the hook:\n\n'
+ f'{gclient_hook_content}')
+ return
+
+ print(f'Creating a pre-commit hook at {hook}')
+ with open(hook, 'w') as f:
+ f.write('#!/bin/sh\n')
+ f.write(f'{gclient_hook_content}\n')
+ os.chmod(hook, 0o755)
+
def _RemoveUnversionedGitDirs(self):
"""Remove directories that are no longer part of the checkout.
@@ -3552,6 +3594,23 @@
return client.RunOnDeps('runhooks', args)
+# TODO(crbug.com/1481266): Collect merics for installhooks.
+def CMDinstallhooks(parser, args):
+ """Installs gclient git hooks.
+
+ Currently only installs a pre-commit hook to drop staged gitlinks. To
+ bypass this pre-commit hook once it's installed, set the environment
+ variable SKIP_GITLINK_PRECOMMIT=1.
+ """
+ (options, args) = parser.parse_args(args)
+ client = GClient.LoadCurrentConfig(options)
+ if not client:
+ raise gclient_utils.Error(
+ 'client not configured; see \'gclient config\'')
+ client._InstallPreCommitHook()
+ return 0
+
+
@metrics.collector.collect_metrics('gclient revinfo')
def CMDrevinfo(parser, args):
"""Outputs revision info mapping for the client and its dependencies.