Skip to content

Commit

Permalink
ENH: Introducing new git command call wrapper, using GitPython
Browse files Browse the repository at this point in the history
  • Loading branch information
bpoldrack committed Jun 8, 2016
1 parent e41911f commit 2187954
Show file tree
Hide file tree
Showing 2 changed files with 157 additions and 1 deletion.
96 changes: 96 additions & 0 deletions datalad/support/gitrepo.py
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,7 @@ def get_hexsha(self, branch=None):
branch: str, optional
"""
# TODO: support not only a branch but any treeish
# Note: repo.tree(treeish).hexsha
if branch is None:
return self.repo.active_branch.object.hexsha
for b in self.repo.branches:
Expand Down Expand Up @@ -690,6 +691,101 @@ def get_file_content(self, file_, branch='HEAD'):
return content_str.splitlines()
# TODO: keep splitlines?

def _gitpy_custom_call(self, cmd, cmd_args=None, cmd_options=None,
git_options=None, env=None,

# 'old' options for Runner; not sure yet, which of
# them are actually still needed:
log_stdout=True, log_stderr=True, log_online=False,
expect_stderr=True, cwd=None,
shell=None, expect_fail=False):

"""Helper to call GitPython's wrapper for git calls.
The used instance of `gitpy.Git` is bound to the repository,
which determines its working directory.
This is used for adhoc implementation of a git command and to
demonstrate how to use it in more specific implementations.
Note
----
Aims to replace the use of datalad's `Runner` class for direct git
calls. (Currently the `_git_custom_command()` method).
Therefore mimicking its behaviour during RF'ing.
Parameters
----------
cmd: str
the native git command to call
cmd_args: list of str
arguments to the git command
cmd_options: dict
options for the command as key, value pair
(this transformation, needs some central place to document)
git_options: dict
options for the git executable as key, value pair
(see above)
env: dict
environment vaiables to temporarily set for this call
TODO
----
Example
Returns
-------
(stdout, stderr)
"""

# TODO: Reconsider when to log/stream what (stdout, stderr) and/or
# fully implement the behaviour of `Runner`

if log_online:
raise NotImplementedError("option 'log_online' not implemented yet")
with_exceptions = not expect_fail
if cwd:
# the gitpy.cmd.Git instance, bound to this repository doesn't allow
# to explicitly set the working dir, except for using os.getcwd
raise NotImplementedError("working dir is a read-only property")

_tmp_shell = gitpy.cmd.Git.USE_SHELL
gitpy.cmd.Git.USE_SHELL = shell

if env is None:
env = {}
if git_options is None:
git_options = {}
if cmd_options is None:
cmd_options = {}
cmd_options.update({'with_exceptions': with_exceptions,
'with_extended_output': True})



with self.repo.git.custom_environment(**env):
try:
status, std_out, std_err = \
self.repo.git(**git_options).__getattr__(cmd)(
cmd_args, **cmd_options)
except GitCommandError as e:
# For now just reraise. May be raise CommandError instead
raise
finally:
gitpy.cmd.Git.USE_SHELL = _tmp_shell

if not expect_stderr and std_err:
lgr.error("Unexpected output on stderr: %s" % std_err)
raise CommandError
if log_stdout:
for line in std_out.splitlines():
lgr.debug("stdout| " + line)
if log_stderr:
for line in std_err.splitlines():
lgr.log(level=logging.DEBUG if expect_stderr else logging.ERROR,
msg="stderr| " + line)

return std_out, std_err

@normalize_paths(match_return_type=False)
def _git_custom_command(self, files, cmd_str,
log_stdout=True, log_stderr=True, log_online=False,
Expand Down
62 changes: 61 additions & 1 deletion datalad/support/tests/test_gitrepo.py
Original file line number Diff line number Diff line change
Expand Up @@ -716,4 +716,64 @@ def test_get_added_files_commit_msg():
f = GitRepo._get_added_files_commit_msg
eq_(f([]), 'No files were added')
eq_(f(["f1"]), 'Added 1 file\n\nFiles:\nf1')
eq_(f(["f1", "f2"]), 'Added 2 files\n\nFiles:\nf1\nf2')
eq_(f(["f1", "f2"]), 'Added 2 files\n\nFiles:\nf1\nf2')


@with_tempfile(mkdir=True)
@with_tempfile(mkdir=True)
def test_git_custom_calls(path, path2):
# we need a GitRepo instance
repo = GitRepo(path, create=True)
with open(opj(path, "cc_test.dat"), 'w') as f:
f.write("test_git_custom_calls")

out, err = repo._gitpy_custom_call('add', 'cc_test.dat')

# actually executed:
assert_in("cc_test.dat", repo.get_indexed_files())
ok_(repo.dirty)

# call using cmd_options:
out, err = repo._gitpy_custom_call('commit',
cmd_options={'m': 'added file'})
ok_clean_git(path, annex=False)
# check output:
assert_in("1 file changed", out)
assert_in("cc_test.dat", out)
eq_('', err)

# impossible 'add' call should raise ...
assert_raises(GitCommandError, repo._gitpy_custom_call,
'add', 'not_existing', expect_fail=False)
# .. except we expect it to fail:
repo._gitpy_custom_call('add', 'not_existing', expect_fail=True)

# log outputs:
with swallow_logs(new_level=logging.DEBUG) as cm:
out, err = repo._gitpy_custom_call('status',
log_stdout=True,
log_stderr=True)

assert_in("On branch master", out)
assert_in("nothing to commit, working directory clean", out)
eq_("", err)
for line in out.splitlines():
assert_in("stdout| " + line, cm.out)

# don't log outputs:
with swallow_logs(new_level=logging.DEBUG) as cm:
out, err = repo._gitpy_custom_call('status',
log_stdout=False,
log_stderr=False)

assert_in("On branch master", out)
assert_in("nothing to commit, working directory clean", out)
eq_("", err)
eq_("", cm.out)

# use git_options:
# Note: 'path2' doesn't contain a git repository
with assert_raises(GitCommandError) as cm:
repo._gitpy_custom_call('status', git_options={'C': path2})
assert_in("git -C %s status" % path2, str(cm.exception))
assert_in("fatal: Not a git repository", str(cm.exception))

0 comments on commit 2187954

Please sign in to comment.