Skip to content

Commit ead94f2

Browse files
committed
cmd: added option to return the process directly, allowing to read the output directly from the output stream
commit: now reads commit information directly from the output stream of the process by implementing its iterator method repo: removed log method as it was redundant ( equal to the commits method )
1 parent ac1cec7 commit ead94f2

File tree

7 files changed

+75
-67
lines changed

7 files changed

+75
-67
lines changed

Diff for: CHANGES

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ Repo
4141
of the active branch.
4242
* tree method now requires a Ref instance as input and defaults to the active_branche
4343
instead of master
44+
* Removed 'log' method as it as effectively the same as the 'commits' method
4445

4546
Diff
4647
----

Diff for: lib/git/cmd.py

+45-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
GIT_PYTHON_TRACE = os.environ.get("GIT_PYTHON_TRACE", False)
1414

1515
execute_kwargs = ('istream', 'with_keep_cwd', 'with_extended_output',
16-
'with_exceptions', 'with_raw_output')
16+
'with_exceptions', 'with_raw_output', 'as_process')
1717

1818
extra = {}
1919
if sys.platform == 'win32':
@@ -34,6 +34,35 @@ class Git(object):
3434
of the command to stdout.
3535
Set its value to 'full' to see details about the returned values.
3636
"""
37+
38+
class AutoInterrupt(object):
39+
"""
40+
Kill/Interrupt the stored process instance once this instance goes out of scope. It is
41+
used to prevent processes piling up in case iterators stop reading.
42+
Besides all attributes are wired through to the contained process object
43+
"""
44+
__slots__= "proc"
45+
46+
def __init__(self, proc ):
47+
self.proc = proc
48+
49+
def __del__(self):
50+
# did the process finish already so we have a return code ?
51+
if self.proc.poll() is not None:
52+
return
53+
54+
# try to kill it
55+
try:
56+
os.kill(self.proc.pid, 2) # interrupt signal
57+
except AttributeError:
58+
# try windows
59+
subprocess.call(("TASKKILL", "/T", "/PID", self.proc.pid))
60+
# END exception handling
61+
62+
def __getattr__(self, attr):
63+
return getattr(self.proc, attr)
64+
65+
3766
def __init__(self, git_dir=None):
3867
"""
3968
Initialize this instance with:
@@ -70,6 +99,7 @@ def execute(self, command,
7099
with_extended_output=False,
71100
with_exceptions=True,
72101
with_raw_output=False,
102+
as_process=False
73103
):
74104
"""
75105
Handles executing the command on the shell and consumes and returns
@@ -96,6 +126,16 @@ def execute(self, command,
96126
97127
``with_raw_output``
98128
Whether to avoid stripping off trailing whitespace.
129+
130+
``as_process``
131+
Whether to return the created process instance directly from which
132+
streams can be read on demand. This will render with_extended_output,
133+
with_exceptions and with_raw_output ineffective - the caller will have
134+
to deal with the details himself.
135+
It is important to note that the process will be placed into an AutoInterrupt
136+
wrapper that will interrupt the process once it goes out of scope. If you
137+
use the command in iterators, you should pass the whole process instance
138+
instead of a single stream.
99139
100140
Returns::
101141
@@ -127,7 +167,11 @@ def execute(self, command,
127167
**extra
128168
)
129169

170+
if as_process:
171+
return self.AutoInterrupt(proc)
172+
130173
# Wait for the process to return
174+
status = 0
131175
try:
132176
stdout_value = proc.stdout.read()
133177
stderr_value = proc.stderr.read()

Diff for: lib/git/objects/commit.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -142,26 +142,28 @@ def iter_items(cls, repo, ref, path='', **kwargs):
142142
Returns
143143
iterator yielding Commit items
144144
"""
145-
options = {'pretty': 'raw'}
145+
options = {'pretty': 'raw', 'as_process' : True }
146146
options.update(kwargs)
147147

148-
output = repo.git.rev_list(ref, '--', path, **options)
149-
return cls._iter_from_stream(repo, iter(output.splitlines(False)))
148+
# the test system might confront us with string values -
149+
proc = repo.git.rev_list(ref, '--', path, **options)
150+
return cls._iter_from_process(repo, proc)
150151

