summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.travis.yml46
-rw-r--r--AUTHORS1
-rw-r--r--VERSION2
-rw-r--r--doc/source/changes.rst8
-rw-r--r--git/cmd.py9
-rw-r--r--git/index/fun.py3
-rw-r--r--git/refs/tag.py3
-rw-r--r--git/remote.py2
-rw-r--r--git/repo/base.py32
-rw-r--r--git/util.py2
-rwxr-xr-xsetup.py2
-rw-r--r--test/test_fun.py15
-rw-r--r--test/test_repo.py38
13 files changed, 144 insertions, 19 deletions
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 00000000..1bed8368
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,46 @@
+# UNUSED, only for reference. If adjustments are needed, please see github actions
+language: python
+python:
+<<<<<<< HEAD
+ - "3.4"
+=======
+>>>>>>> b0f79c58ad919e90261d1e332df79a4ad0bc40de
+ - "3.6"
+ - "3.7"
+ - "3.8"
+ - "nightly"
+ # - "pypy" - won't work as smmap doesn't work (see gitdb/.travis.yml for details)
+matrix:
+ allow_failures:
+ - python: "nightly"
+git:
+ # a higher depth is needed for most of the tests - must be high enough to not actually be shallow
+ # as we clone our own repository in the process
+ depth: 99999
+install:
+ - python --version; git --version
+ - git submodule update --init --recursive
+ - git fetch --tags
+ - pip install -r test-requirements.txt
+ - pip install -r doc/requirements.txt
+ - pip install codecov
+
+ # generate some reflog as git-python tests need it (in master)
+ - ./init-tests-after-clone.sh
+
+ # as commits are performed with the default user, it needs to be set for travis too
+ - git config --global user.email "travis@ci.com"
+ - git config --global user.name "Travis Runner"
+ # If we rewrite the user's config by accident, we will mess it up
+ # and cause subsequent tests to fail
+ - cat git/test/fixtures/.gitconfig >> ~/.gitconfig
+script:
+ # Make sure we limit open handles to see if we are leaking them
+ - ulimit -n 128
+ - ulimit -n
+ - coverage run --omit="test/*" -m unittest --buffer
+ - coverage report
+ - if [ "$TRAVIS_PYTHON_VERSION" == '3.6' ]; then cd doc && make html; fi
+ - if [ "$TRAVIS_PYTHON_VERSION" == '3.6' ]; then flake8 --ignore=W293,E265,E266,W503,W504,E731; fi
+after_success:
+ - codecov
diff --git a/AUTHORS b/AUTHORS
index 7b21b2b2..606796d9 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -43,4 +43,5 @@ Contributors are:
-Liam Beguin <liambeguin _at_ gmail.com>
-Ram Rachum <ram _at_ rachum.com>
-Alba Mendez <me _at_ alba.sh>
+-Robert Westman <robert _at_ byteflux.io>
Portions derived from other open source works and are clearly marked.
diff --git a/VERSION b/VERSION
index 3797f3f9..5762a6ff 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-3.1.17
+3.1.18
diff --git a/doc/source/changes.rst b/doc/source/changes.rst
index 68a94516..aabef802 100644
--- a/doc/source/changes.rst
+++ b/doc/source/changes.rst
@@ -2,6 +2,14 @@
Changelog
=========
+3.1.18
+======
+
+* drop support for python 3.5 to reduce maintenance burden on typing. Lower patch levels of python 3.5 would break, too.
+
+See the following for details:
+https://github.com/gitpython-developers/gitpython/milestone/50?closed=1
+
3.1.17
======
diff --git a/git/cmd.py b/git/cmd.py
index d15b97ca..e078e4a1 100644
--- a/git/cmd.py
+++ b/git/cmd.py
@@ -17,9 +17,7 @@ from subprocess import (
import subprocess
import sys
import threading
-from collections import OrderedDict
from textwrap import dedent
-import warnings
from git.compat import (
defenc,
@@ -1004,13 +1002,6 @@ class Git(LazyMixin):
def transform_kwargs(self, split_single_char_options: bool = True, **kwargs: Any) -> List[str]:
"""Transforms Python style kwargs into git command line options."""
- # Python 3.6 preserves the order of kwargs and thus has a stable
- # order. For older versions sort the kwargs by the key to get a stable
- # order.
- if sys.version_info[:2] < (3, 6):
- kwargs = OrderedDict(sorted(kwargs.items(), key=lambda x: x[0]))
- warnings.warn("Python 3.5 support is deprecated and will be removed 2021-09-05.\n" +
- "It does not preserve the order for key-word arguments and enforce lexical sorting instead.")
args = []
for k, v in kwargs.items():
if isinstance(v, (list, tuple)):
diff --git a/git/index/fun.py b/git/index/fun.py
index 96d9b475..3fded347 100644
--- a/git/index/fun.py
+++ b/git/index/fun.py
@@ -11,6 +11,7 @@ from stat import (
S_ISDIR,
S_IFMT,
S_IFREG,
+ S_IXUSR,
)
import subprocess
@@ -115,7 +116,7 @@ def stat_mode_to_index_mode(mode: int) -> int:
return S_IFLNK
if S_ISDIR(mode) or S_IFMT(mode) == S_IFGITLINK: # submodules
return S_IFGITLINK
- return S_IFREG | 0o644 | (mode & 0o111) # blobs with or without executable bit
+ return S_IFREG | (mode & S_IXUSR and 0o755 or 0o644) # blobs with or without executable bit
def write_cache(entries: Sequence[Union[BaseIndexEntry, 'IndexEntry']], stream: IO[bytes],
diff --git a/git/refs/tag.py b/git/refs/tag.py
index 8f88c522..4d84239e 100644
--- a/git/refs/tag.py
+++ b/git/refs/tag.py
@@ -18,7 +18,8 @@ class TagReference(Reference):
print(tagref.tag.message)"""
__slots__ = ()
- _common_path_default = "refs/tags"
+ _common_default = "tags"
+ _common_path_default = Reference._common_path_default + "/" + _common_default
@property
def commit(self):
diff --git a/git/remote.py b/git/remote.py
index e17f7bb8..6ea4b2a1 100644
--- a/git/remote.py
+++ b/git/remote.py
@@ -612,7 +612,7 @@ class Remote(LazyMixin, Iterable):
# * [would prune] origin/new_branch
token = " * [would prune] "
if not line.startswith(token):
- raise ValueError("Could not parse git-remote prune result: %r" % line)
+ continue
ref_name = line.replace(token, "")
# sometimes, paths start with a full ref name, like refs/tags/foo, see #260
if ref_name.startswith(Reference._common_path_default + '/'):
diff --git a/git/repo/base.py b/git/repo/base.py
index 2d2e915c..5abd4961 100644
--- a/git/repo/base.py
+++ b/git/repo/base.py
@@ -3,12 +3,13 @@
#
# This module is part of GitPython and is released under
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
-
import logging
import os
import re
import warnings
+from gitdb.exc import BadObject
+
from git.cmd import (
Git,
handle_process_output
@@ -402,7 +403,17 @@ class Repo(object):
def tag(self, path: PathLike) -> TagReference:
""":return: TagReference Object, reference pointing to a Commit or Tag
:param path: path to the tag reference, i.e. 0.1.5 or tags/0.1.5 """
- return TagReference(self, path)
+ full_path = self._to_full_tag_path(path)
+ return TagReference(self, full_path)
+
+ @staticmethod
+ def _to_full_tag_path(path):
+ if path.startswith(TagReference._common_path_default + '/'):
+ return path
+ if path.startswith(TagReference._common_default + '/'):
+ return Reference._common_path_default + '/' + path
+ else:
+ return TagReference._common_path_default + '/' + path
def create_head(self, path: PathLike, commit: str = 'HEAD',
force: bool = False, logmsg: Optional[str] = None
@@ -608,6 +619,23 @@ class Repo(object):
raise
return True
+ def is_valid_object(self, sha: str, object_type: str = None) -> bool:
+ try:
+ complete_sha = self.odb.partial_to_complete_sha_hex(sha)
+ object_info = self.odb.info(complete_sha)
+ if object_type:
+ if object_info.type == object_type.encode():
+ return True
+ else:
+ log.debug("Commit hash points to an object of type '%s'. Requested were objects of type '%s'",
+ object_info.type.decode(), object_type)
+ return False
+ else:
+ return True
+ except BadObject:
+ log.debug("Commit hash is invalid.")
+ return False
+
def _get_daemon_export(self) -> bool:
if self.git_dir:
filename = osp.join(self.git_dir, self.DAEMON_EXPORT_FILE)
diff --git a/git/util.py b/git/util.py
index 76aaee49..edbd5f1e 100644
--- a/git/util.py
+++ b/git/util.py
@@ -470,7 +470,7 @@ class RemoteProgress(object):
line_str = line
self._cur_line = line_str
- if self.error_lines or self._cur_line.startswith(('error:', 'fatal:')):
+ if self._cur_line.startswith(('error:', 'fatal:')):
self.error_lines.append(self._cur_line)
return
diff --git a/setup.py b/setup.py
index 3fbcbbad..2845bbec 100755
--- a/setup.py
+++ b/setup.py
@@ -126,6 +126,6 @@ setup(
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
- "Programming Language :: Python :: 3.9"
+ "Programming Language :: Python :: 3.9"
]
)
diff --git a/test/test_fun.py b/test/test_fun.py
index a7fb8f8b..e3d07194 100644
--- a/test/test_fun.py
+++ b/test/test_fun.py
@@ -1,5 +1,5 @@
from io import BytesIO
-from stat import S_IFDIR, S_IFREG, S_IFLNK
+from stat import S_IFDIR, S_IFREG, S_IFLNK, S_IXUSR
from os import stat
import os.path as osp
from unittest import SkipTest
@@ -7,7 +7,8 @@ from unittest import SkipTest
from git import Git
from git.index import IndexFile
from git.index.fun import (
- aggressive_tree_merge
+ aggressive_tree_merge,
+ stat_mode_to_index_mode,
)
from git.objects.fun import (
traverse_tree_recursive,
@@ -206,6 +207,16 @@ class TestFun(TestBase):
assert_entries(aggressive_tree_merge(odb, trees), 2, True)
# END handle ours, theirs
+ def test_stat_mode_to_index_mode(self):
+ modes = (
+ 0o600, 0o611, 0o640, 0o641, 0o644, 0o650, 0o651,
+ 0o700, 0o711, 0o740, 0o744, 0o750, 0o751, 0o755,
+ )
+ for mode in modes:
+ expected_mode = S_IFREG | (mode & S_IXUSR and 0o755 or 0o644)
+ assert stat_mode_to_index_mode(mode) == expected_mode
+ # END for each mode
+
def _assert_tree_entries(self, entries, num_trees):
for entry in entries:
assert len(entry) == num_trees
diff --git a/test/test_repo.py b/test/test_repo.py
index 8dc17833..8aced94d 100644
--- a/test/test_repo.py
+++ b/test/test_repo.py
@@ -414,6 +414,16 @@ class TestRepo(TestBase):
def test_tag(self):
assert self.rorepo.tag('refs/tags/0.1.5').commit
+ def test_tag_to_full_tag_path(self):
+ tags = ['0.1.5', 'tags/0.1.5', 'refs/tags/0.1.5']
+ value_errors = []
+ for tag in tags:
+ try:
+ self.rorepo.tag(tag)
+ except ValueError as valueError:
+ value_errors.append(valueError.args[0])
+ self.assertEqual(value_errors, [])
+
def test_archive(self):
tmpfile = tempfile.mktemp(suffix='archive-test')
with open(tmpfile, 'wb') as stream:
@@ -979,6 +989,34 @@ class TestRepo(TestBase):
for i, j in itertools.permutations([c1, 'ffffff', ''], r=2):
self.assertRaises(GitCommandError, repo.is_ancestor, i, j)
+ def test_is_valid_object(self):
+ repo = self.rorepo
+ commit_sha = 'f6aa8d1'
+ blob_sha = '1fbe3e4375'
+ tree_sha = '960b40fe36'
+ tag_sha = '42c2f60c43'
+
+ # Check for valid objects
+ self.assertTrue(repo.is_valid_object(commit_sha))
+ self.assertTrue(repo.is_valid_object(blob_sha))
+ self.assertTrue(repo.is_valid_object(tree_sha))
+ self.assertTrue(repo.is_valid_object(tag_sha))
+
+ # Check for valid objects of specific type
+ self.assertTrue(repo.is_valid_object(commit_sha, 'commit'))
+ self.assertTrue(repo.is_valid_object(blob_sha, 'blob'))
+ self.assertTrue(repo.is_valid_object(tree_sha, 'tree'))
+ self.assertTrue(repo.is_valid_object(tag_sha, 'tag'))
+
+ # Check for invalid objects
+ self.assertFalse(repo.is_valid_object(b'1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a', 'blob'))
+
+ # Check for invalid objects of specific type
+ self.assertFalse(repo.is_valid_object(commit_sha, 'blob'))
+ self.assertFalse(repo.is_valid_object(blob_sha, 'commit'))
+ self.assertFalse(repo.is_valid_object(tree_sha, 'commit'))
+ self.assertFalse(repo.is_valid_object(tag_sha, 'commit'))
+
@with_rw_directory
def test_git_work_tree_dotgit(self, rw_dir):
"""Check that we find .git as a worktree file and find the worktree