Skip to content

Commit e583a9e

Browse files
committed
Added version_info property to git command. Its cached and efficient, including test
1 parent e11f12a commit e583a9e

File tree

2 files changed

+106
-71
lines changed

2 files changed

+106
-71
lines changed

git/cmd.py

+24-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
66

77
import os, sys
8-
from util import *
8+
from util import (
9+
LazyMixin,
10+
stream_copy
11+
)
912
from exc import GitCommandError
1013

1114
from subprocess import (
@@ -26,7 +29,7 @@
2629
def dashify(string):
2730
return string.replace('_', '-')
2831

29-
class Git(object):
32+
class Git(LazyMixin):
3033
"""
3134
The Git class manages communication with the Git binary.
3235
@@ -41,7 +44,7 @@ class Git(object):
4144
of the command to stdout.
4245
Set its value to 'full' to see details about the returned values.
4346
"""
44-
__slots__ = ("_working_dir", "cat_file_all", "cat_file_header")
47+
__slots__ = ("_working_dir", "cat_file_all", "cat_file_header", "_version_info")
4548

4649
# CONFIGURATION
4750
# The size in bytes read from stdout when copying git's output to another stream
@@ -214,14 +217,30 @@ def __getattr__(self, name):
214217
"""A convenience method as it allows to call the command as if it was
215218
an object.
216219
:return: Callable object that will execute call _call_process with your arguments."""
217-
if name[:1] == '_':
218-
raise AttributeError(name)
220+
if name[0] == '_':
221+
return LazyMixin.__getattr__(self, name)
219222
return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)
220223

224+
def _set_cache_(self, attr):
225+
if attr == '_version_info':
226+
version_numbers = self._call_process('version').rpartition(' ')[2]
227+
self._version_info = tuple(int(n) for n in version_numbers.split('.'))
228+
else:
229+
super(Git, self)._set_cache_(attr)
230+
#END handle version info
231+
232+
221233
@property
222234
def working_dir(self):
223235
""":return: Git directory we are working on"""
224236
return self._working_dir
237+
238+
@property
239+
def version_info(self):
240+
""":return: tuple(int, ...) tuple with integers representing the major, minor
241+
and additional version numbers as parsed from git version.
242+
This value is generated on demand and is cached"""
243+
return self._version_info
225244

