You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
137 lines
4.6 KiB
137 lines
4.6 KiB
4 months ago
|
# Copyright 2013-2014 Sebastian Kreft
|
||
|
#
|
||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
# you may not use this file except in compliance with the License.
|
||
|
# You may obtain a copy of the License at
|
||
|
#
|
||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
# See the License for the specific language governing permissions and
|
||
|
# limitations under the License.
|
||
|
"""Functions to get information from git."""
|
||
|
|
||
|
import os.path
|
||
|
import subprocess
|
||
|
|
||
|
import gitlint.utils as utils
|
||
|
|
||
|
|
||
|
def repository_root():
|
||
|
"""Returns the root of the repository as an absolute path."""
|
||
|
try:
|
||
|
root = subprocess.check_output(['git', 'rev-parse', '--show-toplevel'],
|
||
|
stderr=subprocess.STDOUT).strip()
|
||
|
# Convert to unicode first
|
||
|
return root.decode('utf-8')
|
||
|
except subprocess.CalledProcessError:
|
||
|
return None
|
||
|
|
||
|
|
||
|
def last_commit():
|
||
|
"""Returns the SHA1 of the last commit."""
|
||
|
try:
|
||
|
root = subprocess.check_output(['git', 'rev-parse', 'HEAD'],
|
||
|
stderr=subprocess.STDOUT).strip()
|
||
|
# Convert to unicode first
|
||
|
return root.decode('utf-8')
|
||
|
except subprocess.CalledProcessError:
|
||
|
return None
|
||
|
|
||
|
|
||
|
def _remove_filename_quotes(filename):
|
||
|
"""Removes the quotes from a filename returned by git status."""
|
||
|
if filename.startswith('"') and filename.endswith('"'):
|
||
|
return filename[1:-1]
|
||
|
|
||
|
return filename
|
||
|
|
||
|
|
||
|
def modified_files(root, tracked_only=False, commit=None):
|
||
|
"""Returns a list of files that has been modified since the last commit.
|
||
|
|
||
|
Args:
|
||
|
root: the root of the repository, it has to be an absolute path.
|
||
|
tracked_only: exclude untracked files when True.
|
||
|
commit: SHA1 of the commit. If None, it will get the modified files in the
|
||
|
working copy.
|
||
|
|
||
|
Returns: a dictionary with the modified files as keys, and additional
|
||
|
information as value. In this case it adds the status returned by
|
||
|
git status.
|
||
|
"""
|
||
|
assert os.path.isabs(root), "Root has to be absolute, got: %s" % root
|
||
|
|
||
|
if commit:
|
||
|
return _modified_files_with_commit(root, commit)
|
||
|
|
||
|
# Convert to unicode and split
|
||
|
status_lines = subprocess.check_output([
|
||
|
'git', 'status', '--porcelain', '--untracked-files=all',
|
||
|
'--ignore-submodules=all']).decode('utf-8').split(os.linesep)
|
||
|
|
||
|
modes = ['M ', ' M', 'A ', 'AM', 'MM']
|
||
|
if not tracked_only:
|
||
|
modes.append(r'\?\?')
|
||
|
modes_str = '|'.join(modes)
|
||
|
|
||
|
modified_file_status = utils.filter_lines(
|
||
|
status_lines,
|
||
|
r'(?P<mode>%s) (?P<filename>.+)' % modes_str,
|
||
|
groups=('filename', 'mode'))
|
||
|
|
||
|
return dict((os.path.join(root, _remove_filename_quotes(filename)), mode)
|
||
|
for filename, mode in modified_file_status)
|
||
|
|
||
|
|
||
|
def _modified_files_with_commit(root, commit):
|
||
|
# Convert to unicode and split
|
||
|
status_lines = subprocess.check_output(
|
||
|
['git', 'diff-tree', '-r', '--root', '--no-commit-id', '--name-status',
|
||
|
commit]).decode('utf-8').split(os.linesep)
|
||
|
|
||
|
modified_file_status = utils.filter_lines(
|
||
|
status_lines,
|
||
|
r'(?P<mode>A|M)\s(?P<filename>.+)',
|
||
|
groups=('filename', 'mode'))
|
||
|
|
||
|
# We need to add a space to the mode, so to be compatible with the output
|
||
|
# generated by modified files.
|
||
|
return dict((os.path.join(root, _remove_filename_quotes(filename)),
|
||
|
mode + ' ') for filename, mode in modified_file_status)
|
||
|
|
||
|
|
||
|
def modified_lines(filename, extra_data, commit=None):
|
||
|
"""Returns the lines that have been modifed for this file.
|
||
|
|
||
|
Args:
|
||
|
filename: the file to check.
|
||
|
extra_data: is the extra_data returned by modified_files. Additionally, a
|
||
|
value of None means that the file was not modified.
|
||
|
commit: the complete sha1 (40 chars) of the commit.
|
||
|
|
||
|
Returns: a list of lines that were modified, or None in case all lines are
|
||
|
new.
|
||
|
"""
|
||
|
if extra_data is None:
|
||
|
return []
|
||
|
if extra_data not in ('M ', ' M', 'MM'):
|
||
|
return None
|
||
|
|
||
|
if commit is None:
|
||
|
commit = '0' * 40
|
||
|
commit = commit.encode('utf-8')
|
||
|
|
||
|
# Split as bytes, as the output may have some non unicode characters.
|
||
|
blame_lines = subprocess.check_output(
|
||
|
['git', 'blame', (commit + b'^!'), '--porcelain', '--', filename]).split(
|
||
|
os.linesep.encode('utf-8'))
|
||
|
modified_line_numbers = utils.filter_lines(
|
||
|
blame_lines,
|
||
|
commit + br' (?P<line>\d+) (\d+)',
|
||
|
groups=('line',))
|
||
|
|
||
|
return list(map(int, modified_line_numbers))
|