From 1287f69b42fa7d6b9d65abfef80899b22edfef55 Mon Sep 17 00:00:00 2001 From: Jonas Trappenberg Date: Tue, 20 Jan 2015 21:47:07 -0800 Subject: [PATCH 1/6] Fix some typos --- git/cmd.py | 4 ++-- git/remote.py | 2 +- git/test/test_remote.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/git/cmd.py b/git/cmd.py index 55ed74dd0..ef347925d 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -139,7 +139,7 @@ def deplete_buffer(fno, handler, buf_list, wg=None): 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 @@ -731,7 +731,7 @@ def make_call(): 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 fcec52285..c176b43b7 100644 --- a/git/remote.py +++ b/git/remote.py @@ -354,7 +354,7 @@ def __init__(self, repo, name): # 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/test/test_remote.py b/git/test/test_remote.py index d4a92ed4d..bf2f76a86 100644 --- a/git/test/test_remote.py +++ b/git/test/test_remote.py @@ -163,11 +163,11 @@ def fetch_and_test(remote, **kwargs): 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 From 261aedd2e13308755894405c7a76b72373dab879 Mon Sep 17 00:00:00 2001 From: Jonas Trappenberg Date: Tue, 20 Jan 2015 23:19:28 -0800 Subject: [PATCH 2/6] Add 'sshkey' context manager --- doc/source/tutorial.rst | 6 ++++ git/cmd.py | 76 ++++++++++++++++++++++++++++++++++++++++- scripts/ssh_wrapper.py | 9 +++++ 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100755 scripts/ssh_wrapper.py diff --git a/doc/source/tutorial.rst b/doc/source/tutorial.rst index c6e5626b5..6ca0fd877 100644 --- a/doc/source/tutorial.rst +++ b/doc/source/tutorial.rst @@ -383,6 +383,12 @@ You can easily access configuration information for a remote by accessing option Change configuration for a specific remote only:: o.config_writer.set("pushurl", "other_url") + +You can also specify an SSH key to use for any operations on the remotes: + + private_key_file = project_dir+'id_rsa_deployment_key' + with repo.git.sshkey(private_key_file): + o.fetch() Submodule Handling diff --git a/git/cmd.py b/git/cmd.py index ef347925d..1022fc254 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 errno import mmap +from contextlib import contextmanager from subprocess import ( call, Popen, @@ -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 @@ def __init__(self, working_dir=None): 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 @@ -536,6 +541,8 @@ def execute(self, command, # Start the process env = os.environ.copy() env["LC_MESSAGES"] = "C" + env.update(self._environment) + proc = Popen(command, env=env, cwd=cwd, @@ -608,6 +615,73 @@ def as_text(stdout_value): else: return stdout_value + def set_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.set_environment(PWD='/tmp') + self.set_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.iteritems(): + # 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 environment(self, **kwargs): + """ + A context manager around the above set_environment to restore the + environment back to its previous state after operation. + + ``Examples``:: + + with self.environment(GIT_SSH='/bin/ssh_wrapper'): + repo.remotes.origin.fetch() + + :param kwargs: see set_environment + """ + old_env = self.set_environment(**kwargs) + try: + yield + finally: + self.set_environment(**old_env) + + @contextmanager + def sshkey(self, sshkey_file): + """ + A context manager to temporarily set an SSH key for all operations that + run inside it. + + ``Examples``:: + + with self.environment(GIT_SSH=project_dir+'deployment_key'): + repo.remotes.origin.fetch() + + :param sshkey_file: Path to a private SSH key file + """ + this_dir = os.path.dirname(__file__) + ssh_wrapper = os.path.join(this_dir, '..', 'scripts', 'ssh_wrapper.py') + + with self.environment(GIT_SSH_KEY_FILE=sshkey_file, 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() diff --git a/scripts/ssh_wrapper.py b/scripts/ssh_wrapper.py new file mode 100755 index 000000000..af657f606 --- /dev/null +++ b/scripts/ssh_wrapper.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python + +import os +import subprocess +import sys + +ssh_options = ['-i', os.environ['GIT_SSH_KEY_FILE']] +ret_code = subprocess.call(['ssh'] + ssh_options + sys.argv[1:]) +sys.exit(ret_code) From 34c0831453355e09222ccc6665782d7070f5ddab Mon Sep 17 00:00:00 2001 From: Jonas Trappenberg Date: Wed, 21 Jan 2015 21:50:46 -0800 Subject: [PATCH 3/6] Add method to query environment --- git/cmd.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/git/cmd.py b/git/cmd.py index 1022fc254..65159f284 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -615,6 +615,9 @@ def as_text(stdout_value): else: return stdout_value + def environment(self): + return self._environment + def set_environment(self, **kwargs): """ Set environment variables for future git invocations. Return all changed From 2a9b2f22c6fb5bd3e30e674874dbc8aa7e5b00ae Mon Sep 17 00:00:00 2001 From: Jonas Trappenberg Date: Wed, 21 Jan 2015 21:51:10 -0800 Subject: [PATCH 4/6] Rename 'environment' and 'set_environment' --- git/cmd.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/git/cmd.py b/git/cmd.py index 65159f284..914424706 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -618,7 +618,7 @@ def as_text(stdout_value): def environment(self): return self._environment - def set_environment(self, **kwargs): + 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 @@ -626,8 +626,8 @@ def set_environment(self, **kwargs): ``Examples``:: - old_env = self.set_environment(PWD='/tmp') - self.set_environment(**old_env) + 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 @@ -648,23 +648,23 @@ def set_environment(self, **kwargs): return old_env @contextmanager - def environment(self, **kwargs): + def with_environment(self, **kwargs): """ - A context manager around the above set_environment to restore the + A context manager around the above update_environment to restore the environment back to its previous state after operation. ``Examples``:: - with self.environment(GIT_SSH='/bin/ssh_wrapper'): + with self.with_environment(GIT_SSH='/bin/ssh_wrapper'): repo.remotes.origin.fetch() - :param kwargs: see set_environment + :param kwargs: see update_environment """ - old_env = self.set_environment(**kwargs) + old_env = self.update_environment(**kwargs) try: yield finally: - self.set_environment(**old_env) + self.update_environment(**old_env) @contextmanager def sshkey(self, sshkey_file): @@ -682,7 +682,7 @@ def sshkey(self, sshkey_file): this_dir = os.path.dirname(__file__) ssh_wrapper = os.path.join(this_dir, '..', 'scripts', 'ssh_wrapper.py') - with self.environment(GIT_SSH_KEY_FILE=sshkey_file, GIT_SSH=ssh_wrapper): + with self.with_environment(GIT_SSH_KEY_FILE=sshkey_file, GIT_SSH=ssh_wrapper): yield def transform_kwargs(self, split_single_char_options=False, **kwargs): From 98a17a28a21fe5f06dd2da561839fdbaa1912c64 Mon Sep 17 00:00:00 2001 From: Jonas Trappenberg Date: Wed, 21 Jan 2015 22:26:52 -0800 Subject: [PATCH 5/6] Add SSH wrapper to MANIFEST.in --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 95b2e883f..4c02e39a3 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 - From 6f038611ff120f8283f0f1a56962f95b66730c72 Mon Sep 17 00:00:00 2001 From: Jonas Trappenberg Date: Wed, 21 Jan 2015 22:26:59 -0800 Subject: [PATCH 6/6] Add a few tests --- git/test/test_git.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/git/test/test_git.py b/git/test/test_git.py index f25fa21a8..85ee56c6b 100644 --- a/git/test/test_git.py +++ b/git/test/test_git.py @@ -153,3 +153,23 @@ def test_env_vars_passed_to_git(self): editor = 'non_existant_editor' with mock.patch.dict('os.environ', {'GIT_EDITOR': editor}): assert self.git.var("GIT_EDITOR") == editor + + def test_environment(self): + # sanity check + assert self.git.environment() == {} + + # make sure the context manager works and cleans up after itself + with self.git.with_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() == {}