Repository: gitpython-developers/GitPython (python)

Issue:
Can't pass environment variables to Git.execute
This is needed for commands like `git-commit` and `git-commit-tree` in order to be able to specify author and committer information. While `git-commit` does provide the `--author` and `--date` to specify author information, there are no options for specifying committer information, and `git-commit-tree` is wholly dependent on environment variables.

For instance, the following code fails with `TypeError: type object got multiple values for keyword argument 'env'`.

```
def pipe_of(string):
	import os
	if type(string) is str:
		string = bytes(string, 'utf-8')
	r,w = os.pipe()
	os.write(w, string)
	os.close(w)
	return r
git = Repo('/path/ro/repo').git
blob = git.execute(['git', 'hash-object', '-w', '--stdin'], istream=pipe_of("my data"))
tree = git.execute(['git', 'mktree'], istream=pipe_of("100644 blob {}\tmy-file\n".format(blob)))
commit = git.execute(['git', 'commit-tree', tree], istream=None, env={
	'GIT_AUTHOR_NAME':'Author Name',
	'GIT_AUTHOR_EMAIL':'author@example.com',
	'GIT_AUTHOR_DATE':'1400000000+0000',
	'GIT_COMMITTER_NAME':'Comitter Name',
	'GIT_COMMITTER_EMAIL':'committer@example.com',
	'GIT_COMMITTER_DATE':'1500000000+0000',
	})
```

Pull request:
Implemented Per-Call Environment Variables
Added the ability to specify environment variables per-call.
Resolves #644 

Patch:
diff --git a/AUTHORS b/AUTHORS
index 0d3bdacf..2b67d6cb 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -19,6 +19,7 @@ Contributors are:
 -Timothy B. Hartman <tbhartman _at_ gmail.com>
 -Konstantin Popov <konstantin.popov.89 _at_ yandex.ru>
 -Peter Jones <pjones _at_ redhat.com>
+-Anson Mansfield <anson.mansfield _at_ gmail.com>
 -Ken Odegard <ken.odegard _at_ gmail.com>
 -Alexis Horgix Chotard
 
diff --git a/git/cmd.py b/git/cmd.py
index 312f933a..e0946e47 100644
--- a/git/cmd.py
+++ b/git/cmd.py
@@ -47,7 +47,7 @@ from .util import (
 execute_kwargs = set(('istream', 'with_extended_output',
                       'with_exceptions', 'as_process', 'stdout_as_string',
                       'output_stream', 'with_stdout', 'kill_after_timeout',
-                      'universal_newlines', 'shell'))
+                      'universal_newlines', 'shell', 'env'))
 
 log = logging.getLogger(__name__)
 log.addHandler(logging.NullHandler())
@@ -597,6 +597,7 @@ class Git(LazyMixin):
                 with_stdout=True,
                 universal_newlines=False,
                 shell=None,
+                env=None,
                 **subprocess_kwargs
                 ):
         """Handles executing the command on the shell and consumes and returns
@@ -640,6 +641,9 @@ class Git(LazyMixin):
             decoded into a string using the default encoding (usually utf-8).
             The latter can fail, if the output contains binary data.
 
+        :param env:
+            A dictionary of environment variables to be passed to `subprocess.Popen`.
+
         :param subprocess_kwargs:
             Keyword arguments to be passed to subprocess.Popen. Please note that
             some of the valid kwargs are already set by this method, the ones you
@@ -685,6 +689,7 @@ class Git(LazyMixin):
         cwd = self._working_dir or os.getcwd()
 
         # Start the process
+        inline_env = env
         env = os.environ.copy()
         # Attempt to force all output to plain ascii english, which is what some parsing code
         # may expect.
@@ -693,6 +698,8 @@ class Git(LazyMixin):
         env["LANGUAGE"] = "C"
         env["LC_ALL"] = "C"
         env.update(self._environment)
+        if inline_env is not None:
+            env.update(inline_env)
 
         if is_win:
             cmd_not_found_exception = OSError


Test patch:
diff --git a/git/test/fixtures/ls_tree_empty b/git/test/fixtures/ls_tree_empty
new file mode 100644
index 00000000..e69de29b
diff --git a/git/test/test_git.py b/git/test/test_git.py
index 00577e33..0a0d7ef7 100644
--- a/git/test/test_git.py
+++ b/git/test/test_git.py
@@ -106,6 +106,21 @@ class TestGit(TestBase):
         self.git.version(pass_this_kwarg=False)
         assert_true("pass_this_kwarg" not in git.call_args[1])
 
+    def test_it_accepts_environment_variables(self):
+        filename = fixture_path("ls_tree_empty")
+        with open(filename, 'r') as fh:
+            tree = self.git.mktree(istream=fh)
+            env = {
+                'GIT_AUTHOR_NAME': 'Author Name',
+                'GIT_AUTHOR_EMAIL': 'author@example.com',
+                'GIT_AUTHOR_DATE': '1400000000+0000',
+                'GIT_COMMITTER_NAME': 'Committer Name',
+                'GIT_COMMITTER_EMAIL': 'committer@example.com',
+                'GIT_COMMITTER_DATE': '1500000000+0000',
+            }
+            commit = self.git.commit_tree(tree, m='message', env=env)
+            assert_equal(commit, '4cfd6b0314682d5a58f80be39850bad1640e9241')
+
     def test_persistent_cat_file_command(self):
         # read header only
         import subprocess as sp