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