You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

297 lines
11KB

  1. # -*- coding: utf-8 -*-
  2. # test_git.py
  3. # Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
  4. #
  5. # This module is part of GitPython and is released under
  6. # the BSD License: http://www.opensource.org/licenses/bsd-license.php
  7. import os
  8. import subprocess
  9. import sys
  10. from tempfile import TemporaryFile
  11. from git import (
  12. Git,
  13. refresh,
  14. GitCommandError,
  15. GitCommandNotFound,
  16. Repo,
  17. cmd
  18. )
  19. from git.compat import PY3, is_darwin
  20. from git.test.lib import (
  21. TestBase,
  22. patch,
  23. raises,
  24. assert_equal,
  25. assert_true,
  26. assert_match,
  27. fixture_path
  28. )
  29. from git.test.lib import with_rw_directory
  30. from git.util import finalize_process
  31. import os.path as osp
  32. try:
  33. from unittest import mock
  34. except ImportError:
  35. import mock
  36. from git.compat import is_win
  37. class TestGit(TestBase):
  38. @classmethod
  39. def setUpClass(cls):
  40. super(TestGit, cls).setUpClass()
  41. cls.git = Git(cls.rorepo.working_dir)
  42. def tearDown(self):
  43. import gc
  44. gc.collect()
  45. @patch.object(Git, 'execute')
  46. def test_call_process_calls_execute(self, git):
  47. git.return_value = ''
  48. self.git.version()
  49. assert_true(git.called)
  50. assert_equal(git.call_args, ((['git', 'version'],), {}))
  51. def test_call_unpack_args_unicode(self):
  52. args = Git._Git__unpack_args(u'Unicode€™')
  53. if PY3:
  54. mangled_value = 'Unicode\u20ac\u2122'
  55. else:
  56. mangled_value = 'Unicode\xe2\x82\xac\xe2\x84\xa2'
  57. assert_equal(args, [mangled_value])
  58. def test_call_unpack_args(self):
  59. args = Git._Git__unpack_args(['git', 'log', '--', u'Unicode€™'])
  60. if PY3:
  61. mangled_value = 'Unicode\u20ac\u2122'
  62. else:
  63. mangled_value = 'Unicode\xe2\x82\xac\xe2\x84\xa2'
  64. assert_equal(args, ['git', 'log', '--', mangled_value])
  65. @raises(GitCommandError)
  66. def test_it_raises_errors(self):
  67. self.git.this_does_not_exist()
  68. def test_it_transforms_kwargs_into_git_command_arguments(self):
  69. assert_equal(["-s"], self.git.transform_kwargs(**{'s': True}))
  70. assert_equal(["-s", "5"], self.git.transform_kwargs(**{'s': 5}))
  71. assert_equal([], self.git.transform_kwargs(**{'s': None}))
  72. assert_equal(["--max-count"], self.git.transform_kwargs(**{'max_count': True}))
  73. assert_equal(["--max-count=5"], self.git.transform_kwargs(**{'max_count': 5}))
  74. assert_equal(["--max-count=0"], self.git.transform_kwargs(**{'max_count': 0}))
  75. assert_equal([], self.git.transform_kwargs(**{'max_count': None}))
  76. # Multiple args are supported by using lists/tuples
  77. assert_equal(["-L", "1-3", "-L", "12-18"], self.git.transform_kwargs(**{'L': ('1-3', '12-18')}))
  78. assert_equal(["-C", "-C"], self.git.transform_kwargs(**{'C': [True, True, None, False]}))
  79. # order is undefined
  80. res = self.git.transform_kwargs(**{'s': True, 't': True})
  81. self.assertEqual({'-s', '-t'}, set(res))
  82. def test_it_executes_git_to_shell_and_returns_result(self):
  83. assert_match(r'^git version [\d\.]{2}.*$', self.git.execute(["git", "version"]))
  84. def test_it_accepts_stdin(self):
  85. filename = fixture_path("cat_file_blob")
  86. with open(filename, 'r') as fh:
  87. assert_equal("70c379b63ffa0795fdbfbc128e5a2818397b7ef8",
  88. self.git.hash_object(istream=fh, stdin=True))
  89. @patch.object(Git, 'execute')
  90. def test_it_ignores_false_kwargs(self, git):
  91. # this_should_not_be_ignored=False implies it *should* be ignored
  92. self.git.version(pass_this_kwarg=False)
  93. assert_true("pass_this_kwarg" not in git.call_args[1])
  94. @raises(GitCommandError)
  95. def test_it_raises_proper_exception_with_output_stream(self):
  96. tmp_file = TemporaryFile()
  97. self.git.checkout('non-existent-branch', output_stream=tmp_file)
  98. def test_it_accepts_environment_variables(self):
  99. filename = fixture_path("ls_tree_empty")
  100. with open(filename, 'r') as fh:
  101. tree = self.git.mktree(istream=fh)
  102. env = {
  103. 'GIT_AUTHOR_NAME': 'Author Name',
  104. 'GIT_AUTHOR_EMAIL': 'author@example.com',
  105. 'GIT_AUTHOR_DATE': '1400000000+0000',
  106. 'GIT_COMMITTER_NAME': 'Committer Name',
  107. 'GIT_COMMITTER_EMAIL': 'committer@example.com',
  108. 'GIT_COMMITTER_DATE': '1500000000+0000',
  109. }
  110. commit = self.git.commit_tree(tree, m='message', env=env)
  111. assert_equal(commit, '4cfd6b0314682d5a58f80be39850bad1640e9241')
  112. def test_persistent_cat_file_command(self):
  113. # read header only
  114. import subprocess as sp
  115. hexsha = "b2339455342180c7cc1e9bba3e9f181f7baa5167"
  116. g = self.git.cat_file(batch_check=True, istream=sp.PIPE, as_process=True)
  117. g.stdin.write(b"b2339455342180c7cc1e9bba3e9f181f7baa5167\n")
  118. g.stdin.flush()
  119. obj_info = g.stdout.readline()
  120. # read header + data
  121. g = self.git.cat_file(batch=True, istream=sp.PIPE, as_process=True)
  122. g.stdin.write(b"b2339455342180c7cc1e9bba3e9f181f7baa5167\n")
  123. g.stdin.flush()
  124. obj_info_two = g.stdout.readline()
  125. self.assertEqual(obj_info, obj_info_two)
  126. # read data - have to read it in one large chunk
  127. size = int(obj_info.split()[2])
  128. g.stdout.read(size)
  129. g.stdout.read(1)
  130. # now we should be able to read a new object
  131. g.stdin.write(b"b2339455342180c7cc1e9bba3e9f181f7baa5167\n")
  132. g.stdin.flush()
  133. self.assertEqual(g.stdout.readline(), obj_info)
  134. # same can be achieved using the respective command functions
  135. hexsha, typename, size = self.git.get_object_header(hexsha)
  136. hexsha, typename_two, size_two, data = self.git.get_object_data(hexsha) # @UnusedVariable
  137. self.assertEqual(typename, typename_two)
  138. self.assertEqual(size, size_two)
  139. def test_version(self):
  140. v = self.git.version_info
  141. self.assertIsInstance(v, tuple)
  142. for n in v:
  143. self.assertIsInstance(n, int)
  144. # END verify number types
  145. def test_cmd_override(self):
  146. prev_cmd = self.git.GIT_PYTHON_GIT_EXECUTABLE
  147. exc = GitCommandNotFound
  148. try:
  149. # set it to something that doens't exist, assure it raises
  150. type(self.git).GIT_PYTHON_GIT_EXECUTABLE = osp.join(
  151. "some", "path", "which", "doesn't", "exist", "gitbinary")
  152. self.failUnlessRaises(exc, self.git.version)
  153. finally:
  154. type(self.git).GIT_PYTHON_GIT_EXECUTABLE = prev_cmd
  155. # END undo adjustment
  156. def test_refresh(self):
  157. # test a bad git path refresh
  158. self.assertRaises(GitCommandNotFound, refresh, "yada")
  159. # test a good path refresh
  160. which_cmd = "where" if is_win else "which"
  161. path = os.popen("{0} git".format(which_cmd)).read().strip().split('\n')[0]
  162. refresh(path)
  163. def test_options_are_passed_to_git(self):
  164. # This work because any command after git --version is ignored
  165. git_version = self.git(version=True).NoOp()
  166. git_command_version = self.git.version()
  167. self.assertEquals(git_version, git_command_version)
  168. def test_persistent_options(self):
  169. git_command_version = self.git.version()
  170. # analog to test_options_are_passed_to_git
  171. self.git.set_persistent_git_options(version=True)
  172. git_version = self.git.NoOp()
  173. self.assertEquals(git_version, git_command_version)
  174. # subsequent calls keep this option:
  175. git_version_2 = self.git.NoOp()
  176. self.assertEquals(git_version_2, git_command_version)
  177. # reset to empty:
  178. self.git.set_persistent_git_options()
  179. self.assertRaises(GitCommandError, self.git.NoOp)
  180. def test_single_char_git_options_are_passed_to_git(self):
  181. input_value = 'TestValue'
  182. output_value = self.git(c='user.name=%s' % input_value).config('--get', 'user.name')
  183. self.assertEquals(input_value, output_value)
  184. def test_change_to_transform_kwargs_does_not_break_command_options(self):
  185. self.git.log(n=1)
  186. def test_insert_after_kwarg_raises(self):
  187. # This isn't a complete add command, which doesn't matter here
  188. self.failUnlessRaises(ValueError, self.git.remote, 'add', insert_kwargs_after='foo')
  189. def test_env_vars_passed_to_git(self):
  190. editor = 'non_existent_editor'
  191. with mock.patch.dict('os.environ', {'GIT_EDITOR': editor}): # @UndefinedVariable
  192. self.assertEqual(self.git.var("GIT_EDITOR"), editor)
  193. @with_rw_directory
  194. def test_environment(self, rw_dir):
  195. # sanity check
  196. self.assertEqual(self.git.environment(), {})
  197. # make sure the context manager works and cleans up after itself
  198. with self.git.custom_environment(PWD='/tmp'):
  199. self.assertEqual(self.git.environment(), {'PWD': '/tmp'})
  200. self.assertEqual(self.git.environment(), {})
  201. old_env = self.git.update_environment(VARKEY='VARVALUE')
  202. # The returned dict can be used to revert the change, hence why it has
  203. # an entry with value 'None'.
  204. self.assertEqual(old_env, {'VARKEY': None})
  205. self.assertEqual(self.git.environment(), {'VARKEY': 'VARVALUE'})
  206. new_env = self.git.update_environment(**old_env)
  207. self.assertEqual(new_env, {'VARKEY': 'VARVALUE'})
  208. self.assertEqual(self.git.environment(), {})
  209. path = osp.join(rw_dir, 'failing-script.sh')
  210. with open(path, 'wt') as stream:
  211. stream.write("#!/usr/bin/env sh\n"
  212. "echo FOO\n")
  213. os.chmod(path, 0o777)
  214. rw_repo = Repo.init(osp.join(rw_dir, 'repo'))
  215. remote = rw_repo.create_remote('ssh-origin', "ssh://git@server/foo")
  216. with rw_repo.git.custom_environment(GIT_SSH=path):
  217. try:
  218. remote.fetch()
  219. except GitCommandError as err:
  220. if sys.version_info[0] < 3 and is_darwin:
  221. self.assertIn('ssh-orig, ' in str(err))
  222. self.assertEqual(err.status, 128)
  223. else:
  224. self.assertIn('FOO', str(err))
  225. def test_handle_process_output(self):
  226. from git.cmd import handle_process_output
  227. line_count = 5002
  228. count = [None, 0, 0]
  229. def counter_stdout(line):
  230. count[1] += 1
  231. def counter_stderr(line):
  232. count[2] += 1
  233. cmdline = [sys.executable, fixture_path('cat_file.py'), str(fixture_path('issue-301_stderr'))]
  234. proc = subprocess.Popen(cmdline,
  235. stdin=None,
  236. stdout=subprocess.PIPE,
  237. stderr=subprocess.PIPE,
  238. shell=False,
  239. creationflags=cmd.PROC_CREATIONFLAGS,
  240. )
  241. handle_process_output(proc, counter_stdout, counter_stderr, finalize_process)
  242. self.assertEqual(count[1], line_count)
  243. self.assertEqual(count[2], line_count)