226245
def execute(self, command,
227246
istream=None,

git/test/test_git.py

+82-66
Original file line numberDiff line numberDiff line change
@@ -5,80 +5,96 @@
55
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
66

77
import os, sys
8-
from git.test.lib import *
8+
from git.test.lib import (
9+
TestBase,
10+
patch_object,
11+
raises,
12+
assert_equal,
13+
assert_true,
14+
assert_match,
15+
fixture_path
16+
)
917
from git import Git, GitCommandError
1018

11-
class TestGit(TestCase):
12-
13-
@classmethod
14-
def setUpAll(cls):
15-
cls.git = Git(GIT_REPO)
19+
class TestGit(TestBase):
20+
21+
@classmethod
22+
def setUpAll(cls):
23+
super(TestGit, cls).setUpAll()
24+
cls.git = Git(cls.rorepo.working_dir)
1625

17-
@patch_object(Git, 'execute')
18-
def test_call_process_calls_execute(self, git):
19-
git.return_value = ''
20-
self.git.version()
21-
assert_true(git.called)
22-
assert_equal(git.call_args, ((['git', 'version'],), {}))
26+
@patch_object(Git, 'execute')
27+
def test_call_process_calls_execute(self, git):
28+
git.return_value = ''
29+
self.git.version()
30+
assert_true(git.called)
31+
assert_equal(git.call_args, ((['git', 'version'],), {}))
2332

24-
@raises(GitCommandError)
25-
def test_it_raises_errors(self):
26-
self.git.this_does_not_exist()
33+
@raises(GitCommandError)
34+
def test_it_raises_errors(self):
35+
self.git.this_does_not_exist()
2736

2837

29-
def test_it_transforms_kwargs_into_git_command_arguments(self):
30-
assert_equal(["-s"], self.git.transform_kwargs(**{'s': True}))
31-
assert_equal(["-s5"], self.git.transform_kwargs(**{'s': 5}))
38+
def test_it_transforms_kwargs_into_git_command_arguments(self):
39+
assert_equal(["-s"], self.git.transform_kwargs(**{'s': True}))
40+
assert_equal(["-s5"], self.git.transform_kwargs(**{'s': 5}))
3241

33-
assert_equal(["--max-count"], self.git.transform_kwargs(**{'max_count': True}))
34-
assert_equal(["--max-count=5"], self.git.transform_kwargs(**{'max_count': 5}))
42+
assert_equal(["--max-count"], self.git.transform_kwargs(**{'max_count': True}))
43+
assert_equal(["--max-count=5"], self.git.transform_kwargs(**{'max_count': 5}))
3544

36-
assert_equal(["-s", "-t"], self.git.transform_kwargs(**{'s': True, 't': True}))
45+
assert_equal(["-s", "-t"], self.git.transform_kwargs(**{'s': True, 't': True}))
3746

38-
def test_it_executes_git_to_shell_and_returns_result(self):
39-
assert_match('^git version [\d\.]{2}.*$', self.git.execute(["git","version"]))
47+
def test_it_executes_git_to_shell_and_returns_result(self):
48+
assert_match('^git version [\d\.]{2}.*$', self.git.execute(["git","version"]))
4049

41-
def test_it_accepts_stdin(self):
42-
filename = fixture_path("cat_file_blob")
43-
fh = open(filename, 'r')
44-
assert_equal("70c379b63ffa0795fdbfbc128e5a2818397b7ef8",
45-
self.git.hash_object(istream=fh, stdin=True))
46-
fh.close()
50+
def test_it_accepts_stdin(self):
51+
filename = fixture_path("cat_file_blob")
52+
fh = open(filename, 'r')
53+
assert_equal("70c379b63ffa0795fdbfbc128e5a2818397b7ef8",
54+
self.git.hash_object(istream=fh, stdin=True))
55+
fh.close()
4756

48-
@patch_object(Git, 'execute')
49-
def test_it_ignores_false_kwargs(self, git):
50-
# this_should_not_be_ignored=False implies it *should* be ignored
51-
output = self.git.version(pass_this_kwarg=False)
52-
assert_true("pass_this_kwarg" not in git.call_args[1])
53-
54-
def test_persistent_cat_file_command(self):
55-
# read header only
56-
import subprocess as sp
57-
hexsha = "b2339455342180c7cc1e9bba3e9f181f7baa5167"
58-
g = self.git.cat_file(batch_check=True, istream=sp.PIPE,as_process=True)
59-
g.stdin.write("b2339455342180c7cc1e9bba3e9f181f7baa5167\n")
60-
g.stdin.flush()
61-
obj_info = g.stdout.readline()
62-
63-
# read header + data
64-
g = self.git.cat_file(batch=True, istream=sp.PIPE,as_process=True)
65-
g.stdin.write("b2339455342180c7cc1e9bba3e9f181f7baa5167\n")
66-
g.stdin.flush()
67-
obj_info_two = g.stdout.readline()
68-
assert obj_info == obj_info_two
69-
70-
# read data - have to read it in one large chunk
71-
size = int(obj_info.split()[2])
72-
data = g.stdout.read(size)
73-
terminating_newline = g.stdout.read(1)
74-
75-
# now we should be able to read a new object
76-
g.stdin.write("b2339455342180c7cc1e9bba3e9f181f7baa5167\n")
77-
g.stdin.flush()
78-
assert g.stdout.readline() == obj_info
79-
80-
81-
# same can be achived using the respective command functions
82-
hexsha, typename, size = self.git.get_object_header(hexsha)
83-
hexsha, typename_two, size_two, data = self.git.get_object_data(hexsha)
84-
assert typename == typename_two and size == size_two
57+
@patch_object(Git, 'execute')
58+
def test_it_ignores_false_kwargs(self, git):
59+
# this_should_not_be_ignored=False implies it *should* be ignored
60+
output = self.git.version(pass_this_kwarg=False)
61+
assert_true("pass_this_kwarg" not in git.call_args[1])
62+
63+
def test_persistent_cat_file_command(self):
64+
# read header only
65+
import subprocess as sp
66+
hexsha = "b2339455342180c7cc1e9bba3e9f181f7baa5167"
67+
g = self.git.cat_file(batch_check=True, istream=sp.PIPE,as_process=True)
68+
g.stdin.write("b2339455342180c7cc1e9bba3e9f181f7baa5167\n")
69+
g.stdin.flush()
70+
obj_info = g.stdout.readline()
71+
72+
# read header + data
73+
g = self.git.cat_file(batch=True, istream=sp.PIPE,as_process=True)
74+
g.stdin.write("b2339455342180c7cc1e9bba3e9f181f7baa5167\n")
75+
g.stdin.flush()
76+
obj_info_two = g.stdout.readline()
77+
assert obj_info == obj_info_two
78+
79+
# read data - have to read it in one large chunk
80+
size = int(obj_info.split()[2])
81+
data = g.stdout.read(size)
82+
terminating_newline = g.stdout.read(1)
83+
84+
# now we should be able to read a new object
85+
g.stdin.write("b2339455342180c7cc1e9bba3e9f181f7baa5167\n")
86+
g.stdin.flush()
87+
assert g.stdout.readline() == obj_info
88+
89+
90+
# same can be achived using the respective command functions
91+
hexsha, typename, size = self.git.get_object_header(hexsha)
92+
hexsha, typename_two, size_two, data = self.git.get_object_data(hexsha)
93+
assert typename == typename_two and size == size_two
94+
95+
def test_version(self):
96+
v = self.git.version_info
97+
assert isinstance(v, tuple)
98+
for n in v:
99+
assert isinstance(n, int)
100+
#END verify number types

0 commit comments

Comments
 (0)