• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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