summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MANIFEST.in2
-rw-r--r--doc/source/tutorial.rst9
-rw-r--r--git/cmd.py85
-rw-r--r--git/remote.py2
-rwxr-xr-xgit/scripts/ssh_wrapper.sh2
-rw-r--r--git/test/test_docs.py9
-rw-r--r--git/test/test_git.py56
-rw-r--r--git/test/test_remote.py4
8 files changed, 151 insertions, 18 deletions
diff --git a/MANIFEST.in b/MANIFEST.in
index 95b2e883..4c02e39a 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -4,7 +4,7 @@ include CHANGES
include AUTHORS
include README
include requirements.txt
+include scripts/ssh_wrapper.py
graft git/test/fixtures
graft git/test/performance
-
diff --git a/doc/source/tutorial.rst b/doc/source/tutorial.rst
index 4df0013c..7f57ec94 100644
--- a/doc/source/tutorial.rst
+++ b/doc/source/tutorial.rst
@@ -329,7 +329,14 @@ You can easily access configuration information for a remote by accessing option
.. literalinclude:: ../../git/test/test_docs.py
:language: python
:start-after: # [26-test_references_and_objects]
- :end-before: # ![26-test_references_and_objects]
+ :end-before: # ![26-test_references_and_objects]
+
+You can also specify an SSH key to use for any operations on the remotes
+
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [32-test_references_and_objects]
+ :end-before: # ![32-test_references_and_objects]
Submodule Handling
******************
diff --git a/git/cmd.py b/git/cmd.py
index 55ed74dd..960b2a21 100644
--- a/git/cmd.py
+++ b/git/cmd.py
@@ -5,6 +5,7 @@
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
import os
+import os.path
import sys
import select
import logging
@@ -12,6 +13,7 @@ import threading
import errno
import mmap
+from contextlib import contextmanager
from subprocess import (
call,
Popen,
@@ -139,7 +141,7 @@ def handle_process_output(process, stdout_handler, stderr_handler, finalizer):
if hasattr(select, 'poll'):
# poll is preferred, as select is limited to file handles up to 1024 ... . This could otherwise be
- # an issue for us, as it matters how many handles or own process has
+ # an issue for us, as it matters how many handles our own process has
poll = select.poll()
READ_ONLY = select.POLLIN | select.POLLPRI | select.POLLHUP | select.POLLERR
CLOSED = select.POLLHUP | select.POLLERR
@@ -223,7 +225,7 @@ class Git(LazyMixin):
Set its value to 'full' to see details about the returned values.
"""
__slots__ = ("_working_dir", "cat_file_all", "cat_file_header", "_version_info",
- "_git_options")
+ "_git_options", "_environment")
# CONFIGURATION
# The size in bytes read from stdout when copying git's output to another stream
@@ -413,6 +415,9 @@ class Git(LazyMixin):
self._working_dir = working_dir
self._git_options = ()
+ # Extra environment variables to pass to git commands
+ self._environment = {}
+
# cached command slots
self.cat_file_header = None
self.cat_file_all = None
@@ -434,6 +439,10 @@ class Git(LazyMixin):
super(Git, self)._set_cache_(attr)
# END handle version info
+ def _sshkey_script_path(self):
+ this_dir = os.path.dirname(__file__)
+ return os.path.join(this_dir, 'scripts', 'ssh_wrapper.sh')
+
@property
def working_dir(self):
""":return: Git directory we are working on"""
@@ -536,6 +545,8 @@ class Git(LazyMixin):
# Start the process
env = os.environ.copy()
env["LC_MESSAGES"] = "C"
+ env.update(self._environment)
+
proc = Popen(command,
env=env,
cwd=cwd,
@@ -608,6 +619,74 @@ class Git(LazyMixin):
else:
return stdout_value
+ def environment(self):
+ return self._environment
+
+ def update_environment(self, **kwargs):
+ """
+ Set environment variables for future git invocations. Return all changed
+ values in a format that can be passed back into this function to revert
+ the changes:
+
+ ``Examples``::
+
+ old_env = self.update_environment(PWD='/tmp')
+ self.update_environment(**old_env)
+
+ :param kwargs: environment variables to use for git processes
+ :return: dict that maps environment variables to their old values
+ """
+ old_env = {}
+ for key, value in kwargs.items():
+ # set value if it is None
+ if value is not None:
+ if key in self._environment:
+ old_env[key] = self._environment[key]
+ else:
+ old_env[key] = None
+ self._environment[key] = value
+ # remove key from environment if its value is None
+ elif key in self._environment:
+ old_env[key] = self._environment[key]
+ del self._environment[key]
+ return old_env
+
+ @contextmanager
+ def custom_environment(self, **kwargs):
+ """
+ A context manager around the above ``update_environment`` method to restore the
+ environment back to its previous state after operation.
+
+ ``Examples``::
+
+ with self.custom_environment(GIT_SSH='/bin/ssh_wrapper'):
+ repo.remotes.origin.fetch()
+
+ :param kwargs: see update_environment
+ """
+ old_env = self.update_environment(**kwargs)
+ try:
+ yield
+ finally:
+ self.update_environment(**old_env)
+
+ @contextmanager
+ def sshkey(self, sshkey_file_path):
+ """
+ A context manager to temporarily set an SSH key for all operations that
+ run inside it.
+
+ ``Examples``::
+
+ with self.sshkey('deployment_key'):
+ repo.remotes.origin.fetch()
+
+ :param sshkey_file_path: Path to a private SSH key file
+ """
+ ssh_wrapper = self._sshkey_script_path()
+ with self.custom_environment(GIT_SSH_KEY_FILE=sshkey_file_path, GIT_SSH=ssh_wrapper):
+ yield
+
def transform_kwargs(self, split_single_char_options=False, **kwargs):
"""Transforms Python style kwargs into git command line options."""
args = list()
@@ -731,7 +810,7 @@ class Git(LazyMixin):
import warnings
msg = "WARNING: Automatically switched to use git.cmd as git executable"
msg += ", which reduces performance by ~70%."
- msg += "Its recommended to put git.exe into the PATH or to "
+ msg += "It is recommended to put git.exe into the PATH or to "
msg += "set the %s " % self._git_exec_env_var
msg += "environment variable to the executable's location"
warnings.warn(msg)
diff --git a/git/remote.py b/git/remote.py
index 39d9dc4d..d048f87b 100644
--- a/git/remote.py
+++ b/git/remote.py
@@ -362,7 +362,7 @@ class Remote(LazyMixin, Iterable):
# that it has the config_writer property, but instead calls __getattr__
# which will not yield the expected results. 'pinging' the members
# with a dir call creates the config_writer property that we require
- # ... bugs like these make me wonder wheter python really wants to be used
+ # ... bugs like these make me wonder whether python really wants to be used
# for production. It doesn't happen on linux though.
dir(self)
# END windows special handling
diff --git a/git/scripts/ssh_wrapper.sh b/git/scripts/ssh_wrapper.sh
new file mode 100755
index 00000000..bc0ab024
--- /dev/null
+++ b/git/scripts/ssh_wrapper.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/env sh
+ssh -i "$GIT_SSH_KEY_FILE" $@
diff --git a/git/test/test_docs.py b/git/test/test_docs.py
index 586f0ce4..965d10fb 100644
--- a/git/test/test_docs.py
+++ b/git/test/test_docs.py
@@ -437,6 +437,15 @@ class Tutorials(TestBase):
git.for_each_ref() # '-' becomes '_' when calling it
# ![31-test_references_and_objects]
+ # [32-test_references_and_objects]
+ private_key_file = os.path.join(rw_dir, 'id_rsa_deployment_key')
+ with repo.git.sshkey(private_key_file):
+ # Note that we don't actually make the call here, as our test-setup doesn't permit it to
+ # succeed.
+ # It will in your case :)
+ repo.remotes.origin.fetch
+ # ![32-test_references_and_objects]
+
def test_submodules(self):
# [1-test_submodules]
repo = self.rorepo
diff --git a/git/test/test_git.py b/git/test/test_git.py
index f25fa21a..990f4cd0 100644
--- a/git/test/test_git.py
+++ b/git/test/test_git.py
@@ -4,18 +4,24 @@
#
# This module is part of GitPython and is released under
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
-
import os
import mock
-from git.test.lib import (TestBase,
- patch,
- raises,
- assert_equal,
- assert_true,
- assert_match,
- fixture_path)
-from git import (Git,
- GitCommandError)
+
+from git.test.lib import (
+ TestBase,
+ patch,
+ raises,
+ assert_equal,
+ assert_true,
+ assert_match,
+ fixture_path
+)
+from git import (
+ Git,
+ GitCommandError,
+ Repo
+)
+from gitdb.test.lib import with_rw_directory
from git.compat import PY3
@@ -153,3 +159,33 @@ class TestGit(TestBase):
editor = 'non_existant_editor'
with mock.patch.dict('os.environ', {'GIT_EDITOR': editor}):
assert self.git.var("GIT_EDITOR") == editor
+
+ @with_rw_directory
+ def test_environment(self, rw_dir):
+ # sanity check
+ assert self.git.environment() == {}
+
+ # make sure the context manager works and cleans up after itself
+ with self.git.custom_environment(PWD='/tmp'):
+ assert self.git.environment() == {'PWD': '/tmp'}
+
+ assert self.git.environment() == {}
+
+ old_env = self.git.update_environment(VARKEY='VARVALUE')
+ # The returned dict can be used to revert the change, hence why it has
+ # an entry with value 'None'.
+ assert old_env == {'VARKEY': None}
+ assert self.git.environment() == {'VARKEY': 'VARVALUE'}
+
+ new_env = self.git.update_environment(**old_env)
+ assert new_env == {'VARKEY': 'VARVALUE'}
+ assert self.git.environment() == {}
+
+ rw_repo = Repo.init(os.path.join(rw_dir, 'repo'))
+ remote = rw_repo.create_remote('ssh-origin', "ssh://git@server/foo")
+
+ with rw_repo.git.sshkey('doesntexist.key'):
+ remote.fetch()
+ # end
+
+
diff --git a/git/test/test_remote.py b/git/test/test_remote.py
index 4fd78230..98d74d8b 100644
--- a/git/test/test_remote.py
+++ b/git/test/test_remote.py
@@ -164,11 +164,11 @@ class TestRemote(TestBase):
def get_info(res, remote, name):
return res["%s/%s" % (remote, name)]
- # put remote head to master as it is garantueed to exist
+ # put remote head to master as it is guaranteed to exist
remote_repo.head.reference = remote_repo.heads.master
res = fetch_and_test(remote)
- # all uptodate
+ # all up to date
for info in res:
assert info.flags & info.HEAD_UPTODATE