diff options
Diffstat (limited to 'git')
| -rw-r--r-- | git/cmd.py | 4 | ||||
| -rw-r--r-- | git/index/base.py | 19 | ||||
| -rw-r--r-- | git/index/fun.py | 7 | ||||
| -rw-r--r-- | git/objects/submodule/base.py | 6 | ||||
| -rw-r--r-- | git/refs/symbolic.py | 10 | ||||
| -rw-r--r-- | git/remote.py | 16 | ||||
| -rw-r--r-- | git/repo/base.py | 26 | ||||
| -rw-r--r-- | git/test/test_index.py | 114 | ||||
| -rw-r--r-- | git/test/test_repo.py | 23 | ||||
| -rw-r--r-- | git/test/test_submodule.py | 12 |
10 files changed, 190 insertions, 47 deletions
@@ -780,8 +780,8 @@ class Git(LazyMixin): if kill_after_timeout: watchdog.cancel() if kill_check.isSet(): - stderr_value = 'Timeout: the command "%s" did not complete in %d ' \ - 'secs.' % (" ".join(command), kill_after_timeout) + stderr_value = ('Timeout: the command "%s" did not complete in %d ' + 'secs.' % (" ".join(command), kill_after_timeout)).encode(defenc) # strip trailing "\n" if stdout_value.endswith(b"\n"): stdout_value = stdout_value[:-1] diff --git a/git/index/base.py b/git/index/base.py index 4fee2aae..a9e3a3c7 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -948,6 +948,11 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): :return: Commit object representing the new commit""" if not skip_hooks: run_commit_hook('pre-commit', self) + + self._write_commit_editmsg(message) + run_commit_hook('commit-msg', self, self._commit_editmsg_filepath()) + message = self._read_commit_editmsg() + self._remove_commit_editmsg() tree = self.write_tree() rval = Commit.create_from_tree(self.repo, tree, message, parent_commits, head, author=author, committer=committer, @@ -955,6 +960,20 @@ class IndexFile(LazyMixin, diff.Diffable, Serializable): if not skip_hooks: run_commit_hook('post-commit', self) return rval + + def _write_commit_editmsg(self, message): + with open(self._commit_editmsg_filepath(), "wb") as commit_editmsg_file: + commit_editmsg_file.write(message.encode(defenc)) + + def _remove_commit_editmsg(self): + os.remove(self._commit_editmsg_filepath()) + + def _read_commit_editmsg(self): + with open(self._commit_editmsg_filepath(), "rb") as commit_editmsg_file: + return commit_editmsg_file.read().decode(defenc) + + def _commit_editmsg_filepath(self): + return osp.join(self.repo.common_dir, "COMMIT_EDITMSG") @classmethod def _flush_stdin_and_wait(cls, proc, ignore_stdout=False): diff --git a/git/index/fun.py b/git/index/fun.py index 7f7518e1..c01a32b8 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -62,10 +62,11 @@ def hook_path(name, git_dir): return osp.join(git_dir, 'hooks', name) -def run_commit_hook(name, index): +def run_commit_hook(name, index, *args): """Run the commit hook of the given name. Silently ignores hooks that do not exist. :param name: name of hook, like 'pre-commit' :param index: IndexFile instance + :param args: arguments passed to hook file :raises HookExecutionError: """ hp = hook_path(name, index.repo.git_dir) if not os.access(hp, os.X_OK): @@ -75,7 +76,7 @@ def run_commit_hook(name, index): env['GIT_INDEX_FILE'] = safe_decode(index.path) if PY3 else safe_encode(index.path) env['GIT_EDITOR'] = ':' try: - cmd = subprocess.Popen(hp, + cmd = subprocess.Popen([hp] + list(args), env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -93,7 +94,7 @@ def run_commit_hook(name, index): if cmd.returncode != 0: stdout = force_text(stdout, defenc) stderr = force_text(stderr, defenc) - raise HookExecutionError(hp, cmd.returncode, stdout, stderr) + raise HookExecutionError(hp, cmd.returncode, stderr, stdout) # end handle return code diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index a6b4caed..33151217 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -278,7 +278,7 @@ class Submodule(IndexObject, Iterable, Traversable): if not path.startswith(working_tree_linux): raise ValueError("Submodule checkout path '%s' needs to be within the parents repository at '%s'" % (working_tree_linux, path)) - path = path[len(working_tree_linux) + 1:] + path = path[len(working_tree_linux.rstrip('/')) + 1:] if not path: raise ValueError("Absolute submodule path '%s' didn't yield a valid relative path" % path) # end verify converted relative path makes sense @@ -358,7 +358,9 @@ class Submodule(IndexObject, Iterable, Traversable): if sm.exists(): # reretrieve submodule from tree try: - return repo.head.commit.tree[path] + sm = repo.head.commit.tree[path] + sm._name = name + return sm except KeyError: # could only be in index index = repo.index diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py index bef6ba3c..8efeafc5 100644 --- a/git/refs/symbolic.py +++ b/git/refs/symbolic.py @@ -96,7 +96,15 @@ class SymbolicReference(object): if not line: continue if line.startswith('#'): - if line.startswith('# pack-refs with:') and not line.endswith('peeled'): + # "# pack-refs with: peeled fully-peeled sorted" + # the git source code shows "peeled", + # "fully-peeled" and "sorted" as the keywords + # that can go on this line, as per comments in git file + # refs/packed-backend.c + # I looked at master on 2017-10-11, + # commit 111ef79afe, after tag v2.15.0-rc1 + # from repo https://github.com/git/git.git + if line.startswith('# pack-refs with:') and 'peeled' not in line: raise TypeError("PackingType of packed-Refs not understood: %r" % line) # END abort if we do not understand the packing scheme continue diff --git a/git/remote.py b/git/remote.py index 7261be81..35460f5a 100644 --- a/git/remote.py +++ b/git/remote.py @@ -536,10 +536,18 @@ class Remote(LazyMixin, Iterable): # and: http://stackoverflow.com/a/32991784/548792 # if 'Unknown subcommand: get-url' in str(ex): - remote_details = self.repo.git.remote("show", self.name) - for line in remote_details.split('\n'): - if ' Push URL:' in line: - yield line.split(': ')[-1] + try: + remote_details = self.repo.git.remote("show", self.name) + for line in remote_details.split('\n'): + if ' Push URL:' in line: + yield line.split(': ')[-1] + except GitCommandError as ex: + if any([msg in str(ex) for msg in ['correct access rights','cannot run ssh']]): + # If ssh is not setup to access this repository, see issue 694 + result = Git().execute(['git','config','--get','remote.%s.url' % self.name]) + yield result + else: + raise ex else: raise ex diff --git a/git/repo/base.py b/git/repo/base.py index d3bdc983..990def64 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -209,9 +209,17 @@ class Repo(object): def close(self): if self.git: self.git.clear_cache() - gc.collect() + # Tempfiles objects on Windows are holding references to + # open files until they are collected by the garbage + # collector, thus preventing deletion. + # TODO: Find these references and ensure they are closed + # and deleted synchronously rather than forcing a gc + # collection. + if is_win: + gc.collect() gitdb.util.mman.collect() - gc.collect() + if is_win: + gc.collect() def __eq__(self, rhs): if isinstance(rhs, Repo): @@ -905,6 +913,10 @@ class Repo(object): odbt = kwargs.pop('odbt', odb_default_type) + # when pathlib.Path or other classbased path is passed + if not isinstance(path, str): + path = str(path) + ## A bug win cygwin's Git, when `--bare` or `--separate-git-dir` # it prepends the cwd or(?) the `url` into the `path, so:: # git clone --bare /cygwin/d/foo.git C:\\Work @@ -918,9 +930,9 @@ class Repo(object): if sep_dir: kwargs['separate_git_dir'] = Git.polish_url(sep_dir) proc = git.clone(Git.polish_url(url), clone_path, with_extended_output=True, as_process=True, - v=True, **add_progress(kwargs, git, progress)) + v=True, universal_newlines=True, **add_progress(kwargs, git, progress)) if progress: - handle_process_output(proc, None, progress.new_message_handler(), finalize_process) + handle_process_output(proc, None, progress.new_message_handler(), finalize_process, decode_streams=False) else: (stdout, stderr) = proc.communicate() log.debug("Cmd(%s)'s unused stdout: %s", getattr(proc, 'args', ''), stdout) @@ -931,12 +943,16 @@ class Repo(object): if not osp.isabs(path) and git.working_dir: path = osp.join(git._working_dir, path) + repo = cls(path, odbt=odbt) + + # retain env values that were passed to _clone() + repo.git.update_environment(**git.environment()) + # adjust remotes - there may be operating systems which use backslashes, # These might be given as initial paths, but when handling the config file # that contains the remote from which we were clones, git stops liking it # as it will escape the backslashes. Hence we undo the escaping just to be # sure - repo = cls(path, odbt=odbt) if repo.remotes: with repo.remotes[0].config_writer as writer: writer.set_value('url', Git.polish_url(repo.remotes[0].url)) diff --git a/git/test/test_index.py b/git/test/test_index.py index e8d38a09..cf746140 100644 --- a/git/test/test_index.py +++ b/git/test/test_index.py @@ -729,35 +729,6 @@ class TestIndex(TestBase): assert fkey not in index.entries index.add(files, write=True) - if is_win: - hp = hook_path('pre-commit', index.repo.git_dir) - hpd = osp.dirname(hp) - if not osp.isdir(hpd): - os.mkdir(hpd) - with open(hp, "wt") as fp: - fp.write("#!/usr/bin/env sh\necho stdout; echo stderr 1>&2; exit 1") - # end - os.chmod(hp, 0o744) - try: - index.commit("This should fail") - except HookExecutionError as err: - if is_win: - self.assertIsInstance(err.status, OSError) - self.assertEqual(err.command, [hp]) - self.assertEqual(err.stdout, '') - self.assertEqual(err.stderr, '') - assert str(err) - else: - self.assertEqual(err.status, 1) - self.assertEqual(err.command, hp) - self.assertEqual(err.stdout, 'stdout\n') - self.assertEqual(err.stderr, 'stderr\n') - assert str(err) - else: - raise AssertionError("Should have cought a HookExecutionError") - # end exception handling - os.remove(hp) - # end hook testing nc = index.commit("2 files committed", head=False) for fkey in keys: @@ -859,3 +830,88 @@ class TestIndex(TestBase): r = Repo.init(rw_dir) r.index.add([fp]) r.index.commit('Added [.exe') + + @with_rw_repo('HEAD', bare=True) + def test_pre_commit_hook_success(self, rw_repo): + index = rw_repo.index + hp = hook_path('pre-commit', index.repo.git_dir) + hpd = osp.dirname(hp) + if not osp.isdir(hpd): + os.mkdir(hpd) + with open(hp, "wt") as fp: + fp.write("#!/usr/bin/env sh\nexit 0") + os.chmod(hp, 0o744) + index.commit("This should not fail") + + @with_rw_repo('HEAD', bare=True) + def test_pre_commit_hook_fail(self, rw_repo): + index = rw_repo.index + hp = hook_path('pre-commit', index.repo.git_dir) + hpd = osp.dirname(hp) + if not osp.isdir(hpd): + os.mkdir(hpd) + with open(hp, "wt") as fp: + fp.write("#!/usr/bin/env sh\necho stdout; echo stderr 1>&2; exit 1") + os.chmod(hp, 0o744) + try: + index.commit("This should fail") + except HookExecutionError as err: + if is_win: + self.assertIsInstance(err.status, OSError) + self.assertEqual(err.command, [hp]) + self.assertEqual(err.stdout, '') + self.assertEqual(err.stderr, '') + assert str(err) + else: + self.assertEqual(err.status, 1) + self.assertEqual(err.command, [hp]) + self.assertEqual(err.stdout, "\n stdout: 'stdout\n'") + self.assertEqual(err.stderr, "\n stderr: 'stderr\n'") + assert str(err) + else: + raise AssertionError("Should have cought a HookExecutionError") + + @with_rw_repo('HEAD', bare=True) + def test_commit_msg_hook_success(self, rw_repo): + index = rw_repo.index + commit_message = u"commit default head by Frèderic Çaufl€" + from_hook_message = u"from commit-msg" + + hp = hook_path('commit-msg', index.repo.git_dir) + hpd = osp.dirname(hp) + if not osp.isdir(hpd): + os.mkdir(hpd) + with open(hp, "wt") as fp: + fp.write('#!/usr/bin/env sh\necho -n " {}" >> "$1"'.format(from_hook_message)) + os.chmod(hp, 0o744) + + new_commit = index.commit(commit_message) + self.assertEqual(new_commit.message, u"{} {}".format(commit_message, from_hook_message)) + + @with_rw_repo('HEAD', bare=True) + def test_commit_msg_hook_fail(self, rw_repo): + index = rw_repo.index + hp = hook_path('commit-msg', index.repo.git_dir) + hpd = osp.dirname(hp) + if not osp.isdir(hpd): + os.mkdir(hpd) + with open(hp, "wt") as fp: + fp.write("#!/usr/bin/env sh\necho stdout; echo stderr 1>&2; exit 1") + os.chmod(hp, 0o744) + try: + index.commit("This should fail") + except HookExecutionError as err: + if is_win: + self.assertIsInstance(err.status, OSError) + self.assertEqual(err.command, [hp]) + self.assertEqual(err.stdout, '') + self.assertEqual(err.stderr, '') + assert str(err) + else: + self.assertEqual(err.status, 1) + self.assertEqual(err.command, [hp]) + self.assertEqual(err.stdout, "\n stdout: 'stdout\n'") + self.assertEqual(err.stderr, "\n stderr: 'stderr\n'") + assert str(err) + else: + raise AssertionError("Should have cought a HookExecutionError") diff --git a/git/test/test_repo.py b/git/test/test_repo.py index 312e67f9..2c3ad957 100644 --- a/git/test/test_repo.py +++ b/git/test/test_repo.py @@ -16,6 +16,11 @@ try: except ImportError: from unittest2 import skipIf, SkipTest +try: + import pathlib +except ImportError: + pathlib = None + from git import ( InvalidGitRepositoryError, Repo, @@ -201,6 +206,24 @@ class TestRepo(TestBase): pass # END test repos with working tree + @with_rw_directory + def test_clone_from_keeps_env(self, rw_dir): + original_repo = Repo.init(osp.join(rw_dir, "repo")) + environment = {"entry1": "value", "another_entry": "10"} + + cloned = Repo.clone_from(original_repo.git_dir, osp.join(rw_dir, "clone"), env=environment) + + assert_equal(environment, cloned.git.environment()) + + @with_rw_directory + def test_clone_from_pathlib(self, rw_dir): + if pathlib is None: # pythons bellow 3.4 don't have pathlib + raise SkipTest("pathlib was introduced in 3.4") + + original_repo = Repo.init(osp.join(rw_dir, "repo")) + + Repo.clone_from(original_repo.git_dir, pathlib.Path(rw_dir) / "clone_pathlib") + def test_init(self): prev_cwd = os.getcwd() os.chdir(tempfile.gettempdir()) diff --git a/git/test/test_submodule.py b/git/test/test_submodule.py index e667ae17..f970dd2c 100644 --- a/git/test/test_submodule.py +++ b/git/test/test_submodule.py @@ -10,7 +10,7 @@ except ImportError: import git from git.cmd import Git -from git.compat import string_types +from git.compat import string_types, is_win from git.exc import ( InvalidGitRepositoryError, RepositoryDirtyError @@ -911,3 +911,13 @@ class TestSubmodule(TestBase): parent_repo.submodule_update(to_latest_revision=True, force_reset=True) assert sm_mod.commit() == sm_pfb.commit, "Now head should have been reset" assert sm_mod.head.ref.name == sm_pfb.name + + @skipIf(not is_win, "Specifically for Windows.") + def test_to_relative_path_with_super_at_root_drive(self): + class Repo(object): + working_tree_dir = 'D:\\' + super_repo = Repo() + submodule_path = 'D:\\submodule_path' + relative_path = Submodule._to_relative_path(super_repo, submodule_path) + msg = '_to_relative_path should be "submodule_path" but was "%s"' % relative_path + assert relative_path == 'submodule_path', msg
\ No newline at end of file |
