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

CHANGES

Lines changed: 1 addition & 0 deletions
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
----

lib/git/cmd.py

Lines changed: 45 additions & 1 deletion
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()

lib/git/objects/commit.py

Lines changed: 8 additions & 6 deletions
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"

lib/git/repo.py

Lines changed: 0 additions & 20 deletions
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)

test/git/test_commit.py

Lines changed: 8 additions & 20 deletions
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("tom@mojombo.com", 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("byronimo@gmail.com", 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',

test/git/test_repo.py

Lines changed: 2 additions & 20 deletions
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)

test/testlib/helper.py

Lines changed: 11 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy