xref: /aosp_15_r20/prebuilts/checkstyle/gitlint/git.py (revision 387726c4b5c67c6b48512fa4a28a3b8997d21b0d)
1*387726c4SAndroid Build Coastguard Worker# Copyright 2013-2014 Sebastian Kreft
2*387726c4SAndroid Build Coastguard Worker#
3*387726c4SAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
4*387726c4SAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
5*387726c4SAndroid Build Coastguard Worker# You may obtain a copy of the License at
6*387726c4SAndroid Build Coastguard Worker#
7*387726c4SAndroid Build Coastguard Worker#     http://www.apache.org/licenses/LICENSE-2.0
8*387726c4SAndroid Build Coastguard Worker#
9*387726c4SAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
10*387726c4SAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
11*387726c4SAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*387726c4SAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
13*387726c4SAndroid Build Coastguard Worker# limitations under the License.
14*387726c4SAndroid Build Coastguard Worker"""Functions to get information from git."""
15*387726c4SAndroid Build Coastguard Worker
16*387726c4SAndroid Build Coastguard Workerimport os.path
17*387726c4SAndroid Build Coastguard Workerimport subprocess
18*387726c4SAndroid Build Coastguard Worker
19*387726c4SAndroid Build Coastguard Workerimport gitlint.utils as utils
20*387726c4SAndroid Build Coastguard Worker
21*387726c4SAndroid Build Coastguard Worker
22*387726c4SAndroid Build Coastguard Workerdef repository_root():
23*387726c4SAndroid Build Coastguard Worker    """Returns the root of the repository as an absolute path."""
24*387726c4SAndroid Build Coastguard Worker    try:
25*387726c4SAndroid Build Coastguard Worker        root = subprocess.check_output(['git', 'rev-parse', '--show-toplevel'],
26*387726c4SAndroid Build Coastguard Worker                                       stderr=subprocess.STDOUT).strip()
27*387726c4SAndroid Build Coastguard Worker        # Convert to unicode first
28*387726c4SAndroid Build Coastguard Worker        return root.decode('utf-8')
29*387726c4SAndroid Build Coastguard Worker    except subprocess.CalledProcessError:
30*387726c4SAndroid Build Coastguard Worker        return None
31*387726c4SAndroid Build Coastguard Worker
32*387726c4SAndroid Build Coastguard Worker
33*387726c4SAndroid Build Coastguard Workerdef last_commit():
34*387726c4SAndroid Build Coastguard Worker    """Returns the SHA1 of the last commit."""
35*387726c4SAndroid Build Coastguard Worker    try:
36*387726c4SAndroid Build Coastguard Worker        root = subprocess.check_output(['git', 'rev-parse', 'HEAD'],
37*387726c4SAndroid Build Coastguard Worker                                       stderr=subprocess.STDOUT).strip()
38*387726c4SAndroid Build Coastguard Worker        # Convert to unicode first
39*387726c4SAndroid Build Coastguard Worker        return root.decode('utf-8')
40*387726c4SAndroid Build Coastguard Worker    except subprocess.CalledProcessError:
41*387726c4SAndroid Build Coastguard Worker        return None
42*387726c4SAndroid Build Coastguard Worker
43*387726c4SAndroid Build Coastguard Worker
44*387726c4SAndroid Build Coastguard Workerdef _remove_filename_quotes(filename):
45*387726c4SAndroid Build Coastguard Worker    """Removes the quotes from a filename returned by git status."""
46*387726c4SAndroid Build Coastguard Worker    if filename.startswith('"') and filename.endswith('"'):
47*387726c4SAndroid Build Coastguard Worker        return filename[1:-1]
48*387726c4SAndroid Build Coastguard Worker
49*387726c4SAndroid Build Coastguard Worker    return filename
50*387726c4SAndroid Build Coastguard Worker
51*387726c4SAndroid Build Coastguard Worker
52*387726c4SAndroid Build Coastguard Workerdef modified_files(root, tracked_only=False, commit=None):
53*387726c4SAndroid Build Coastguard Worker    """Returns a list of files that has been modified since the last commit.
54*387726c4SAndroid Build Coastguard Worker
55*387726c4SAndroid Build Coastguard Worker    Args:
56*387726c4SAndroid Build Coastguard Worker      root: the root of the repository, it has to be an absolute path.
57*387726c4SAndroid Build Coastguard Worker      tracked_only: exclude untracked files when True.
58*387726c4SAndroid Build Coastguard Worker      commit: SHA1 of the commit. If None, it will get the modified files in the
59*387726c4SAndroid Build Coastguard Worker        working copy.
60*387726c4SAndroid Build Coastguard Worker
61*387726c4SAndroid Build Coastguard Worker    Returns: a dictionary with the modified files as keys, and additional
62*387726c4SAndroid Build Coastguard Worker      information as value. In this case it adds the status returned by
63*387726c4SAndroid Build Coastguard Worker      git status.
64*387726c4SAndroid Build Coastguard Worker    """
65*387726c4SAndroid Build Coastguard Worker    assert os.path.isabs(root), "Root has to be absolute, got: %s" % root
66*387726c4SAndroid Build Coastguard Worker
67*387726c4SAndroid Build Coastguard Worker    if commit:
68*387726c4SAndroid Build Coastguard Worker        return _modified_files_with_commit(root, commit)
69*387726c4SAndroid Build Coastguard Worker
70*387726c4SAndroid Build Coastguard Worker    # Convert to unicode and split
71*387726c4SAndroid Build Coastguard Worker    status_lines = subprocess.check_output([
72*387726c4SAndroid Build Coastguard Worker        'git', 'status', '--porcelain', '--untracked-files=all',
73*387726c4SAndroid Build Coastguard Worker        '--ignore-submodules=all']).decode('utf-8').split(os.linesep)
74*387726c4SAndroid Build Coastguard Worker
75*387726c4SAndroid Build Coastguard Worker    modes = ['M ', ' M', 'A ', 'AM', 'MM']
76*387726c4SAndroid Build Coastguard Worker    if not tracked_only:
77*387726c4SAndroid Build Coastguard Worker        modes.append(r'\?\?')
78*387726c4SAndroid Build Coastguard Worker    modes_str = '|'.join(modes)
79*387726c4SAndroid Build Coastguard Worker
80*387726c4SAndroid Build Coastguard Worker    modified_file_status = utils.filter_lines(
81*387726c4SAndroid Build Coastguard Worker        status_lines,
82*387726c4SAndroid Build Coastguard Worker        r'(?P<mode>%s) (?P<filename>.+)' % modes_str,
83*387726c4SAndroid Build Coastguard Worker        groups=('filename', 'mode'))
84*387726c4SAndroid Build Coastguard Worker
85*387726c4SAndroid Build Coastguard Worker    return dict((os.path.join(root, _remove_filename_quotes(filename)), mode)
86*387726c4SAndroid Build Coastguard Worker                for filename, mode in modified_file_status)
87*387726c4SAndroid Build Coastguard Worker
88*387726c4SAndroid Build Coastguard Worker
89*387726c4SAndroid Build Coastguard Workerdef _modified_files_with_commit(root, commit):
90*387726c4SAndroid Build Coastguard Worker    # Convert to unicode and split
91*387726c4SAndroid Build Coastguard Worker    status_lines = subprocess.check_output(
92*387726c4SAndroid Build Coastguard Worker        ['git', 'diff-tree', '-r', '--root', '--no-commit-id', '--name-status',
93*387726c4SAndroid Build Coastguard Worker         commit]).decode('utf-8').split(os.linesep)
94*387726c4SAndroid Build Coastguard Worker
95*387726c4SAndroid Build Coastguard Worker    modified_file_status = utils.filter_lines(
96*387726c4SAndroid Build Coastguard Worker        status_lines,
97*387726c4SAndroid Build Coastguard Worker        r'(?P<mode>A|M)\s(?P<filename>.+)',
98*387726c4SAndroid Build Coastguard Worker        groups=('filename', 'mode'))
99*387726c4SAndroid Build Coastguard Worker
100*387726c4SAndroid Build Coastguard Worker    # We need to add a space to the mode, so to be compatible with the output
101*387726c4SAndroid Build Coastguard Worker    # generated by modified files.
102*387726c4SAndroid Build Coastguard Worker    return dict((os.path.join(root, _remove_filename_quotes(filename)),
103*387726c4SAndroid Build Coastguard Worker                 mode + ' ') for filename, mode in modified_file_status)
104*387726c4SAndroid Build Coastguard Worker
105*387726c4SAndroid Build Coastguard Worker
106*387726c4SAndroid Build Coastguard Workerdef modified_lines(filename, extra_data, commit=None):
107*387726c4SAndroid Build Coastguard Worker    """Returns the lines that have been modifed for this file.
108*387726c4SAndroid Build Coastguard Worker
109*387726c4SAndroid Build Coastguard Worker    Args:
110*387726c4SAndroid Build Coastguard Worker      filename: the file to check.
111*387726c4SAndroid Build Coastguard Worker      extra_data: is the extra_data returned by modified_files. Additionally, a
112*387726c4SAndroid Build Coastguard Worker        value of None means that the file was not modified.
113*387726c4SAndroid Build Coastguard Worker      commit: the complete sha1 (40 chars) of the commit.
114*387726c4SAndroid Build Coastguard Worker
115*387726c4SAndroid Build Coastguard Worker    Returns: a list of lines that were modified, or None in case all lines are
116*387726c4SAndroid Build Coastguard Worker      new.
117*387726c4SAndroid Build Coastguard Worker    """
118*387726c4SAndroid Build Coastguard Worker    if extra_data is None:
119*387726c4SAndroid Build Coastguard Worker        return []
120*387726c4SAndroid Build Coastguard Worker    if extra_data not in ('M ', ' M', 'MM'):
121*387726c4SAndroid Build Coastguard Worker        return None
122*387726c4SAndroid Build Coastguard Worker
123*387726c4SAndroid Build Coastguard Worker    if commit is None:
124*387726c4SAndroid Build Coastguard Worker        commit = '0' * 40
125*387726c4SAndroid Build Coastguard Worker    commit = commit.encode('utf-8')
126*387726c4SAndroid Build Coastguard Worker
127*387726c4SAndroid Build Coastguard Worker    # Split as bytes, as the output may have some non unicode characters.
128*387726c4SAndroid Build Coastguard Worker    blame_lines = subprocess.check_output(
129*387726c4SAndroid Build Coastguard Worker        ['git', 'blame', (commit + b'^!'), '--porcelain', '--', filename]).split(
130*387726c4SAndroid Build Coastguard Worker            os.linesep.encode('utf-8'))
131*387726c4SAndroid Build Coastguard Worker    modified_line_numbers = utils.filter_lines(
132*387726c4SAndroid Build Coastguard Worker        blame_lines,
133*387726c4SAndroid Build Coastguard Worker        commit + br' (?P<line>\d+) (\d+)',
134*387726c4SAndroid Build Coastguard Worker        groups=('line',))
135*387726c4SAndroid Build Coastguard Worker
136*387726c4SAndroid Build Coastguard Worker    return list(map(int, modified_line_numbers))
137