151152
@classmethod
152-
def _iter_from_stream(cls, repo, stream):
153+
def _iter_from_process(cls, repo, proc):
153154
"""
154155
Parse out commit information into a list of Commit objects
155156
156157
``repo``
157158
is the Repo
158159
159-
``stream``
160-
output stream from the git-rev-list command (raw format)
160+
``proc``
161+
git-rev-list process instance (raw format)
161162
162163
Returns
163164
iterator returning Commit objects
164165
"""
166+
stream = proc.stdout
165167
for line in stream:
166168
id = line.split()[1]
167169
assert line.split()[0] == "commit"

Diff for: lib/git/repo.py

-20
Original file line numberDiff line numberDiff line change
@@ -347,26 +347,6 @@ def tree(self, treeish=None):
347347
return root
348348

349349

350-
def log(self, commit='master', path=None, **kwargs):
351-
"""
352-
The Commit for a treeish, and all commits leading to it.
353-
354-
``kwargs``
355-
keyword arguments specifying flags to be used in git-log command,
356-
i.e.: max_count=1 to limit the amount of commits returned
357-
358-
Returns
359-
``git.Commit[]``
360-
"""
361-
options = {'pretty': 'raw'}
362-
options.update(kwargs)
363-
arg = [commit, '--']
364-
if path:
365-
arg.append(path)
366-
commits = self.git.log(*arg, **options)
367-
print commits.splitlines(False)
368-
return list(Commit._iter_from_stream(self, iter(commits.splitlines())))
369-
370350
def diff(self, a, b, *paths):
371351
"""
372352
The diff from commit ``a`` to commit ``b``, optionally restricted to the given file(s)

Diff for: test/git/test_commit.py

+8-20
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,13 @@ class TestCommit(object):
1111
def setup(self):
1212
self.repo = Repo(GIT_REPO)
1313

14-
@patch_object(Git, '_call_process')
15-
def test_bake(self, git):
16-
git.return_value = fixture('rev_list_single')
14+
def test_bake(self):
1715

18-
commit = Commit(self.repo, **{'id': '4c8124ffcf4039d292442eeccabdeca5af5c5017'})
16+
commit = Commit(self.repo, **{'id': '2454ae89983a4496a445ce347d7a41c0bb0ea7ae'})
1917
commit.author # bake
2018

21-
assert_equal("Tom Preston-Werner", commit.author.name)
22-
assert_equal("[email protected]", commit.author.email)
23-
24-
assert_true(git.called)
25-
assert_equal(git.call_args, (('rev_list', '4c8124ffcf4039d292442eeccabdeca5af5c5017', '--', ''), {'pretty': 'raw', 'max_count': 1}))
19+
assert_equal("Sebastian Thiel", commit.author.name)
20+
assert_equal("[email protected]", commit.author.email)
2621

2722

2823
@patch_object(Git, '_call_process')
@@ -159,17 +154,10 @@ def test_diffs_on_initial_import(self):
159154
assert diff.deleted_file and isinstance(diff.deleted_file, bool)
160155
# END for each diff in initial import commit
161156

162-
@patch_object(Git, '_call_process')
163-
def test_diffs_on_initial_import_with_empty_commit(self, git):
164-
git.return_value = fixture('show_empty_commit')
165-
166-
commit = Commit(self.repo, id='634396b2f541a9f2d58b00be1a07f0c358b999b3')
157+
def test_diffs_on_initial_import_without_parents(self):
158+
commit = Commit(self.repo, id='33ebe7acec14b25c5f84f35a664803fcab2f7781')
167159
diffs = commit.diffs
168-
169-
assert_equal([], diffs)
170-
171-
assert_true(git.called)
172-
assert_equal(git.call_args, (('show', '634396b2f541a9f2d58b00be1a07f0c358b999b3', '-M'), {'full_index': True, 'pretty': 'raw'}))
160+
assert diffs
173161

174162
def test_diffs_with_mode_only_change(self):
175163
commit = Commit(self.repo, id='ccde80b7a3037a004a7807a6b79916ce2a1e9729')
@@ -216,7 +204,7 @@ def test_rev_list_bisect_all(self, git):
216204
bisect_all=True)
217205
assert_true(git.called)
218206

219-
commits = Commit._iter_from_stream(self.repo, iter(revs.splitlines(False)))
207+
commits = Commit._iter_from_process(self.repo, ListProcessAdapter(revs))
220208
expected_ids = (
221209
'cf37099ea8d1d8c7fbf9b6d12d7ec0249d3acb8b',
222210
'33ebe7acec14b25c5f84f35a664803fcab2f7781',

Diff for: test/git/test_repo.py

+2-20
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def test_heads_should_populate_head_data(self):
4141

4242
@patch_object(Git, '_call_process')
4343
def test_commits(self, git):
44-
git.return_value = fixture('rev_list')
44+
git.return_value = ListProcessAdapter(fixture('rev_list'))
4545

4646
commits = self.repo.commits('master', max_count=10)
4747

@@ -65,7 +65,6 @@ def test_commits(self, git):
6565
assert_equal("Merge branch 'site'", c.summary)
6666

6767
assert_true(git.called)
68-
assert_equal(git.call_args, (('rev_list', 'master', '--', ''), {'skip': 0, 'pretty': 'raw', 'max_count': 10}))
6968

7069
@patch_object(Git, '_call_process')
7170
def test_commit_count(self, git):
@@ -78,14 +77,13 @@ def test_commit_count(self, git):
7877

7978
@patch_object(Git, '_call_process')
8079
def test_commit(self, git):
81-
git.return_value = fixture('rev_list_single')
80+
git.return_value = ListProcessAdapter(fixture('rev_list_single'))
8281

8382
commit = self.repo.commit('4c8124ffcf4039d292442eeccabdeca5af5c5017')
8483

8584
assert_equal("4c8124ffcf4039d292442eeccabdeca5af5c5017", commit.id)
8685

8786
assert_true(git.called)
88-
assert_equal(git.call_args, (('rev_list', '4c8124ffcf4039d292442eeccabdeca5af5c5017', '--', ''), {'pretty': 'raw', 'max_count': 1}))
8987

9088
@patch_object(Git, '_call_process')
9189
def test_tree(self, git):
@@ -217,22 +215,6 @@ def test_repr(self):
217215
path = os.path.join(os.path.abspath(GIT_REPO), '.git')
218216
assert_equal('<git.Repo "%s">' % path, repr(self.repo))
219217

220-
@patch_object(Git, '_call_process')
221-
def test_log(self, git):
222-
git.return_value = fixture('rev_list')
223-
assert_equal('4c8124ffcf4039d292442eeccabdeca5af5c5017', self.repo.log()[0].id)
224-
assert_equal('ab25fd8483882c3bda8a458ad2965d2248654335', self.repo.log()[-1].id)
225-
assert_true(git.called)
226-
assert_equal(git.call_count, 2)
227-
assert_equal(git.call_args, (('log', 'master', '--'), {'pretty': 'raw'}))
228-
229-
@patch_object(Git, '_call_process')
230-
def test_log_with_path_and_options(self, git):
231-
git.return_value = fixture('rev_list')
232-
self.repo.log('master', 'file.rb', **{'max_count': 1})
233-
assert_true(git.called)
234-
assert_equal(git.call_args, (('log', 'master', '--', 'file.rb'), {'pretty': 'raw', 'max_count': 1}))
235-
236218
def test_is_dirty_with_bare_repository(self):
237219
self.repo.bare = True
238220
assert_false(self.repo.is_dirty)

Diff for: test/testlib/helper.py

+11
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,14 @@ def fixture(name):
1717

1818
def absolute_project_path():
1919
return os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
20+
21+
22+
class ListProcessAdapter(object):
23+
"""Allows to use lists as Process object as returned by SubProcess.Popen.
24+
Its tailored to work with the test system only"""
25+
26+
def __init__(self, input_list_or_string):
27+
l = input_list_or_string
28+
if isinstance(l,basestring):
29+
l = l.splitlines()
30+
self.stdout = iter(l)

0 commit comments

Comments
 (0)