1#!/usr/bin/env python3 2# Copyright 2020 The Pigweed Authors 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); you may not 5# use this file except in compliance with the License. You may obtain a copy of 6# the License at 7# 8# https://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13# License for the specific language governing permissions and limitations under 14# the License. 15"""Creates a Git hook that calls a script with certain arguments.""" 16 17import argparse 18import logging 19import os 20from pathlib import Path 21import re 22import shlex 23import subprocess 24from typing import Sequence, Union 25 26_LOG: logging.Logger = logging.getLogger(__name__) 27 28 29def git_repo_root(path: Union[Path, str]) -> Path: 30 return Path( 31 subprocess.run(['git', '-C', path, 'rev-parse', '--show-toplevel'], 32 check=True, 33 stdout=subprocess.PIPE).stdout.strip().decode()) 34 35 36def install_hook(script, 37 hook: str, 38 args: Sequence[str] = (), 39 repository: Union[Path, str] = '.') -> None: 40 """Installs a simple Git hook that calls a script with arguments.""" 41 root = git_repo_root(repository).resolve() 42 script = os.path.relpath(script, root) 43 44 if root.joinpath('.git').is_dir(): 45 hook_path = root.joinpath('.git', 'hooks', hook) 46 else: # This repo is probably a submodule with a .git file instead 47 match = re.match('^gitdir: (.*)$', root.joinpath('.git').read_text()) 48 if not match: 49 raise ValueError('Unexpected format for .git file') 50 51 hook_path = root.joinpath(match.group(1), 'hooks', hook).resolve() 52 53 hook_path.parent.mkdir(exist_ok=True) 54 55 command = ' '.join(shlex.quote(arg) for arg in (script, *args)) 56 57 with hook_path.open('w') as file: 58 line = lambda *args: print(*args, file=file) 59 60 line('#!/bin/sh') 61 line(f'# {hook} hook generated by {__file__}') 62 line() 63 line(command) 64 65 hook_path.chmod(0o755) 66 logging.info('Created %s hook for %s at %s', hook, script, hook_path) 67 68 69def argument_parser(parser=None) -> argparse.ArgumentParser: 70 if parser is None: 71 parser = argparse.ArgumentParser(description=__doc__) 72 73 def path(arg: str) -> Path: 74 if not os.path.exists(arg): 75 raise argparse.ArgumentTypeError(f'"{arg}" is not a valid path') 76 77 return Path(arg) 78 79 parser.add_argument( 80 '-r', 81 '--repository', 82 default='.', 83 type=path, 84 help='Path to the repository in which to install the hook') 85 parser.add_argument('--hook', 86 required=True, 87 help='Which type of Git hook to create') 88 parser.add_argument('-s', 89 '--script', 90 required=True, 91 type=path, 92 help='Path to the script to execute in the hook') 93 parser.add_argument('args', 94 nargs='*', 95 help='Arguments to provide to the commit hook') 96 97 return parser 98 99 100if __name__ == '__main__': 101 logging.basicConfig(format='%(message)s', level=logging.INFO) 102 install_hook(**vars(argument_parser().parse_args())) 103