|
1
|
|
|
import sys |
|
2
|
|
|
from subprocess import STDOUT |
|
3
|
|
|
from subprocess import CalledProcessError |
|
4
|
|
|
from subprocess import call |
|
5
|
|
|
from subprocess import check_output |
|
6
|
|
|
|
|
7
|
|
|
if sys.version_info[0] > 2: |
|
8
|
|
|
NOT_A_GIT_REPO_STDERR = b'Not a git repository' |
|
9
|
|
|
else: |
|
10
|
|
|
NOT_A_GIT_REPO_STDERR = 'Not a git repository' |
|
11
|
|
|
|
|
12
|
|
|
|
|
13
|
|
|
class GitRepoNotFound(CalledProcessError): |
|
14
|
|
|
"""Raised when trying to instantiate a GitRepo object on a file path that is not a git repo.""" |
|
15
|
|
|
pass |
|
16
|
|
|
|
|
17
|
|
|
|
|
18
|
|
|
class GitCommandException(CalledProcessError): |
|
19
|
|
|
"""Raised for any other git command failures.""" |
|
20
|
|
|
pass |
|
21
|
|
|
|
|
22
|
|
|
|
|
23
|
|
|
# def is_git_url(url=None): |
|
24
|
|
|
# return bool(re.match(r'((git|ssh|http(s)?)|(git@[\w\.]+))(:(//)?)([\w\.@\:/\-~]+)(\.git)(/)?'), url) |
|
25
|
|
|
|
|
26
|
|
|
|
|
27
|
|
|
class GitRepo(object): |
|
28
|
|
|
def __init__(self, path=None): |
|
29
|
|
|
self._path = path |
|
30
|
|
|
self.base_command = ['git'] |
|
31
|
|
|
if path is not None: |
|
32
|
|
|
self.base_command += ['-C', path] |
|
33
|
|
|
try: |
|
34
|
|
|
# Ensure that either the current working directory, or the specified path, is part of a git repository. |
|
35
|
|
|
self._check_output(['rev-parse']) |
|
36
|
|
|
except GitCommandException as command_error: |
|
37
|
|
|
if NOT_A_GIT_REPO_STDERR in command_error.output: |
|
38
|
|
|
raise GitRepoNotFound(*command_error.args) |
|
39
|
|
|
|
|
40
|
|
|
def _check_output(self, command): |
|
41
|
|
|
"""Wrap the call to subprocess.check_output() to raise customized exceptions and always return strings.""" |
|
42
|
|
|
try: |
|
43
|
|
|
if sys.version_info[0] > 2: |
|
44
|
|
|
return check_output(self.base_command + command, stderr=STDOUT).decode('utf8') |
|
45
|
|
|
else: |
|
46
|
|
|
return check_output(self.base_command + command, stderr=STDOUT) |
|
47
|
|
|
except CalledProcessError as command_error: |
|
48
|
|
|
raise GitCommandException(*command_error.args) |
|
49
|
|
|
|
|
50
|
|
|
def _call(self, command): |
|
51
|
|
|
return call(self.base_command + command) |
|
52
|
|
|
|
|
53
|
|
|
@property |
|
54
|
|
|
def top_level_directory_path(self): |
|
55
|
|
|
"""What is the top level directory of the git repo.""" |
|
56
|
|
|
return self._check_output(['rev-parse', '--show-toplevel']) |
|
57
|
|
|
|
|
58
|
|
|
@property |
|
59
|
|
|
def is_dirty(self): |
|
60
|
|
|
return bool(self._call(['diff', '--cached', '--quiet'])) |
|
61
|
|
|
|
|
62
|
|
|
@property |
|
63
|
|
|
def branch(self): |
|
64
|
|
|
return self._check_output(['branch'])[2:-1] |
|
65
|
|
|
|
|
66
|
|
|
@property |
|
67
|
|
|
def configured_options(self): |
|
68
|
|
|
"""What are the configured options in the git repo.""" |
|
69
|
|
|
stdout_lines = self._check_output(['config', '--list']).splitlines() |
|
70
|
|
|
return {key: value for key, value in [line.split('=') for line in stdout_lines]} |
|
71
|
|
|
|
|
72
|
|
|
@property |
|
73
|
|
|
def remotes(self): |
|
74
|
|
|
return list(set( |
|
75
|
|
|
[conf_var[1] for conf_var in [key.split('.') for key in self.configured_options.keys()] |
|
76
|
|
|
if conf_var[0] == 'remote'] |
|
77
|
|
|
)) |
|
78
|
|
|
|
|
79
|
|
|
@property |
|
80
|
|
|
def remote_urls(self): |
|
81
|
|
|
return {remote: self.configured_options.get('remote.{}.url'.format(remote)) for remote in self.remotes} |
|
82
|
|
|
|