diff --git a/.gitignore b/.gitignore index 60e8a550..12c3a970 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /.envrc /.tox/ /build/ +/ci/ /dist/ /docs/_build/ /MANIFEST @@ -13,3 +14,5 @@ __pycache__/ *.pyc *.so *.swp +/pygit2/_libgit2.c +/pygit2/_libgit2.o diff --git a/build.sh b/build.sh index 31fea244..c710a076 100644 --- a/build.sh +++ b/build.sh @@ -178,7 +178,7 @@ if [ -n "$LIBGIT2_VERSION" ]; then wget https://github.com/libgit2/libgit2/archive/refs/tags/v$LIBGIT2_VERSION.tar.gz -N -O $FILENAME.tar.gz tar xf $FILENAME.tar.gz cd $FILENAME - mkdir build -p + mkdir -p build cd build if [ "$KERNEL" = "Darwin" ] && [ "$CIBUILDWHEEL" = "1" ]; then CMAKE_PREFIX_PATH=$OPENSSL_PREFIX:$PREFIX cmake .. \ diff --git a/pygit2/__init__.py b/pygit2/__init__.py index 7b01d37c..d5edd2a1 100644 --- a/pygit2/__init__.py +++ b/pygit2/__init__.py @@ -191,38 +191,6 @@ GIT_OID_HEXSZ, GIT_OID_MINPREFIXLEN, GIT_OID_RAWSZ, - GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS, - GIT_OPT_ENABLE_CACHING, - GIT_OPT_ENABLE_FSYNC_GITDIR, - GIT_OPT_ENABLE_OFS_DELTA, - GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, - GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, - GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION, - GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY, - GIT_OPT_GET_CACHED_MEMORY, - GIT_OPT_GET_MWINDOW_FILE_LIMIT, - GIT_OPT_GET_MWINDOW_MAPPED_LIMIT, - GIT_OPT_GET_MWINDOW_SIZE, - GIT_OPT_GET_OWNER_VALIDATION, - GIT_OPT_GET_PACK_MAX_OBJECTS, - GIT_OPT_GET_SEARCH_PATH, - GIT_OPT_GET_TEMPLATE_PATH, - GIT_OPT_GET_USER_AGENT, - GIT_OPT_GET_WINDOWS_SHAREMODE, - GIT_OPT_SET_ALLOCATOR, - GIT_OPT_SET_CACHE_MAX_SIZE, - GIT_OPT_SET_CACHE_OBJECT_LIMIT, - GIT_OPT_SET_MWINDOW_FILE_LIMIT, - GIT_OPT_SET_MWINDOW_MAPPED_LIMIT, - GIT_OPT_SET_MWINDOW_SIZE, - GIT_OPT_SET_OWNER_VALIDATION, - GIT_OPT_SET_PACK_MAX_OBJECTS, - GIT_OPT_SET_SEARCH_PATH, - GIT_OPT_SET_SSL_CERT_LOCATIONS, - GIT_OPT_SET_SSL_CIPHERS, - GIT_OPT_SET_TEMPLATE_PATH, - GIT_OPT_SET_USER_AGENT, - GIT_OPT_SET_WINDOWS_SHAREMODE, GIT_REFERENCES_ALL, GIT_REFERENCES_BRANCHES, GIT_REFERENCES_TAGS, @@ -322,7 +290,6 @@ hash, hashfile, init_file_backend, - option, reference_is_valid_name, tree_entry_cmp, ) @@ -345,6 +312,55 @@ from .filter import Filter from .index import Index, IndexEntry from .legacyenums import * +from .options import ( + GIT_OPT_ADD_SSL_X509_CERT, + GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS, + GIT_OPT_ENABLE_CACHING, + GIT_OPT_ENABLE_FSYNC_GITDIR, + GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, + GIT_OPT_ENABLE_OFS_DELTA, + GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, + GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, + GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION, + GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY, + GIT_OPT_GET_CACHED_MEMORY, + GIT_OPT_GET_EXTENSIONS, + GIT_OPT_GET_HOMEDIR, + GIT_OPT_GET_MWINDOW_FILE_LIMIT, + GIT_OPT_GET_MWINDOW_MAPPED_LIMIT, + GIT_OPT_GET_MWINDOW_SIZE, + GIT_OPT_GET_OWNER_VALIDATION, + GIT_OPT_GET_PACK_MAX_OBJECTS, + GIT_OPT_GET_SEARCH_PATH, + GIT_OPT_GET_SERVER_CONNECT_TIMEOUT, + GIT_OPT_GET_SERVER_TIMEOUT, + GIT_OPT_GET_TEMPLATE_PATH, + GIT_OPT_GET_USER_AGENT, + GIT_OPT_GET_USER_AGENT_PRODUCT, + GIT_OPT_GET_WINDOWS_SHAREMODE, + GIT_OPT_SET_ALLOCATOR, + GIT_OPT_SET_CACHE_MAX_SIZE, + GIT_OPT_SET_CACHE_OBJECT_LIMIT, + GIT_OPT_SET_EXTENSIONS, + GIT_OPT_SET_HOMEDIR, + GIT_OPT_SET_MWINDOW_FILE_LIMIT, + GIT_OPT_SET_MWINDOW_MAPPED_LIMIT, + GIT_OPT_SET_MWINDOW_SIZE, + GIT_OPT_SET_ODB_LOOSE_PRIORITY, + GIT_OPT_SET_ODB_PACKED_PRIORITY, + GIT_OPT_SET_OWNER_VALIDATION, + GIT_OPT_SET_PACK_MAX_OBJECTS, + GIT_OPT_SET_SEARCH_PATH, + GIT_OPT_SET_SERVER_CONNECT_TIMEOUT, + GIT_OPT_SET_SERVER_TIMEOUT, + GIT_OPT_SET_SSL_CERT_LOCATIONS, + GIT_OPT_SET_SSL_CIPHERS, + GIT_OPT_SET_TEMPLATE_PATH, + GIT_OPT_SET_USER_AGENT, + GIT_OPT_SET_USER_AGENT_PRODUCT, + GIT_OPT_SET_WINDOWS_SHAREMODE, + option, +) from .packbuilder import PackBuilder from .remotes import Remote from .repository import Repository @@ -745,37 +761,51 @@ def clone_repository( 'GIT_OBJECT_REF_DELTA', 'GIT_OBJECT_TAG', 'GIT_OBJECT_TREE', + 'GIT_OPT_ADD_SSL_X509_CERT', 'GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS', 'GIT_OPT_ENABLE_CACHING', 'GIT_OPT_ENABLE_FSYNC_GITDIR', + 'GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE', 'GIT_OPT_ENABLE_OFS_DELTA', 'GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION', 'GIT_OPT_ENABLE_STRICT_OBJECT_CREATION', 'GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION', 'GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY', 'GIT_OPT_GET_CACHED_MEMORY', + 'GIT_OPT_GET_EXTENSIONS', + 'GIT_OPT_GET_HOMEDIR', 'GIT_OPT_GET_MWINDOW_FILE_LIMIT', 'GIT_OPT_GET_MWINDOW_MAPPED_LIMIT', 'GIT_OPT_GET_MWINDOW_SIZE', 'GIT_OPT_GET_OWNER_VALIDATION', 'GIT_OPT_GET_PACK_MAX_OBJECTS', 'GIT_OPT_GET_SEARCH_PATH', + 'GIT_OPT_GET_SERVER_CONNECT_TIMEOUT', + 'GIT_OPT_GET_SERVER_TIMEOUT', 'GIT_OPT_GET_TEMPLATE_PATH', 'GIT_OPT_GET_USER_AGENT', + 'GIT_OPT_GET_USER_AGENT_PRODUCT', 'GIT_OPT_GET_WINDOWS_SHAREMODE', 'GIT_OPT_SET_ALLOCATOR', 'GIT_OPT_SET_CACHE_MAX_SIZE', 'GIT_OPT_SET_CACHE_OBJECT_LIMIT', + 'GIT_OPT_SET_EXTENSIONS', + 'GIT_OPT_SET_HOMEDIR', 'GIT_OPT_SET_MWINDOW_FILE_LIMIT', 'GIT_OPT_SET_MWINDOW_MAPPED_LIMIT', 'GIT_OPT_SET_MWINDOW_SIZE', + 'GIT_OPT_SET_ODB_LOOSE_PRIORITY', + 'GIT_OPT_SET_ODB_PACKED_PRIORITY', 'GIT_OPT_SET_OWNER_VALIDATION', 'GIT_OPT_SET_PACK_MAX_OBJECTS', 'GIT_OPT_SET_SEARCH_PATH', + 'GIT_OPT_SET_SERVER_CONNECT_TIMEOUT', + 'GIT_OPT_SET_SERVER_TIMEOUT', 'GIT_OPT_SET_SSL_CERT_LOCATIONS', 'GIT_OPT_SET_SSL_CIPHERS', 'GIT_OPT_SET_TEMPLATE_PATH', 'GIT_OPT_SET_USER_AGENT', + 'GIT_OPT_SET_USER_AGENT_PRODUCT', 'GIT_OPT_SET_WINDOWS_SHAREMODE', 'GIT_REFERENCES_ALL', 'GIT_REFERENCES_BRANCHES', diff --git a/pygit2/_libgit2/ffi.pyi b/pygit2/_libgit2/ffi.pyi index 0e865e31..c1156fa8 100644 --- a/pygit2/_libgit2/ffi.pyi +++ b/pygit2/_libgit2/ffi.pyi @@ -42,6 +42,9 @@ class int_c: class int64_t: def __getitem__(self, item: Literal[0]) -> int: ... +class ssize_t: + def __getitem__(self, item: Literal[0]) -> int: ... + class _Pointer(Generic[T]): def __setitem__(self, item: Literal[0], a: T) -> None: ... @overload @@ -55,7 +58,8 @@ class _MultiPointer(Generic[T]): class ArrayC(Generic[T]): # incomplete! # def _len(self, ?) -> ?: ... - pass + def __getitem__(self, index: int) -> T: ... + def __setitem__(self, index: int, value: T) -> None: ... class GitTimeC: # incomplete @@ -196,7 +200,7 @@ class GitStashSaveOptionsC: class GitStrrayC: # incomplete? - strings: NULL_TYPE | ArrayC[char] + strings: NULL_TYPE | ArrayC[char_pointer] count: int class GitTreeC: @@ -319,6 +323,8 @@ def new( @overload def new(a: Literal['size_t *', 'size_t*']) -> size_t: ... @overload +def new(a: Literal['ssize_t *', 'ssize_t*']) -> ssize_t: ... +@overload def new(a: Literal['git_stash_save_options *']) -> GitStashSaveOptionsC: ... @overload def new(a: Literal['git_strarray *']) -> GitStrrayC: ... @@ -330,6 +336,14 @@ def new(a: Literal['git_buf *'], b: tuple[NULL_TYPE, Literal[0]]) -> GitBufC: .. def new(a: Literal['char **']) -> _Pointer[char_pointer]: ... @overload def new(a: Literal['char[]', 'char []'], b: bytes | NULL_TYPE) -> ArrayC[char]: ... +@overload +def new( + a: Literal['char *[]'], b: int +) -> ArrayC[char_pointer]: ... # For ext_array in SET_EXTENSIONS +@overload +def new( + a: Literal['char *[]'], b: list[Any] +) -> ArrayC[char_pointer]: ... # For string arrays def addressof(a: object, attribute: str) -> _Pointer[object]: ... class buffer(bytes): @@ -340,4 +354,13 @@ class buffer(bytes): @overload def __getitem__(self, item: slice[Any, Any, Any]) -> bytes: ... +@overload def cast(a: Literal['int'], b: object) -> int: ... +@overload +def cast(a: Literal['unsigned int'], b: object) -> int: ... +@overload +def cast(a: Literal['size_t'], b: object) -> int: ... +@overload +def cast(a: Literal['ssize_t'], b: object) -> int: ... +@overload +def cast(a: Literal['char *'], b: object) -> char_pointer: ... diff --git a/pygit2/_pygit2.pyi b/pygit2/_pygit2.pyi index 105acc2a..1172f965 100644 --- a/pygit2/_pygit2.pyi +++ b/pygit2/_pygit2.pyi @@ -25,7 +25,6 @@ from .enums import ( ApplyLocation, BlobFilter, BranchType, - ConfigLevel, DeltaStatus, DiffFind, DiffFlag, @@ -35,7 +34,6 @@ from .enums import ( MergeAnalysis, MergePreference, ObjectType, - Option, ReferenceFilter, ReferenceType, ResetMode, @@ -52,38 +50,6 @@ LIBGIT2_VER_MAJOR: int LIBGIT2_VER_MINOR: int LIBGIT2_VER_REVISION: int LIBGIT2_VERSION: str -GIT_OPT_GET_MWINDOW_SIZE: int -GIT_OPT_SET_MWINDOW_SIZE: int -GIT_OPT_GET_MWINDOW_MAPPED_LIMIT: int -GIT_OPT_SET_MWINDOW_MAPPED_LIMIT: int -GIT_OPT_GET_SEARCH_PATH: int -GIT_OPT_SET_SEARCH_PATH: int -GIT_OPT_SET_CACHE_OBJECT_LIMIT: int -GIT_OPT_SET_CACHE_MAX_SIZE: int -GIT_OPT_ENABLE_CACHING: int -GIT_OPT_GET_CACHED_MEMORY: int -GIT_OPT_GET_TEMPLATE_PATH: int -GIT_OPT_SET_TEMPLATE_PATH: int -GIT_OPT_SET_SSL_CERT_LOCATIONS: int -GIT_OPT_SET_USER_AGENT: int -GIT_OPT_ENABLE_STRICT_OBJECT_CREATION: int -GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION: int -GIT_OPT_SET_SSL_CIPHERS: int -GIT_OPT_GET_USER_AGENT: int -GIT_OPT_ENABLE_OFS_DELTA: int -GIT_OPT_ENABLE_FSYNC_GITDIR: int -GIT_OPT_GET_WINDOWS_SHAREMODE: int -GIT_OPT_SET_WINDOWS_SHAREMODE: int -GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION: int -GIT_OPT_SET_ALLOCATOR: int -GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY: int -GIT_OPT_GET_PACK_MAX_OBJECTS: int -GIT_OPT_SET_PACK_MAX_OBJECTS: int -GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS: int -GIT_OPT_GET_OWNER_VALIDATION: int -GIT_OPT_SET_OWNER_VALIDATION: int -GIT_OPT_GET_MWINDOW_FILE_LIMIT: int -GIT_OPT_SET_MWINDOW_FILE_LIMIT: int GIT_OID_RAWSZ: int GIT_OID_HEXSZ: int GIT_OID_HEX_ZERO: str @@ -887,79 +853,6 @@ def discover_repository( def hash(data: bytes | str) -> Oid: ... def hashfile(path: str) -> Oid: ... def init_file_backend(path: str, flags: int = 0) -> object: ... -@overload -def option( - opt: Literal[ - Option.GET_MWINDOW_FILE_LIMIT, - Option.GET_MWINDOW_MAPPED_LIMIT, - Option.GET_MWINDOW_SIZE, - ], -) -> int: ... -@overload -def option( - opt: Literal[ - Option.SET_MWINDOW_FILE_LIMIT, - Option.SET_MWINDOW_MAPPED_LIMIT, - Option.SET_MWINDOW_SIZE, - ], - value: int, -) -> None: ... -@overload -def option(opt: Literal[Option.GET_SEARCH_PATH], level: ConfigLevel) -> str: ... -@overload -def option( - opt: Literal[Option.SET_SEARCH_PATH], level: ConfigLevel, value: str -) -> None: ... -@overload -def option( - opt: Literal[Option.SET_CACHE_OBJECT_LIMIT], object_type: ObjectType, limit: int -) -> None: ... -@overload -def option(opt: Literal[Option.SET_CACHE_MAX_SIZE], max_size: int) -> None: ... -@overload -def option(opt: Literal[Option.GET_CACHED_MEMORY]) -> tuple[int, int]: ... - -# not implemented: -# Option.GET_TEMPLATE_PATH -# Option.SET_TEMPLATE_PATH - -@overload -def option( - opt: Literal[Option.SET_SSL_CERT_LOCATIONS], - file: str | bytes | None, - dir: str | bytes | None, -) -> None: ... - -# not implemented: -# Option.SET_USER_AGENT - -@overload -def option( - opt: Literal[ - Option.ENABLE_CACHING, - Option.ENABLE_STRICT_OBJECT_CREATION, - Option.ENABLE_STRICT_SYMBOLIC_REF_CREATION, - Option.ENABLE_OFS_DELTA, - Option.ENABLE_FSYNC_GITDIR, - Option.ENABLE_STRICT_HASH_VERIFICATION, - Option.ENABLE_UNSAVED_INDEX_SAFETY, - Option.DISABLE_PACK_KEEP_FILE_CHECKS, - Option.SET_OWNER_VALIDATION, - ], - value: bool | Literal[0, 1], -) -> None: ... -@overload -def option(opt: Literal[Option.GET_OWNER_VALIDATION]) -> int: ... - -# not implemented: -# Option.SET_SSL_CIPHERS -# Option.GET_USER_AGENT -# Option.GET_WINDOWS_SHAREMODE -# Option.SET_WINDOWS_SHAREMODE -# Option.SET_ALLOCATOR -# Option.GET_PACK_MAX_OBJECTS -# Option.SET_PACK_MAX_OBJECTS - def reference_is_valid_name(refname: str) -> bool: ... def tree_entry_cmp(a: Object, b: Object) -> int: ... def _cache_enums() -> None: ... diff --git a/pygit2/_run.py b/pygit2/_run.py index f64ba0fa..44f30344 100644 --- a/pygit2/_run.py +++ b/pygit2/_run.py @@ -81,6 +81,7 @@ 'revert.h', 'stash.h', 'submodule.h', + 'options.h', 'callbacks.h', # Bridge from libgit2 to Python ] h_source = [] diff --git a/pygit2/decl/options.h b/pygit2/decl/options.h new file mode 100644 index 00000000..f6556d5e --- /dev/null +++ b/pygit2/decl/options.h @@ -0,0 +1,50 @@ +typedef enum { + GIT_OPT_GET_MWINDOW_SIZE, + GIT_OPT_SET_MWINDOW_SIZE, + GIT_OPT_GET_MWINDOW_MAPPED_LIMIT, + GIT_OPT_SET_MWINDOW_MAPPED_LIMIT, + GIT_OPT_GET_SEARCH_PATH, + GIT_OPT_SET_SEARCH_PATH, + GIT_OPT_SET_CACHE_OBJECT_LIMIT, + GIT_OPT_SET_CACHE_MAX_SIZE, + GIT_OPT_ENABLE_CACHING, + GIT_OPT_GET_CACHED_MEMORY, + GIT_OPT_GET_TEMPLATE_PATH, + GIT_OPT_SET_TEMPLATE_PATH, + GIT_OPT_SET_SSL_CERT_LOCATIONS, + GIT_OPT_SET_USER_AGENT, + GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, + GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION, + GIT_OPT_SET_SSL_CIPHERS, + GIT_OPT_GET_USER_AGENT, + GIT_OPT_ENABLE_OFS_DELTA, + GIT_OPT_ENABLE_FSYNC_GITDIR, + GIT_OPT_GET_WINDOWS_SHAREMODE, + GIT_OPT_SET_WINDOWS_SHAREMODE, + GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, + GIT_OPT_SET_ALLOCATOR, + GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY, + GIT_OPT_GET_PACK_MAX_OBJECTS, + GIT_OPT_SET_PACK_MAX_OBJECTS, + GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS, + GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, + GIT_OPT_GET_MWINDOW_FILE_LIMIT, + GIT_OPT_SET_MWINDOW_FILE_LIMIT, + GIT_OPT_SET_ODB_PACKED_PRIORITY, + GIT_OPT_SET_ODB_LOOSE_PRIORITY, + GIT_OPT_GET_EXTENSIONS, + GIT_OPT_SET_EXTENSIONS, + GIT_OPT_GET_OWNER_VALIDATION, + GIT_OPT_SET_OWNER_VALIDATION, + GIT_OPT_GET_HOMEDIR, + GIT_OPT_SET_HOMEDIR, + GIT_OPT_SET_SERVER_CONNECT_TIMEOUT, + GIT_OPT_GET_SERVER_CONNECT_TIMEOUT, + GIT_OPT_SET_SERVER_TIMEOUT, + GIT_OPT_GET_SERVER_TIMEOUT, + GIT_OPT_SET_USER_AGENT_PRODUCT, + GIT_OPT_GET_USER_AGENT_PRODUCT, + GIT_OPT_ADD_SSL_X509_CERT +} git_libgit2_opt_t; + +int git_libgit2_opts(int option, ...); \ No newline at end of file diff --git a/pygit2/enums.py b/pygit2/enums.py index fe642168..d690e73a 100644 --- a/pygit2/enums.py +++ b/pygit2/enums.py @@ -25,7 +25,7 @@ from enum import IntEnum, IntFlag -from . import _pygit2 +from . import _pygit2, options from .ffi import C @@ -948,51 +948,54 @@ class Option(IntEnum): """Global libgit2 library options""" # Commented out values --> exists in libgit2 but not supported in pygit2's options.c yet - GET_MWINDOW_SIZE = _pygit2.GIT_OPT_GET_MWINDOW_SIZE - SET_MWINDOW_SIZE = _pygit2.GIT_OPT_SET_MWINDOW_SIZE - GET_MWINDOW_MAPPED_LIMIT = _pygit2.GIT_OPT_GET_MWINDOW_MAPPED_LIMIT - SET_MWINDOW_MAPPED_LIMIT = _pygit2.GIT_OPT_SET_MWINDOW_MAPPED_LIMIT - GET_SEARCH_PATH = _pygit2.GIT_OPT_GET_SEARCH_PATH - SET_SEARCH_PATH = _pygit2.GIT_OPT_SET_SEARCH_PATH - SET_CACHE_OBJECT_LIMIT = _pygit2.GIT_OPT_SET_CACHE_OBJECT_LIMIT - SET_CACHE_MAX_SIZE = _pygit2.GIT_OPT_SET_CACHE_MAX_SIZE - ENABLE_CACHING = _pygit2.GIT_OPT_ENABLE_CACHING - GET_CACHED_MEMORY = _pygit2.GIT_OPT_GET_CACHED_MEMORY - GET_TEMPLATE_PATH = _pygit2.GIT_OPT_GET_TEMPLATE_PATH - SET_TEMPLATE_PATH = _pygit2.GIT_OPT_SET_TEMPLATE_PATH - SET_SSL_CERT_LOCATIONS = _pygit2.GIT_OPT_SET_SSL_CERT_LOCATIONS - SET_USER_AGENT = _pygit2.GIT_OPT_SET_USER_AGENT - ENABLE_STRICT_OBJECT_CREATION = _pygit2.GIT_OPT_ENABLE_STRICT_OBJECT_CREATION + GET_MWINDOW_SIZE = options.GIT_OPT_GET_MWINDOW_SIZE + SET_MWINDOW_SIZE = options.GIT_OPT_SET_MWINDOW_SIZE + GET_MWINDOW_MAPPED_LIMIT = options.GIT_OPT_GET_MWINDOW_MAPPED_LIMIT + SET_MWINDOW_MAPPED_LIMIT = options.GIT_OPT_SET_MWINDOW_MAPPED_LIMIT + GET_SEARCH_PATH = options.GIT_OPT_GET_SEARCH_PATH + SET_SEARCH_PATH = options.GIT_OPT_SET_SEARCH_PATH + SET_CACHE_OBJECT_LIMIT = options.GIT_OPT_SET_CACHE_OBJECT_LIMIT + SET_CACHE_MAX_SIZE = options.GIT_OPT_SET_CACHE_MAX_SIZE + ENABLE_CACHING = options.GIT_OPT_ENABLE_CACHING + GET_CACHED_MEMORY = options.GIT_OPT_GET_CACHED_MEMORY + GET_TEMPLATE_PATH = options.GIT_OPT_GET_TEMPLATE_PATH + SET_TEMPLATE_PATH = options.GIT_OPT_SET_TEMPLATE_PATH + SET_SSL_CERT_LOCATIONS = options.GIT_OPT_SET_SSL_CERT_LOCATIONS + SET_USER_AGENT = options.GIT_OPT_SET_USER_AGENT + ENABLE_STRICT_OBJECT_CREATION = options.GIT_OPT_ENABLE_STRICT_OBJECT_CREATION ENABLE_STRICT_SYMBOLIC_REF_CREATION = ( - _pygit2.GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION + options.GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION ) - SET_SSL_CIPHERS = _pygit2.GIT_OPT_SET_SSL_CIPHERS - GET_USER_AGENT = _pygit2.GIT_OPT_GET_USER_AGENT - ENABLE_OFS_DELTA = _pygit2.GIT_OPT_ENABLE_OFS_DELTA - ENABLE_FSYNC_GITDIR = _pygit2.GIT_OPT_ENABLE_FSYNC_GITDIR - GET_WINDOWS_SHAREMODE = _pygit2.GIT_OPT_GET_WINDOWS_SHAREMODE - SET_WINDOWS_SHAREMODE = _pygit2.GIT_OPT_SET_WINDOWS_SHAREMODE - ENABLE_STRICT_HASH_VERIFICATION = _pygit2.GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION - SET_ALLOCATOR = _pygit2.GIT_OPT_SET_ALLOCATOR - ENABLE_UNSAVED_INDEX_SAFETY = _pygit2.GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY - GET_PACK_MAX_OBJECTS = _pygit2.GIT_OPT_GET_PACK_MAX_OBJECTS - SET_PACK_MAX_OBJECTS = _pygit2.GIT_OPT_SET_PACK_MAX_OBJECTS - DISABLE_PACK_KEEP_FILE_CHECKS = _pygit2.GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS - # ENABLE_HTTP_EXPECT_CONTINUE = _pygit2.GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE - GET_MWINDOW_FILE_LIMIT = _pygit2.GIT_OPT_GET_MWINDOW_FILE_LIMIT - SET_MWINDOW_FILE_LIMIT = _pygit2.GIT_OPT_SET_MWINDOW_FILE_LIMIT - # SET_ODB_PACKED_PRIORITY = _pygit2.GIT_OPT_SET_ODB_PACKED_PRIORITY - # SET_ODB_LOOSE_PRIORITY = _pygit2.GIT_OPT_SET_ODB_LOOSE_PRIORITY - # GET_EXTENSIONS = _pygit2.GIT_OPT_GET_EXTENSIONS - # SET_EXTENSIONS = _pygit2.GIT_OPT_SET_EXTENSIONS - GET_OWNER_VALIDATION = _pygit2.GIT_OPT_GET_OWNER_VALIDATION - SET_OWNER_VALIDATION = _pygit2.GIT_OPT_SET_OWNER_VALIDATION - # GET_HOMEDIR = _pygit2.GIT_OPT_GET_HOMEDIR - # SET_HOMEDIR = _pygit2.GIT_OPT_SET_HOMEDIR - # SET_SERVER_CONNECT_TIMEOUT = _pygit2.GIT_OPT_SET_SERVER_CONNECT_TIMEOUT - # GET_SERVER_CONNECT_TIMEOUT = _pygit2.GIT_OPT_GET_SERVER_CONNECT_TIMEOUT - # SET_SERVER_TIMEOUT = _pygit2.GIT_OPT_SET_SERVER_TIMEOUT - # GET_SERVER_TIMEOUT = _pygit2.GIT_OPT_GET_SERVER_TIMEOUT + SET_SSL_CIPHERS = options.GIT_OPT_SET_SSL_CIPHERS + GET_USER_AGENT = options.GIT_OPT_GET_USER_AGENT + ENABLE_OFS_DELTA = options.GIT_OPT_ENABLE_OFS_DELTA + ENABLE_FSYNC_GITDIR = options.GIT_OPT_ENABLE_FSYNC_GITDIR + GET_WINDOWS_SHAREMODE = options.GIT_OPT_GET_WINDOWS_SHAREMODE + SET_WINDOWS_SHAREMODE = options.GIT_OPT_SET_WINDOWS_SHAREMODE + ENABLE_STRICT_HASH_VERIFICATION = options.GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION + SET_ALLOCATOR = options.GIT_OPT_SET_ALLOCATOR + ENABLE_UNSAVED_INDEX_SAFETY = options.GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY + GET_PACK_MAX_OBJECTS = options.GIT_OPT_GET_PACK_MAX_OBJECTS + SET_PACK_MAX_OBJECTS = options.GIT_OPT_SET_PACK_MAX_OBJECTS + DISABLE_PACK_KEEP_FILE_CHECKS = options.GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS + ENABLE_HTTP_EXPECT_CONTINUE = options.GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE + GET_MWINDOW_FILE_LIMIT = options.GIT_OPT_GET_MWINDOW_FILE_LIMIT + SET_MWINDOW_FILE_LIMIT = options.GIT_OPT_SET_MWINDOW_FILE_LIMIT + SET_ODB_PACKED_PRIORITY = options.GIT_OPT_SET_ODB_PACKED_PRIORITY + SET_ODB_LOOSE_PRIORITY = options.GIT_OPT_SET_ODB_LOOSE_PRIORITY + GET_EXTENSIONS = options.GIT_OPT_GET_EXTENSIONS + SET_EXTENSIONS = options.GIT_OPT_SET_EXTENSIONS + GET_OWNER_VALIDATION = options.GIT_OPT_GET_OWNER_VALIDATION + SET_OWNER_VALIDATION = options.GIT_OPT_SET_OWNER_VALIDATION + GET_HOMEDIR = options.GIT_OPT_GET_HOMEDIR + SET_HOMEDIR = options.GIT_OPT_SET_HOMEDIR + SET_SERVER_CONNECT_TIMEOUT = options.GIT_OPT_SET_SERVER_CONNECT_TIMEOUT + GET_SERVER_CONNECT_TIMEOUT = options.GIT_OPT_GET_SERVER_CONNECT_TIMEOUT + SET_SERVER_TIMEOUT = options.GIT_OPT_SET_SERVER_TIMEOUT + GET_SERVER_TIMEOUT = options.GIT_OPT_GET_SERVER_TIMEOUT + GET_USER_AGENT_PRODUCT = options.GIT_OPT_GET_USER_AGENT_PRODUCT + SET_USER_AGENT_PRODUCT = options.GIT_OPT_SET_USER_AGENT_PRODUCT + ADD_SSL_X509_CERT = options.GIT_OPT_ADD_SSL_X509_CERT class ReferenceFilter(IntEnum): diff --git a/pygit2/options.py b/pygit2/options.py new file mode 100644 index 00000000..06216aee --- /dev/null +++ b/pygit2/options.py @@ -0,0 +1,803 @@ +# Copyright 2010-2025 The pygit2 contributors +# +# This file is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2, +# as published by the Free Software Foundation. +# +# In addition to the permissions in the GNU General Public License, +# the authors give you unlimited permission to link the compiled +# version of this file into combinations with other programs, +# and to distribute those combinations without any restriction +# coming from the use of this file. (The General Public License +# restrictions do apply in other respects; for example, they cover +# modification of the file, and distribution when not linked into +# a combined executable.) +# +# This file is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; see the file COPYING. If not, write to +# the Free Software Foundation, 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301, USA. + +""" +Libgit2 global options management using CFFI. +""" + +from __future__ import annotations + +# Import only for type checking to avoid circular imports +from typing import TYPE_CHECKING, Any, Literal, cast, overload + +from .errors import check_error +from .ffi import C, ffi +from .utils import to_bytes, to_str + +if TYPE_CHECKING: + from ._libgit2.ffi import NULL_TYPE, ArrayC, char, char_pointer + from .enums import ConfigLevel, ObjectType, Option + +# Export GIT_OPT constants for backward compatibility +GIT_OPT_GET_MWINDOW_SIZE: int = C.GIT_OPT_GET_MWINDOW_SIZE +GIT_OPT_SET_MWINDOW_SIZE: int = C.GIT_OPT_SET_MWINDOW_SIZE +GIT_OPT_GET_MWINDOW_MAPPED_LIMIT: int = C.GIT_OPT_GET_MWINDOW_MAPPED_LIMIT +GIT_OPT_SET_MWINDOW_MAPPED_LIMIT: int = C.GIT_OPT_SET_MWINDOW_MAPPED_LIMIT +GIT_OPT_GET_SEARCH_PATH: int = C.GIT_OPT_GET_SEARCH_PATH +GIT_OPT_SET_SEARCH_PATH: int = C.GIT_OPT_SET_SEARCH_PATH +GIT_OPT_SET_CACHE_OBJECT_LIMIT: int = C.GIT_OPT_SET_CACHE_OBJECT_LIMIT +GIT_OPT_SET_CACHE_MAX_SIZE: int = C.GIT_OPT_SET_CACHE_MAX_SIZE +GIT_OPT_ENABLE_CACHING: int = C.GIT_OPT_ENABLE_CACHING +GIT_OPT_GET_CACHED_MEMORY: int = C.GIT_OPT_GET_CACHED_MEMORY +GIT_OPT_GET_TEMPLATE_PATH: int = C.GIT_OPT_GET_TEMPLATE_PATH +GIT_OPT_SET_TEMPLATE_PATH: int = C.GIT_OPT_SET_TEMPLATE_PATH +GIT_OPT_SET_SSL_CERT_LOCATIONS: int = C.GIT_OPT_SET_SSL_CERT_LOCATIONS +GIT_OPT_SET_USER_AGENT: int = C.GIT_OPT_SET_USER_AGENT +GIT_OPT_ENABLE_STRICT_OBJECT_CREATION: int = C.GIT_OPT_ENABLE_STRICT_OBJECT_CREATION +GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION: int = ( + C.GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION +) +GIT_OPT_SET_SSL_CIPHERS: int = C.GIT_OPT_SET_SSL_CIPHERS +GIT_OPT_GET_USER_AGENT: int = C.GIT_OPT_GET_USER_AGENT +GIT_OPT_ENABLE_OFS_DELTA: int = C.GIT_OPT_ENABLE_OFS_DELTA +GIT_OPT_ENABLE_FSYNC_GITDIR: int = C.GIT_OPT_ENABLE_FSYNC_GITDIR +GIT_OPT_GET_WINDOWS_SHAREMODE: int = C.GIT_OPT_GET_WINDOWS_SHAREMODE +GIT_OPT_SET_WINDOWS_SHAREMODE: int = C.GIT_OPT_SET_WINDOWS_SHAREMODE +GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION: int = C.GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION +GIT_OPT_SET_ALLOCATOR: int = C.GIT_OPT_SET_ALLOCATOR +GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY: int = C.GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY +GIT_OPT_GET_PACK_MAX_OBJECTS: int = C.GIT_OPT_GET_PACK_MAX_OBJECTS +GIT_OPT_SET_PACK_MAX_OBJECTS: int = C.GIT_OPT_SET_PACK_MAX_OBJECTS +GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS: int = C.GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS +GIT_OPT_GET_MWINDOW_FILE_LIMIT: int = C.GIT_OPT_GET_MWINDOW_FILE_LIMIT +GIT_OPT_SET_MWINDOW_FILE_LIMIT: int = C.GIT_OPT_SET_MWINDOW_FILE_LIMIT +GIT_OPT_GET_OWNER_VALIDATION: int = C.GIT_OPT_GET_OWNER_VALIDATION +GIT_OPT_SET_OWNER_VALIDATION: int = C.GIT_OPT_SET_OWNER_VALIDATION +GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE: int = C.GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE +GIT_OPT_SET_ODB_PACKED_PRIORITY: int = C.GIT_OPT_SET_ODB_PACKED_PRIORITY +GIT_OPT_SET_ODB_LOOSE_PRIORITY: int = C.GIT_OPT_SET_ODB_LOOSE_PRIORITY +GIT_OPT_GET_EXTENSIONS: int = C.GIT_OPT_GET_EXTENSIONS +GIT_OPT_SET_EXTENSIONS: int = C.GIT_OPT_SET_EXTENSIONS +GIT_OPT_GET_HOMEDIR: int = C.GIT_OPT_GET_HOMEDIR +GIT_OPT_SET_HOMEDIR: int = C.GIT_OPT_SET_HOMEDIR +GIT_OPT_SET_SERVER_CONNECT_TIMEOUT: int = C.GIT_OPT_SET_SERVER_CONNECT_TIMEOUT +GIT_OPT_GET_SERVER_CONNECT_TIMEOUT: int = C.GIT_OPT_GET_SERVER_CONNECT_TIMEOUT +GIT_OPT_SET_SERVER_TIMEOUT: int = C.GIT_OPT_SET_SERVER_TIMEOUT +GIT_OPT_GET_SERVER_TIMEOUT: int = C.GIT_OPT_GET_SERVER_TIMEOUT +GIT_OPT_GET_USER_AGENT_PRODUCT: int = C.GIT_OPT_GET_USER_AGENT_PRODUCT +GIT_OPT_SET_USER_AGENT_PRODUCT: int = C.GIT_OPT_SET_USER_AGENT_PRODUCT +GIT_OPT_ADD_SSL_X509_CERT: int = C.GIT_OPT_ADD_SSL_X509_CERT + + +NOT_PASSED = object() + + +def check_args(option: Option, arg1: Any, arg2: Any, expected: int) -> None: + if expected == 0 and (arg1 is not NOT_PASSED or arg2 is not NOT_PASSED): + raise TypeError(f'option({option}) takes no additional arguments') + + if expected == 1 and (arg1 is NOT_PASSED or arg2 is not NOT_PASSED): + raise TypeError(f'option({option}, x) requires 1 additional argument') + + if expected == 2 and (arg1 is NOT_PASSED or arg2 is NOT_PASSED): + raise TypeError(f'option({option}, x, y) requires 2 additional arguments') + + +@overload +def option( + option_type: Literal[ + Option.GET_MWINDOW_SIZE, + Option.GET_MWINDOW_MAPPED_LIMIT, + Option.GET_MWINDOW_FILE_LIMIT, + ], +) -> int: ... + + +@overload +def option( + option_type: Literal[ + Option.SET_MWINDOW_SIZE, + Option.SET_MWINDOW_MAPPED_LIMIT, + Option.SET_MWINDOW_FILE_LIMIT, + Option.SET_CACHE_MAX_SIZE, + ], + arg1: int, # value +) -> None: ... + + +@overload +def option( + option_type: Literal[Option.GET_SEARCH_PATH], + arg1: ConfigLevel, # value +) -> str: ... + + +@overload +def option( + option_type: Literal[Option.SET_SEARCH_PATH], + arg1: ConfigLevel, # type + arg2: str, # value +) -> None: ... + + +@overload +def option( + option_type: Literal[Option.SET_CACHE_OBJECT_LIMIT], + arg1: ObjectType, # type + arg2: int, # limit +) -> None: ... + + +@overload +def option(option_type: Literal[Option.GET_CACHED_MEMORY]) -> tuple[int, int]: ... + + +@overload +def option( + option_type: Literal[Option.SET_SSL_CERT_LOCATIONS], + arg1: str | bytes | None, # cert_file + arg2: str | bytes | None, # cert_dir +) -> None: ... + + +@overload +def option( + option_type: Literal[ + Option.ENABLE_CACHING, + Option.ENABLE_STRICT_OBJECT_CREATION, + Option.ENABLE_STRICT_SYMBOLIC_REF_CREATION, + Option.ENABLE_OFS_DELTA, + Option.ENABLE_FSYNC_GITDIR, + Option.ENABLE_STRICT_HASH_VERIFICATION, + Option.ENABLE_UNSAVED_INDEX_SAFETY, + Option.DISABLE_PACK_KEEP_FILE_CHECKS, + Option.SET_OWNER_VALIDATION, + ], + arg1: bool, # value +) -> None: ... + + +@overload +def option(option_type: Literal[Option.GET_OWNER_VALIDATION]) -> bool: ... + + +@overload +def option( + option_type: Literal[ + Option.GET_TEMPLATE_PATH, + Option.GET_USER_AGENT, + Option.GET_HOMEDIR, + Option.GET_USER_AGENT_PRODUCT, + ], +) -> str | None: ... + + +@overload +def option( + option_type: Literal[ + Option.SET_TEMPLATE_PATH, + Option.SET_USER_AGENT, + Option.SET_SSL_CIPHERS, + Option.SET_HOMEDIR, + Option.SET_USER_AGENT_PRODUCT, + ], + arg1: str | bytes, # value +) -> None: ... + + +@overload +def option( + option_type: Literal[ + Option.GET_WINDOWS_SHAREMODE, + Option.GET_PACK_MAX_OBJECTS, + Option.GET_SERVER_CONNECT_TIMEOUT, + Option.GET_SERVER_TIMEOUT, + ], +) -> int: ... + + +@overload +def option( + option_type: Literal[ + Option.SET_WINDOWS_SHAREMODE, + Option.SET_PACK_MAX_OBJECTS, + Option.ENABLE_HTTP_EXPECT_CONTINUE, + Option.SET_ODB_PACKED_PRIORITY, + Option.SET_ODB_LOOSE_PRIORITY, + Option.SET_SERVER_CONNECT_TIMEOUT, + Option.SET_SERVER_TIMEOUT, + ], + arg1: int, # value +) -> None: ... + + +@overload +def option(option_type: Literal[Option.GET_EXTENSIONS]) -> list[str]: ... + + +@overload +def option( + option_type: Literal[Option.SET_EXTENSIONS], + arg1: list[str], # extensions + arg2: int, # length +) -> None: ... + + +@overload +def option( + option_type: Literal[Option.ADD_SSL_X509_CERT], + arg1: str | bytes, # certificate +) -> None: ... + + +# Fallback overload for generic Option values (used in tests) +@overload +def option(option_type: Option, arg1: Any = ..., arg2: Any = ...) -> Any: ... + + +def option(option_type: Option, arg1: Any = NOT_PASSED, arg2: Any = NOT_PASSED) -> Any: + """ + Get or set a libgit2 option. + + Parameters: + + GIT_OPT_GET_SEARCH_PATH, level + Get the config search path for the given level. + + GIT_OPT_SET_SEARCH_PATH, level, path + Set the config search path for the given level. + + GIT_OPT_GET_MWINDOW_SIZE + Get the maximum mmap window size. + + GIT_OPT_SET_MWINDOW_SIZE, size + Set the maximum mmap window size. + + GIT_OPT_GET_MWINDOW_FILE_LIMIT + Get the maximum number of files that will be mapped at any time by the library. + + GIT_OPT_SET_MWINDOW_FILE_LIMIT, size + Set the maximum number of files that can be mapped at any time by the library. The default (0) is unlimited. + + GIT_OPT_GET_OWNER_VALIDATION + Gets the owner validation setting for repository directories. + + GIT_OPT_SET_OWNER_VALIDATION, enabled + Set that repository directories should be owned by the current user. + The default is to validate ownership. + + GIT_OPT_GET_TEMPLATE_PATH + Get the default template path. + + GIT_OPT_SET_TEMPLATE_PATH, path + Set the default template path. + + GIT_OPT_GET_USER_AGENT + Get the user agent string. + + GIT_OPT_SET_USER_AGENT, user_agent + Set the user agent string. + + GIT_OPT_GET_PACK_MAX_OBJECTS + Get the maximum number of objects to include in a pack. + + GIT_OPT_SET_PACK_MAX_OBJECTS, count + Set the maximum number of objects to include in a pack. + """ + + if option_type in ( + C.GIT_OPT_GET_MWINDOW_SIZE, + C.GIT_OPT_GET_MWINDOW_MAPPED_LIMIT, + C.GIT_OPT_GET_MWINDOW_FILE_LIMIT, + ): + check_args(option_type, arg1, arg2, 0) + + size_ptr = ffi.new('size_t *') + err = C.git_libgit2_opts(option_type, size_ptr) + check_error(err) + return size_ptr[0] + + elif option_type in ( + C.GIT_OPT_SET_MWINDOW_SIZE, + C.GIT_OPT_SET_MWINDOW_MAPPED_LIMIT, + C.GIT_OPT_SET_MWINDOW_FILE_LIMIT, + ): + check_args(option_type, arg1, arg2, 1) + + if not isinstance(arg1, int): + raise TypeError(f'option value must be an integer, not {type(arg1)}') + size = arg1 + if size < 0: + raise ValueError('size must be non-negative') + + err = C.git_libgit2_opts(option_type, ffi.cast('size_t', size)) + check_error(err) + return None + + elif option_type == C.GIT_OPT_GET_SEARCH_PATH: + check_args(option_type, arg1, arg2, 1) + + level = int(arg1) # Convert enum to int + buf = ffi.new('git_buf *') + err = C.git_libgit2_opts(option_type, ffi.cast('int', level), buf) + check_error(err) + + try: + if buf.ptr != ffi.NULL: + result = to_str(ffi.string(buf.ptr)) + else: + result = None + finally: + C.git_buf_dispose(buf) + + return result + + elif option_type == C.GIT_OPT_SET_SEARCH_PATH: + check_args(option_type, arg1, arg2, 2) + + level = int(arg1) # Convert enum to int + path = arg2 + + path_cdata: ArrayC[char] | NULL_TYPE + if path is None: + path_cdata = ffi.NULL + else: + path_bytes = to_bytes(path) + path_cdata = ffi.new('char[]', path_bytes) + + err = C.git_libgit2_opts(option_type, ffi.cast('int', level), path_cdata) + check_error(err) + return None + + elif option_type == C.GIT_OPT_SET_CACHE_OBJECT_LIMIT: + check_args(option_type, arg1, arg2, 2) + + object_type = int(arg1) # Convert enum to int + if not isinstance(arg2, int): + raise TypeError( + f'option value must be an integer, not {type(arg2).__name__}' + ) + size = arg2 + if size < 0: + raise ValueError('size must be non-negative') + + err = C.git_libgit2_opts( + option_type, ffi.cast('int', object_type), ffi.cast('size_t', size) + ) + check_error(err) + return None + + elif option_type == C.GIT_OPT_SET_CACHE_MAX_SIZE: + check_args(option_type, arg1, arg2, 1) + + size = arg1 + if not isinstance(size, int): + raise TypeError( + f'option value must be an integer, not {type(size).__name__}' + ) + + err = C.git_libgit2_opts(option_type, ffi.cast('ssize_t', size)) + check_error(err) + return None + + elif option_type == C.GIT_OPT_GET_CACHED_MEMORY: + check_args(option_type, arg1, arg2, 0) + + current_ptr = ffi.new('ssize_t *') + allowed_ptr = ffi.new('ssize_t *') + err = C.git_libgit2_opts(option_type, current_ptr, allowed_ptr) + check_error(err) + return (current_ptr[0], allowed_ptr[0]) + + elif option_type == C.GIT_OPT_SET_SSL_CERT_LOCATIONS: + check_args(option_type, arg1, arg2, 2) + + cert_file = arg1 + cert_dir = arg2 + + cert_file_cdata: ArrayC[char] | NULL_TYPE + if cert_file is None: + cert_file_cdata = ffi.NULL + else: + cert_file_bytes = to_bytes(cert_file) + cert_file_cdata = ffi.new('char[]', cert_file_bytes) + + cert_dir_cdata: ArrayC[char] | NULL_TYPE + if cert_dir is None: + cert_dir_cdata = ffi.NULL + else: + cert_dir_bytes = to_bytes(cert_dir) + cert_dir_cdata = ffi.new('char[]', cert_dir_bytes) + + err = C.git_libgit2_opts(option_type, cert_file_cdata, cert_dir_cdata) + check_error(err) + return None + + # Handle boolean/int enable/disable options + elif option_type in ( + C.GIT_OPT_ENABLE_CACHING, + C.GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, + C.GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION, + C.GIT_OPT_ENABLE_OFS_DELTA, + C.GIT_OPT_ENABLE_FSYNC_GITDIR, + C.GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, + C.GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY, + C.GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS, + C.GIT_OPT_SET_OWNER_VALIDATION, + ): + check_args(option_type, arg1, arg2, 1) + + enabled = arg1 + # Convert to int (0 or 1) + value = 1 if enabled else 0 + + err = C.git_libgit2_opts(option_type, ffi.cast('int', value)) + check_error(err) + return None + + elif option_type == C.GIT_OPT_GET_OWNER_VALIDATION: + check_args(option_type, arg1, arg2, 0) + + enabled_ptr = ffi.new('int *') + err = C.git_libgit2_opts(option_type, enabled_ptr) + check_error(err) + return bool(enabled_ptr[0]) + + elif option_type == C.GIT_OPT_GET_TEMPLATE_PATH: + check_args(option_type, arg1, arg2, 0) + + buf = ffi.new('git_buf *') + err = C.git_libgit2_opts(option_type, buf) + check_error(err) + + try: + if buf.ptr != ffi.NULL: + result = to_str(ffi.string(buf.ptr)) + else: + result = None + finally: + C.git_buf_dispose(buf) + + return result + + elif option_type == C.GIT_OPT_SET_TEMPLATE_PATH: + check_args(option_type, arg1, arg2, 1) + + path = arg1 + template_path_cdata: ArrayC[char] | NULL_TYPE + if path is None: + template_path_cdata = ffi.NULL + else: + path_bytes = to_bytes(path) + template_path_cdata = ffi.new('char[]', path_bytes) + + err = C.git_libgit2_opts(option_type, template_path_cdata) + check_error(err) + return None + + elif option_type == C.GIT_OPT_GET_USER_AGENT: + check_args(option_type, arg1, arg2, 0) + + buf = ffi.new('git_buf *') + err = C.git_libgit2_opts(option_type, buf) + check_error(err) + + try: + if buf.ptr != ffi.NULL: + result = to_str(ffi.string(buf.ptr)) + else: + result = None + finally: + C.git_buf_dispose(buf) + + return result + + elif option_type == C.GIT_OPT_SET_USER_AGENT: + check_args(option_type, arg1, arg2, 1) + + agent = arg1 + agent_bytes = to_bytes(agent) + agent_cdata = ffi.new('char[]', agent_bytes) + + err = C.git_libgit2_opts(option_type, agent_cdata) + check_error(err) + return None + + elif option_type == C.GIT_OPT_SET_SSL_CIPHERS: + check_args(option_type, arg1, arg2, 1) + + ciphers = arg1 + ciphers_bytes = to_bytes(ciphers) + ciphers_cdata = ffi.new('char[]', ciphers_bytes) + + err = C.git_libgit2_opts(option_type, ciphers_cdata) + check_error(err) + return None + + # Handle GET_WINDOWS_SHAREMODE + elif option_type == C.GIT_OPT_GET_WINDOWS_SHAREMODE: + check_args(option_type, arg1, arg2, 0) + + value_ptr = ffi.new('unsigned int *') + err = C.git_libgit2_opts(option_type, value_ptr) + check_error(err) + return value_ptr[0] + + # Handle SET_WINDOWS_SHAREMODE + elif option_type == C.GIT_OPT_SET_WINDOWS_SHAREMODE: + check_args(option_type, arg1, arg2, 1) + + if not isinstance(arg1, int): + raise TypeError( + f'option value must be an integer, not {type(arg1).__name__}' + ) + value = arg1 + if value < 0: + raise ValueError('value must be non-negative') + + err = C.git_libgit2_opts(option_type, ffi.cast('unsigned int', value)) + check_error(err) + return None + + # Handle GET_PACK_MAX_OBJECTS + elif option_type == C.GIT_OPT_GET_PACK_MAX_OBJECTS: + check_args(option_type, arg1, arg2, 0) + + size_ptr = ffi.new('size_t *') + err = C.git_libgit2_opts(option_type, size_ptr) + check_error(err) + return size_ptr[0] + + # Handle SET_PACK_MAX_OBJECTS + elif option_type == C.GIT_OPT_SET_PACK_MAX_OBJECTS: + check_args(option_type, arg1, arg2, 1) + + if not isinstance(arg1, int): + raise TypeError( + f'option value must be an integer, not {type(arg1).__name__}' + ) + size = arg1 + if size < 0: + raise ValueError('size must be non-negative') + + err = C.git_libgit2_opts(option_type, ffi.cast('size_t', size)) + check_error(err) + return None + + # Handle ENABLE_HTTP_EXPECT_CONTINUE + elif option_type == C.GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE: + check_args(option_type, arg1, arg2, 1) + + enabled = arg1 + # Convert to int (0 or 1) + value = 1 if enabled else 0 + + err = C.git_libgit2_opts(option_type, ffi.cast('int', value)) + check_error(err) + return None + + # Handle SET_ODB_PACKED_PRIORITY + elif option_type == C.GIT_OPT_SET_ODB_PACKED_PRIORITY: + check_args(option_type, arg1, arg2, 1) + + if not isinstance(arg1, int): + raise TypeError( + f'option value must be an integer, not {type(arg1).__name__}' + ) + priority = arg1 + + err = C.git_libgit2_opts(option_type, ffi.cast('int', priority)) + check_error(err) + return None + + # Handle SET_ODB_LOOSE_PRIORITY + elif option_type == C.GIT_OPT_SET_ODB_LOOSE_PRIORITY: + check_args(option_type, arg1, arg2, 1) + + if not isinstance(arg1, int): + raise TypeError( + f'option value must be an integer, not {type(arg1).__name__}' + ) + priority = arg1 + + err = C.git_libgit2_opts(option_type, ffi.cast('int', priority)) + check_error(err) + return None + + # Handle GET_EXTENSIONS + elif option_type == C.GIT_OPT_GET_EXTENSIONS: + check_args(option_type, arg1, arg2, 0) + + # GET_EXTENSIONS expects a git_strarray pointer + strarray = ffi.new('git_strarray *') + err = C.git_libgit2_opts(option_type, strarray) + check_error(err) + + result = [] + try: + if strarray.strings != ffi.NULL: + # Cast to the non-NULL type for type checking + strings = cast('ArrayC[char_pointer]', strarray.strings) + for i in range(strarray.count): + if strings[i] != ffi.NULL: + result.append(to_str(ffi.string(strings[i]))) + finally: + # Must dispose of the strarray to free the memory + C.git_strarray_dispose(strarray) + + return result + + # Handle SET_EXTENSIONS + elif option_type == C.GIT_OPT_SET_EXTENSIONS: + check_args(option_type, arg1, arg2, 2) + + extensions = arg1 + length = arg2 + + if not isinstance(extensions, list): + raise TypeError('extensions must be a list of strings') + if not isinstance(length, int): + raise TypeError('length must be an integer') + + # Create array of char pointers + # libgit2 will make its own copies with git__strdup + ext_array: ArrayC[char_pointer] = ffi.new('char *[]', len(extensions)) + ext_strings: list[ArrayC[char]] = [] # Keep references during the call + + for i, ext in enumerate(extensions): + ext_bytes = to_bytes(ext) + ext_string: ArrayC[char] = ffi.new('char[]', ext_bytes) + ext_strings.append(ext_string) + ext_array[i] = ffi.cast('char *', ext_string) + + err = C.git_libgit2_opts(option_type, ext_array, ffi.cast('size_t', length)) + check_error(err) + return None + + # Handle GET_HOMEDIR + elif option_type == C.GIT_OPT_GET_HOMEDIR: + check_args(option_type, arg1, arg2, 0) + + buf = ffi.new('git_buf *') + err = C.git_libgit2_opts(option_type, buf) + check_error(err) + + try: + if buf.ptr != ffi.NULL: + result = to_str(ffi.string(buf.ptr)) + else: + result = None + finally: + C.git_buf_dispose(buf) + + return result + + # Handle SET_HOMEDIR + elif option_type == C.GIT_OPT_SET_HOMEDIR: + check_args(option_type, arg1, arg2, 1) + + path = arg1 + homedir_cdata: ArrayC[char] | NULL_TYPE + if path is None: + homedir_cdata = ffi.NULL + else: + path_bytes = to_bytes(path) + homedir_cdata = ffi.new('char[]', path_bytes) + + err = C.git_libgit2_opts(option_type, homedir_cdata) + check_error(err) + return None + + # Handle GET_SERVER_CONNECT_TIMEOUT + elif option_type == C.GIT_OPT_GET_SERVER_CONNECT_TIMEOUT: + check_args(option_type, arg1, arg2, 0) + + timeout_ptr = ffi.new('int *') + err = C.git_libgit2_opts(option_type, timeout_ptr) + check_error(err) + return timeout_ptr[0] + + # Handle SET_SERVER_CONNECT_TIMEOUT + elif option_type == C.GIT_OPT_SET_SERVER_CONNECT_TIMEOUT: + check_args(option_type, arg1, arg2, 1) + + if not isinstance(arg1, int): + raise TypeError( + f'option value must be an integer, not {type(arg1).__name__}' + ) + timeout = arg1 + + err = C.git_libgit2_opts(option_type, ffi.cast('int', timeout)) + check_error(err) + return None + + # Handle GET_SERVER_TIMEOUT + elif option_type == C.GIT_OPT_GET_SERVER_TIMEOUT: + check_args(option_type, arg1, arg2, 0) + + timeout_ptr = ffi.new('int *') + err = C.git_libgit2_opts(option_type, timeout_ptr) + check_error(err) + return timeout_ptr[0] + + # Handle SET_SERVER_TIMEOUT + elif option_type == C.GIT_OPT_SET_SERVER_TIMEOUT: + check_args(option_type, arg1, arg2, 1) + + if not isinstance(arg1, int): + raise TypeError( + f'option value must be an integer, not {type(arg1).__name__}' + ) + timeout = arg1 + + err = C.git_libgit2_opts(option_type, ffi.cast('int', timeout)) + check_error(err) + return None + + # Handle GET_USER_AGENT_PRODUCT + elif option_type == C.GIT_OPT_GET_USER_AGENT_PRODUCT: + check_args(option_type, arg1, arg2, 0) + + buf = ffi.new('git_buf *') + err = C.git_libgit2_opts(option_type, buf) + check_error(err) + + try: + if buf.ptr != ffi.NULL: + result = to_str(ffi.string(buf.ptr)) + else: + result = None + finally: + C.git_buf_dispose(buf) + + return result + + # Handle SET_USER_AGENT_PRODUCT + elif option_type == C.GIT_OPT_SET_USER_AGENT_PRODUCT: + check_args(option_type, arg1, arg2, 1) + + product = arg1 + product_bytes = to_bytes(product) + product_cdata = ffi.new('char[]', product_bytes) + + err = C.git_libgit2_opts(option_type, product_cdata) + check_error(err) + return None + + # Not implemented - ADD_SSL_X509_CERT requires directly binding with OpenSSL + # as the API works accepts a X509* struct. Use GIT_OPT_SET_SSL_CERT_LOCATIONS + # instead. + elif option_type == C.GIT_OPT_ADD_SSL_X509_CERT: + raise NotImplementedError('Use GIT_OPT_SET_SSL_CERT_LOCATIONS instead') + + # Not implemented - SET_ALLOCATOR is not feasible from Python level + # because it requires providing C function pointers for memory management + # (malloc, free, etc.) that must handle raw memory at the C level, + # which cannot be safely implemented in pure Python. + elif option_type == C.GIT_OPT_SET_ALLOCATOR: + raise NotImplementedError('Setting a custom allocator not possible from Python') + + else: + raise ValueError(f'Invalid option {option_type}') diff --git a/pygit2/settings.py b/pygit2/settings.py index da63d979..91b5d319 100644 --- a/pygit2/settings.py +++ b/pygit2/settings.py @@ -32,9 +32,9 @@ import pygit2.enums -from ._pygit2 import option from .enums import ConfigLevel, Option from .errors import GitError +from .options import option class SearchPathList: @@ -105,6 +105,15 @@ def mwindow_mapped_limit(self) -> int: def mwindow_mapped_limit(self, value: int) -> None: option(Option.SET_MWINDOW_MAPPED_LIMIT, value) + @property + def mwindow_file_limit(self) -> int: + """Get or set the maximum number of files to be mapped at any time""" + return option(Option.GET_MWINDOW_FILE_LIMIT) + + @mwindow_file_limit.setter + def mwindow_file_limit(self, value: int) -> None: + option(Option.SET_MWINDOW_FILE_LIMIT, value) + @property def cached_memory(self) -> tuple[int, int]: """ @@ -207,3 +216,133 @@ def set_ssl_cert_locations( option(Option.SET_SSL_CERT_LOCATIONS, cert_file, cert_dir) self._ssl_cert_file = cert_file self._ssl_cert_dir = cert_dir + + @property + def template_path(self) -> str | None: + """Get or set the default template path for new repositories""" + return option(Option.GET_TEMPLATE_PATH) + + @template_path.setter + def template_path(self, value: str | bytes) -> None: + option(Option.SET_TEMPLATE_PATH, value) + + @property + def user_agent(self) -> str | None: + """Get or set the user agent string for network operations""" + return option(Option.GET_USER_AGENT) + + @user_agent.setter + def user_agent(self, value: str | bytes) -> None: + option(Option.SET_USER_AGENT, value) + + @property + def user_agent_product(self) -> str | None: + """Get or set the user agent product name""" + return option(Option.GET_USER_AGENT_PRODUCT) + + @user_agent_product.setter + def user_agent_product(self, value: str | bytes) -> None: + option(Option.SET_USER_AGENT_PRODUCT, value) + + def set_ssl_ciphers(self, ciphers: str | bytes) -> None: + """Set the SSL ciphers to use for HTTPS connections""" + option(Option.SET_SSL_CIPHERS, ciphers) + + def enable_strict_object_creation(self, value: bool = True) -> None: + """Enable or disable strict object creation validation""" + option(Option.ENABLE_STRICT_OBJECT_CREATION, value) + + def enable_strict_symbolic_ref_creation(self, value: bool = True) -> None: + """Enable or disable strict symbolic reference creation validation""" + option(Option.ENABLE_STRICT_SYMBOLIC_REF_CREATION, value) + + def enable_ofs_delta(self, value: bool = True) -> None: + """Enable or disable offset delta encoding""" + option(Option.ENABLE_OFS_DELTA, value) + + def enable_fsync_gitdir(self, value: bool = True) -> None: + """Enable or disable fsync for git directory operations""" + option(Option.ENABLE_FSYNC_GITDIR, value) + + def enable_strict_hash_verification(self, value: bool = True) -> None: + """Enable or disable strict hash verification""" + option(Option.ENABLE_STRICT_HASH_VERIFICATION, value) + + def enable_unsaved_index_safety(self, value: bool = True) -> None: + """Enable or disable unsaved index safety checks""" + option(Option.ENABLE_UNSAVED_INDEX_SAFETY, value) + + def enable_http_expect_continue(self, value: bool = True) -> None: + """Enable or disable HTTP Expect/Continue for large pushes""" + option(Option.ENABLE_HTTP_EXPECT_CONTINUE, value) + + @property + def windows_sharemode(self) -> int: + """Get or set the Windows share mode for opening files""" + return option(Option.GET_WINDOWS_SHAREMODE) + + @windows_sharemode.setter + def windows_sharemode(self, value: int) -> None: + option(Option.SET_WINDOWS_SHAREMODE, value) + + @property + def pack_max_objects(self) -> int: + """Get or set the maximum number of objects in a pack""" + return option(Option.GET_PACK_MAX_OBJECTS) + + @pack_max_objects.setter + def pack_max_objects(self, value: int) -> None: + option(Option.SET_PACK_MAX_OBJECTS, value) + + @property + def owner_validation(self) -> bool: + """Get or set repository directory ownership validation""" + return option(Option.GET_OWNER_VALIDATION) + + @owner_validation.setter + def owner_validation(self, value: bool) -> None: + option(Option.SET_OWNER_VALIDATION, value) + + def set_odb_packed_priority(self, priority: int) -> None: + """Set the priority for packed ODB backend (default 1)""" + option(Option.SET_ODB_PACKED_PRIORITY, priority) + + def set_odb_loose_priority(self, priority: int) -> None: + """Set the priority for loose ODB backend (default 2)""" + option(Option.SET_ODB_LOOSE_PRIORITY, priority) + + @property + def extensions(self) -> list[str]: + """Get the list of enabled extensions""" + return option(Option.GET_EXTENSIONS) + + def set_extensions(self, extensions: list[str]) -> None: + """Set the list of enabled extensions""" + option(Option.SET_EXTENSIONS, extensions, len(extensions)) + + @property + def homedir(self) -> str | None: + """Get or set the home directory""" + return option(Option.GET_HOMEDIR) + + @homedir.setter + def homedir(self, value: str | bytes) -> None: + option(Option.SET_HOMEDIR, value) + + @property + def server_connect_timeout(self) -> int: + """Get or set the server connection timeout in milliseconds""" + return option(Option.GET_SERVER_CONNECT_TIMEOUT) + + @server_connect_timeout.setter + def server_connect_timeout(self, value: int) -> None: + option(Option.SET_SERVER_CONNECT_TIMEOUT, value) + + @property + def server_timeout(self) -> int: + """Get or set the server timeout in milliseconds""" + return option(Option.GET_SERVER_TIMEOUT) + + @server_timeout.setter + def server_timeout(self, value: int) -> None: + option(Option.SET_SERVER_TIMEOUT, value) diff --git a/pygit2/utils.py b/pygit2/utils.py index b3d5d141..dcc77167 100644 --- a/pygit2/utils.py +++ b/pygit2/utils.py @@ -145,7 +145,7 @@ class StrArray: __array: 'GitStrrayC | ffi.NULL_TYPE' __strings: list['None | ArrayC[char]'] - __arr: 'ArrayC[char]' + __arr: 'ArrayC[char_pointer]' def __init__(self, lst: None | Sequence[str | os.PathLike[str]]): # Allow passing in None as lg2 typically considers them the same as empty @@ -164,7 +164,7 @@ def __init__(self, lst: None | Sequence[str | os.PathLike[str]]): strings[i] = ffi.new('char []', to_bytes(li)) - self.__arr = ffi.new('char *[]', strings) # type: ignore[call-overload] + self.__arr = ffi.new('char *[]', strings) self.__strings = strings self.__array = ffi.new('git_strarray *', [self.__arr, len(strings)]) # type: ignore[call-overload] diff --git a/src/options.c b/src/options.c deleted file mode 100644 index 11711400..00000000 --- a/src/options.c +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Copyright 2010-2025 The pygit2 contributors - * - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#define PY_SSIZE_T_CLEAN -#include -#include -#include "error.h" -#include "types.h" -#include "utils.h" - -extern PyObject *GitError; - -static PyObject * -get_search_path(long level) -{ - git_buf buf = {NULL}; - PyObject *py_path; - int err; - - err = git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, level, &buf); - if (err < 0) - return Error_set(err); - - py_path = to_unicode_n(buf.ptr, buf.size, NULL, NULL); - git_buf_dispose(&buf); - - if (!py_path) - return NULL; - - return py_path; -} - -PyObject * -option(PyObject *self, PyObject *args) -{ - long option; - int error; - PyObject *py_option; - - py_option = PyTuple_GetItem(args, 0); - if (!py_option) - return NULL; - - if (!PyLong_Check(py_option)) - return Error_type_error( - "option should be an integer, got %.200s", py_option); - - option = PyLong_AsLong(py_option); - - switch (option) { - - case GIT_OPT_GET_MWINDOW_FILE_LIMIT: - case GIT_OPT_GET_MWINDOW_MAPPED_LIMIT: - case GIT_OPT_GET_MWINDOW_SIZE: - { - size_t value; - - error = git_libgit2_opts(option, &value); - if (error < 0) - return Error_set(error); - - return PyLong_FromSize_t(value); - } - - case GIT_OPT_SET_MWINDOW_FILE_LIMIT: - case GIT_OPT_SET_MWINDOW_MAPPED_LIMIT: - case GIT_OPT_SET_MWINDOW_SIZE: - { - PyObject *py_value = PyTuple_GetItem(args, 1); - if (!py_value) - return NULL; - - if (!PyLong_Check(py_value)) - return Error_type_error("expected integer, got %.200s", py_value); - - size_t value = PyLong_AsSize_t(py_value); - error = git_libgit2_opts(option, value); - if (error < 0) - return Error_set(error); - - Py_RETURN_NONE; - } - - case GIT_OPT_GET_SEARCH_PATH: - { - PyObject *py_level = PyTuple_GetItem(args, 1); - if (!py_level) - return NULL; - - if (!PyLong_Check(py_level)) - return Error_type_error("level should be an integer, got %.200s", py_level); - - return get_search_path(PyLong_AsLong(py_level)); - } - - case GIT_OPT_SET_SEARCH_PATH: - { - PyObject *py_level = PyTuple_GetItem(args, 1); - if (!py_level) - return NULL; - - PyObject *py_path = PyTuple_GetItem(args, 2); - if (!py_path) - return NULL; - - if (!PyLong_Check(py_level)) - return Error_type_error("level should be an integer, got %.200s", py_level); - - const char *path = pgit_borrow(py_path); - if (!path) - return NULL; - - int err = git_libgit2_opts(option, PyLong_AsLong(py_level), path); - if (err < 0) - return Error_set(err); - - Py_RETURN_NONE; - } - - case GIT_OPT_SET_CACHE_OBJECT_LIMIT: - { - size_t limit; - int object_type; - PyObject *py_object_type, *py_limit; - - py_object_type = PyTuple_GetItem(args, 1); - if (!py_object_type) - return NULL; - - py_limit = PyTuple_GetItem(args, 2); - if (!py_limit) - return NULL; - - if (!PyLong_Check(py_limit)) - return Error_type_error( - "limit should be an integer, got %.200s", py_limit); - - object_type = PyLong_AsLong(py_object_type); - limit = PyLong_AsSize_t(py_limit); - error = git_libgit2_opts(option, object_type, limit); - - if (error < 0) - return Error_set(error); - - Py_RETURN_NONE; - } - - case GIT_OPT_SET_CACHE_MAX_SIZE: - { - size_t max_size; - PyObject *py_max_size; - - py_max_size = PyTuple_GetItem(args, 1); - if (!py_max_size) - return NULL; - - if (!PyLong_Check(py_max_size)) - return Error_type_error( - "max_size should be an integer, got %.200s", py_max_size); - - max_size = PyLong_AsSize_t(py_max_size); - error = git_libgit2_opts(option, max_size); - if (error < 0) - return Error_set(error); - - Py_RETURN_NONE; - } - - case GIT_OPT_GET_CACHED_MEMORY: - { - size_t current; - size_t allowed; - PyObject* tup = PyTuple_New(2); - - error = git_libgit2_opts(option, ¤t, &allowed); - if (error < 0) - return Error_set(error); - - PyTuple_SetItem(tup, 0, PyLong_FromLong(current)); - PyTuple_SetItem(tup, 1, PyLong_FromLong(allowed)); - - return tup; - } - - case GIT_OPT_GET_TEMPLATE_PATH: - case GIT_OPT_SET_TEMPLATE_PATH: - { - Py_INCREF(Py_NotImplemented); - return Py_NotImplemented; - } - - case GIT_OPT_SET_SSL_CERT_LOCATIONS: - { - PyObject *py_file, *py_dir; - char *file_path=NULL, *dir_path=NULL; - int err; - - py_file = PyTuple_GetItem(args, 1); - if (!py_file) - return NULL; - py_dir = PyTuple_GetItem(args, 2); - if (!py_dir) - return NULL; - - /* py_file and py_dir are only valid if they are strings */ - PyObject *tvalue_file = NULL; - if (PyUnicode_Check(py_file) || PyBytes_Check(py_file)) - file_path = pgit_borrow_fsdefault(py_file, &tvalue_file); - - PyObject *tvalue_dir = NULL; - if (PyUnicode_Check(py_dir) || PyBytes_Check(py_dir)) - dir_path = pgit_borrow_fsdefault(py_dir, &tvalue_dir); - - err = git_libgit2_opts(option, file_path, dir_path); - Py_XDECREF(tvalue_file); - Py_XDECREF(tvalue_dir); - - if (err) - return Error_set(err); - - Py_RETURN_NONE; - } - - case GIT_OPT_SET_USER_AGENT: - { - Py_INCREF(Py_NotImplemented); - return Py_NotImplemented; - } - - // int enabled - case GIT_OPT_ENABLE_CACHING: - case GIT_OPT_ENABLE_STRICT_OBJECT_CREATION: - case GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION: - case GIT_OPT_ENABLE_OFS_DELTA: - case GIT_OPT_ENABLE_FSYNC_GITDIR: - case GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION: - case GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY: - case GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS: - case GIT_OPT_SET_OWNER_VALIDATION: - { - PyObject *py_value = PyTuple_GetItem(args, 1); - if (!py_value) - return NULL; - - if (!PyLong_Check(py_value)) - return Error_type_error("expected integer, got %.200s", py_value); - - int value = PyLong_AsSize_t(py_value); - error = git_libgit2_opts(option, value); - if (error < 0) - return Error_set(error); - - Py_RETURN_NONE; - } - - // int enabled getter - case GIT_OPT_GET_OWNER_VALIDATION: - { - int enabled; - - error = git_libgit2_opts(option, &enabled); - if (error < 0) - return Error_set(error); - - return PyLong_FromLong(enabled); - } - - // Not implemented - case GIT_OPT_SET_SSL_CIPHERS: - case GIT_OPT_GET_USER_AGENT: - case GIT_OPT_GET_WINDOWS_SHAREMODE: - case GIT_OPT_SET_WINDOWS_SHAREMODE: - case GIT_OPT_SET_ALLOCATOR: - case GIT_OPT_GET_PACK_MAX_OBJECTS: - case GIT_OPT_SET_PACK_MAX_OBJECTS: - { - Py_INCREF(Py_NotImplemented); - return Py_NotImplemented; - } - - } - - PyErr_SetString(PyExc_ValueError, "unknown/unsupported option value"); - return NULL; -} diff --git a/src/options.h b/src/options.h deleted file mode 100644 index f8b9a08e..00000000 --- a/src/options.h +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2010-2025 The pygit2 contributors - * - * This file is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License, version 2, - * as published by the Free Software Foundation. - * - * In addition to the permissions in the GNU General Public License, - * the authors give you unlimited permission to link the compiled - * version of this file into combinations with other programs, - * and to distribute those combinations without any restriction - * coming from the use of this file. (The General Public License - * restrictions do apply in other respects; for example, they cover - * modification of the file, and distribution when not linked into - * a combined executable.) - * - * This file is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; see the file COPYING. If not, write to - * the Free Software Foundation, 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. - */ - -#ifndef INCLUDE_pygit2_blame_h -#define INCLUDE_pygit2_blame_h - -#define PY_SSIZE_T_CLEAN -#include -#include -#include "types.h" - -PyDoc_STRVAR(option__doc__, - "option(option, ...)\n" - "\n" - "Get or set a libgit2 option.\n" - "\n" - "Parameters:\n" - "\n" - "GIT_OPT_GET_SEARCH_PATH, level\n" - " Get the config search path for the given level.\n" - "\n" - "GIT_OPT_SET_SEARCH_PATH, level, path\n" - " Set the config search path for the given level.\n" - "\n" - "GIT_OPT_GET_MWINDOW_SIZE\n" - " Get the maximum mmap window size.\n" - "\n" - "GIT_OPT_SET_MWINDOW_SIZE, size\n" - " Set the maximum mmap window size.\n" - "\n" - "GIT_OPT_GET_MWINDOW_FILE_LIMIT\n" - " Get the maximum number of files that will be mapped at any time by the library.\n" - "\n" - "GIT_OPT_SET_MWINDOW_FILE_LIMIT, size\n" - " Set the maximum number of files that can be mapped at any time by the library. The default (0) is unlimited.\n" - "\n" - "GIT_OPT_GET_OWNER_VALIDATION\n" - " Gets the owner validation setting for repository directories.\n" - "\n" - "GIT_OPT_SET_OWNER_VALIDATION, enabled\n" - " Set that repository directories should be owned by the current user.\n" - " The default is to validate ownership.\n" - ); - - -PyObject *option(PyObject *self, PyObject *args); - -#endif diff --git a/src/pygit2.c b/src/pygit2.c index ca473968..c2bac5e5 100644 --- a/src/pygit2.c +++ b/src/pygit2.c @@ -35,7 +35,6 @@ #include "utils.h" #include "repository.h" #include "oid.h" -#include "options.h" #include "filter.h" PyObject *GitError; @@ -439,7 +438,6 @@ PyMethodDef module_methods[] = { {"hash", hash, METH_VARARGS, hash__doc__}, {"hashfile", hashfile, METH_VARARGS, hashfile__doc__}, {"init_file_backend", init_file_backend, METH_VARARGS, init_file_backend__doc__}, - {"option", option, METH_VARARGS, option__doc__}, {"reference_is_valid_name", reference_is_valid_name, METH_O, reference_is_valid_name__doc__}, {"tree_entry_cmp", tree_entry_cmp, METH_VARARGS, tree_entry_cmp__doc__}, {"filter_register", (PyCFunction)filter_register, METH_VARARGS | METH_KEYWORDS, filter_register__doc__}, @@ -473,39 +471,6 @@ PyInit__pygit2(void) ADD_CONSTANT_INT(m, LIBGIT2_VER_REVISION) ADD_CONSTANT_STR(m, LIBGIT2_VERSION) - /* libgit2 options */ - ADD_CONSTANT_INT(m, GIT_OPT_GET_MWINDOW_SIZE); - ADD_CONSTANT_INT(m, GIT_OPT_SET_MWINDOW_SIZE); - ADD_CONSTANT_INT(m, GIT_OPT_GET_MWINDOW_MAPPED_LIMIT); - ADD_CONSTANT_INT(m, GIT_OPT_SET_MWINDOW_MAPPED_LIMIT); - ADD_CONSTANT_INT(m, GIT_OPT_GET_SEARCH_PATH); - ADD_CONSTANT_INT(m, GIT_OPT_SET_SEARCH_PATH); - ADD_CONSTANT_INT(m, GIT_OPT_SET_CACHE_OBJECT_LIMIT); - ADD_CONSTANT_INT(m, GIT_OPT_SET_CACHE_MAX_SIZE); - ADD_CONSTANT_INT(m, GIT_OPT_ENABLE_CACHING); - ADD_CONSTANT_INT(m, GIT_OPT_GET_CACHED_MEMORY); - ADD_CONSTANT_INT(m, GIT_OPT_GET_TEMPLATE_PATH); - ADD_CONSTANT_INT(m, GIT_OPT_SET_TEMPLATE_PATH); - ADD_CONSTANT_INT(m, GIT_OPT_SET_SSL_CERT_LOCATIONS); - ADD_CONSTANT_INT(m, GIT_OPT_SET_USER_AGENT); - ADD_CONSTANT_INT(m, GIT_OPT_ENABLE_STRICT_OBJECT_CREATION); - ADD_CONSTANT_INT(m, GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION); - ADD_CONSTANT_INT(m, GIT_OPT_SET_SSL_CIPHERS); - ADD_CONSTANT_INT(m, GIT_OPT_GET_USER_AGENT); - ADD_CONSTANT_INT(m, GIT_OPT_ENABLE_OFS_DELTA); - ADD_CONSTANT_INT(m, GIT_OPT_ENABLE_FSYNC_GITDIR); - ADD_CONSTANT_INT(m, GIT_OPT_GET_WINDOWS_SHAREMODE); - ADD_CONSTANT_INT(m, GIT_OPT_SET_WINDOWS_SHAREMODE); - ADD_CONSTANT_INT(m, GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION); - ADD_CONSTANT_INT(m, GIT_OPT_SET_ALLOCATOR); - ADD_CONSTANT_INT(m, GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY); - ADD_CONSTANT_INT(m, GIT_OPT_GET_PACK_MAX_OBJECTS); - ADD_CONSTANT_INT(m, GIT_OPT_SET_PACK_MAX_OBJECTS); - ADD_CONSTANT_INT(m, GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS); - ADD_CONSTANT_INT(m, GIT_OPT_GET_OWNER_VALIDATION); - ADD_CONSTANT_INT(m, GIT_OPT_SET_OWNER_VALIDATION); - ADD_CONSTANT_INT(m, GIT_OPT_GET_MWINDOW_FILE_LIMIT); - ADD_CONSTANT_INT(m, GIT_OPT_SET_MWINDOW_FILE_LIMIT); /* Exceptions */ ADD_EXC(m, GitError, NULL); diff --git a/test/conftest.py b/test/conftest.py index 6052346f..6b8a2737 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -24,7 +24,7 @@ def global_git_config() -> None: # Fix tests running in AppVeyor if platform.system() == 'Windows': - pygit2.option(pygit2.enums.Option.SET_OWNER_VALIDATION, 0) + pygit2.option(pygit2.enums.Option.SET_OWNER_VALIDATION, False) @pytest.fixture diff --git a/test/test_options.py b/test/test_options.py index 790f459f..6a1c1ac0 100644 --- a/test/test_options.py +++ b/test/test_options.py @@ -23,17 +23,21 @@ # the Free Software Foundation, 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. +import sys + +import pytest + import pygit2 from pygit2 import option from pygit2.enums import ConfigLevel, ObjectType, Option def __option(getter: Option, setter: Option, value: object) -> None: - old_value = option(getter) # type: ignore[call-overload] - option(setter, value) # type: ignore[call-overload] - assert value == option(getter) # type: ignore[call-overload] + old_value = option(getter) + option(setter, value) + assert value == option(getter) # Reset to avoid side effects in later tests - option(setter, old_value) # type: ignore[call-overload] + option(setter, old_value) def __proxy(name: str, value: object) -> None: @@ -130,3 +134,129 @@ def test_search_path_proxy() -> None: def test_owner_validation() -> None: __option(Option.GET_OWNER_VALIDATION, Option.SET_OWNER_VALIDATION, 0) + + +def test_template_path() -> None: + original_path = option(Option.GET_TEMPLATE_PATH) + + test_path = '/tmp/test_templates' + option(Option.SET_TEMPLATE_PATH, test_path) + assert option(Option.GET_TEMPLATE_PATH) == test_path + + if original_path: + option(Option.SET_TEMPLATE_PATH, original_path) + else: + option(Option.SET_TEMPLATE_PATH, None) + + +def test_user_agent() -> None: + original_agent = option(Option.GET_USER_AGENT) + + test_agent = 'test-agent/1.0' + option(Option.SET_USER_AGENT, test_agent) + assert option(Option.GET_USER_AGENT) == test_agent + + if original_agent: + option(Option.SET_USER_AGENT, original_agent) + + +def test_pack_max_objects() -> None: + __option(Option.GET_PACK_MAX_OBJECTS, Option.SET_PACK_MAX_OBJECTS, 100000) + + +@pytest.mark.skipif(sys.platform != 'win32', reason='Windows-specific feature') +def test_windows_sharemode() -> None: + __option(Option.GET_WINDOWS_SHAREMODE, Option.SET_WINDOWS_SHAREMODE, 1) + + +def test_ssl_ciphers() -> None: + # Setting SSL ciphers (no getter available) + try: + option(Option.SET_SSL_CIPHERS, 'DEFAULT') + except pygit2.GitError as e: + if "TLS backend doesn't support custom ciphers" in str(e): + pytest.skip(str(e)) + raise + + +def test_enable_http_expect_continue() -> None: + option(Option.ENABLE_HTTP_EXPECT_CONTINUE, True) + option(Option.ENABLE_HTTP_EXPECT_CONTINUE, False) + + +def test_odb_priorities() -> None: + option(Option.SET_ODB_PACKED_PRIORITY, 1) + option(Option.SET_ODB_LOOSE_PRIORITY, 2) + + +def test_extensions() -> None: + original_extensions = option(Option.GET_EXTENSIONS) + assert isinstance(original_extensions, list) + + test_extensions = ['objectformat', 'worktreeconfig'] + option(Option.SET_EXTENSIONS, test_extensions, len(test_extensions)) + + new_extensions = option(Option.GET_EXTENSIONS) + assert isinstance(new_extensions, list) + + # Note: libgit2 may add its own built-in extensions and sort them + for ext in test_extensions: + assert ext in new_extensions, f"Extension '{ext}' not found in {new_extensions}" + + option(Option.SET_EXTENSIONS, [], 0) + empty_extensions = option(Option.GET_EXTENSIONS) + assert isinstance(empty_extensions, list) + + custom_extensions = ['myextension', 'objectformat'] + option(Option.SET_EXTENSIONS, custom_extensions, len(custom_extensions)) + custom_result = option(Option.GET_EXTENSIONS) + assert 'myextension' in custom_result + assert 'objectformat' in custom_result + + if original_extensions: + option(Option.SET_EXTENSIONS, original_extensions, len(original_extensions)) + else: + option(Option.SET_EXTENSIONS, [], 0) + + final_extensions = option(Option.GET_EXTENSIONS) + assert set(final_extensions) == set(original_extensions) + + +def test_homedir() -> None: + original_homedir = option(Option.GET_HOMEDIR) + + test_homedir = '/tmp/test_home' + option(Option.SET_HOMEDIR, test_homedir) + assert option(Option.GET_HOMEDIR) == test_homedir + + if original_homedir: + option(Option.SET_HOMEDIR, original_homedir) + else: + option(Option.SET_HOMEDIR, None) + + +def test_server_timeouts() -> None: + original_connect = option(Option.GET_SERVER_CONNECT_TIMEOUT) + option(Option.SET_SERVER_CONNECT_TIMEOUT, 5000) + assert option(Option.GET_SERVER_CONNECT_TIMEOUT) == 5000 + option(Option.SET_SERVER_CONNECT_TIMEOUT, original_connect) + + original_timeout = option(Option.GET_SERVER_TIMEOUT) + option(Option.SET_SERVER_TIMEOUT, 10000) + assert option(Option.GET_SERVER_TIMEOUT) == 10000 + option(Option.SET_SERVER_TIMEOUT, original_timeout) + + +def test_user_agent_product() -> None: + original_product = option(Option.GET_USER_AGENT_PRODUCT) + + test_product = 'test-product' + option(Option.SET_USER_AGENT_PRODUCT, test_product) + assert option(Option.GET_USER_AGENT_PRODUCT) == test_product + + if original_product: + option(Option.SET_USER_AGENT_PRODUCT, original_product) + + +def test_mwindow_file_limit() -> None: + __option(Option.GET_MWINDOW_FILE_LIMIT, Option.SET_MWINDOW_FILE_LIMIT, 100) diff --git a/test/test_settings.py b/test/test_settings.py new file mode 100644 index 00000000..5c521101 --- /dev/null +++ b/test/test_settings.py @@ -0,0 +1,284 @@ +# Copyright 2010-2025 The pygit2 contributors +# +# This file is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2, +# as published by the Free Software Foundation. +# +# In addition to the permissions in the GNU General Public License, +# the authors give you unlimited permission to link the compiled +# version of this file into combinations with other programs, +# and to distribute those combinations without any restriction +# coming from the use of this file. (The General Public License +# restrictions do apply in other respects; for example, they cover +# modification of the file, and distribution when not linked into +# a combined executable.) +# +# This file is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; see the file COPYING. If not, write to +# the Free Software Foundation, 51 Franklin Street, Fifth Floor, +# Boston, MA 02110-1301, USA. + +"""Test the Settings class.""" + +import sys + +import pytest + +import pygit2 +from pygit2.enums import ConfigLevel, ObjectType + + +def test_mwindow_size() -> None: + original = pygit2.settings.mwindow_size + try: + test_size = 200 * 1024 + pygit2.settings.mwindow_size = test_size + assert pygit2.settings.mwindow_size == test_size + finally: + pygit2.settings.mwindow_size = original + + +def test_mwindow_mapped_limit() -> None: + original = pygit2.settings.mwindow_mapped_limit + try: + test_limit = 300 * 1024 + pygit2.settings.mwindow_mapped_limit = test_limit + assert pygit2.settings.mwindow_mapped_limit == test_limit + finally: + pygit2.settings.mwindow_mapped_limit = original + + +def test_cached_memory() -> None: + cached = pygit2.settings.cached_memory + assert isinstance(cached, tuple) + assert len(cached) == 2 + assert isinstance(cached[0], int) + assert isinstance(cached[1], int) + + +def test_enable_caching() -> None: + assert hasattr(pygit2.settings, 'enable_caching') + assert callable(pygit2.settings.enable_caching) + + # Should not raise exceptions + pygit2.settings.enable_caching(False) + pygit2.settings.enable_caching(True) + + +def test_disable_pack_keep_file_checks() -> None: + assert hasattr(pygit2.settings, 'disable_pack_keep_file_checks') + assert callable(pygit2.settings.disable_pack_keep_file_checks) + + # Should not raise exceptions + pygit2.settings.disable_pack_keep_file_checks(False) + pygit2.settings.disable_pack_keep_file_checks(True) + pygit2.settings.disable_pack_keep_file_checks(False) + + +def test_cache_max_size() -> None: + original_max_size = pygit2.settings.cached_memory[1] + try: + pygit2.settings.cache_max_size(128 * 1024**2) + assert pygit2.settings.cached_memory[1] == 128 * 1024**2 + pygit2.settings.cache_max_size(256 * 1024**2) + assert pygit2.settings.cached_memory[1] == 256 * 1024**2 + finally: + pygit2.settings.cache_max_size(original_max_size) + + +@pytest.mark.parametrize( + 'object_type,test_size,default_size', + [ + (ObjectType.BLOB, 2 * 1024, 0), + (ObjectType.COMMIT, 8 * 1024, 4096), + (ObjectType.TREE, 8 * 1024, 4096), + (ObjectType.TAG, 8 * 1024, 4096), + (ObjectType.BLOB, 0, 0), + ], +) +def test_cache_object_limit( + object_type: ObjectType, test_size: int, default_size: int +) -> None: + assert callable(pygit2.settings.cache_object_limit) + + pygit2.settings.cache_object_limit(object_type, test_size) + pygit2.settings.cache_object_limit(object_type, default_size) + + +@pytest.mark.parametrize( + 'level,test_path', + [ + (ConfigLevel.GLOBAL, '/tmp/test_global'), + (ConfigLevel.XDG, '/tmp/test_xdg'), + (ConfigLevel.SYSTEM, '/tmp/test_system'), + ], +) +def test_search_path(level: ConfigLevel, test_path: str) -> None: + original = pygit2.settings.search_path[level] + try: + pygit2.settings.search_path[level] = test_path + assert pygit2.settings.search_path[level] == test_path + finally: + pygit2.settings.search_path[level] = original + + +def test_template_path() -> None: + original = pygit2.settings.template_path + try: + pygit2.settings.template_path = '/tmp/test_templates' + assert pygit2.settings.template_path == '/tmp/test_templates' + finally: + if original: + pygit2.settings.template_path = original + + +def test_user_agent() -> None: + original = pygit2.settings.user_agent + try: + pygit2.settings.user_agent = 'test-agent/1.0' + assert pygit2.settings.user_agent == 'test-agent/1.0' + finally: + if original: + pygit2.settings.user_agent = original + + +def test_user_agent_product() -> None: + original = pygit2.settings.user_agent_product + try: + pygit2.settings.user_agent_product = 'test-product' + assert pygit2.settings.user_agent_product == 'test-product' + finally: + if original: + pygit2.settings.user_agent_product = original + + +def test_pack_max_objects() -> None: + original = pygit2.settings.pack_max_objects + try: + pygit2.settings.pack_max_objects = 100000 + assert pygit2.settings.pack_max_objects == 100000 + finally: + pygit2.settings.pack_max_objects = original + + +def test_owner_validation() -> None: + original = pygit2.settings.owner_validation + try: + pygit2.settings.owner_validation = False + assert pygit2.settings.owner_validation == False # noqa: E712 + pygit2.settings.owner_validation = True + assert pygit2.settings.owner_validation == True # noqa: E712 + finally: + pygit2.settings.owner_validation = original + + +def test_mwindow_file_limit() -> None: + original = pygit2.settings.mwindow_file_limit + try: + pygit2.settings.mwindow_file_limit = 100 + assert pygit2.settings.mwindow_file_limit == 100 + finally: + pygit2.settings.mwindow_file_limit = original + + +def test_homedir() -> None: + original = pygit2.settings.homedir + try: + pygit2.settings.homedir = '/tmp/test_home' + assert pygit2.settings.homedir == '/tmp/test_home' + finally: + if original: + pygit2.settings.homedir = original + + +def test_server_timeouts() -> None: + original_connect = pygit2.settings.server_connect_timeout + original_timeout = pygit2.settings.server_timeout + try: + pygit2.settings.server_connect_timeout = 5000 + assert pygit2.settings.server_connect_timeout == 5000 + + pygit2.settings.server_timeout = 10000 + assert pygit2.settings.server_timeout == 10000 + finally: + pygit2.settings.server_connect_timeout = original_connect + pygit2.settings.server_timeout = original_timeout + + +def test_extensions() -> None: + original = pygit2.settings.extensions + try: + test_extensions = ['objectformat', 'worktreeconfig'] + pygit2.settings.set_extensions(test_extensions) + + new_extensions = pygit2.settings.extensions + for ext in test_extensions: + assert ext in new_extensions + finally: + if original: + pygit2.settings.set_extensions(original) + + +@pytest.mark.parametrize( + 'method_name,default_value', + [ + ('enable_strict_object_creation', True), + ('enable_strict_symbolic_ref_creation', True), + ('enable_ofs_delta', True), + ('enable_fsync_gitdir', False), + ('enable_strict_hash_verification', True), + ('enable_unsaved_index_safety', False), + ('enable_http_expect_continue', False), + ], +) +def test_enable_methods(method_name: str, default_value: bool) -> None: + assert hasattr(pygit2.settings, method_name) + method = getattr(pygit2.settings, method_name) + assert callable(method) + + method(True) + method(False) + method(default_value) + + +@pytest.mark.parametrize('priority', [1, 5, 10, 0, -1, -2]) +def test_odb_priorities(priority: int) -> None: + """Test setting ODB priorities""" + assert hasattr(pygit2.settings, 'set_odb_packed_priority') + assert hasattr(pygit2.settings, 'set_odb_loose_priority') + assert callable(pygit2.settings.set_odb_packed_priority) + assert callable(pygit2.settings.set_odb_loose_priority) + + pygit2.settings.set_odb_packed_priority(priority) + pygit2.settings.set_odb_loose_priority(priority) + + pygit2.settings.set_odb_packed_priority(1) + pygit2.settings.set_odb_loose_priority(2) + + +def test_ssl_ciphers() -> None: + assert callable(pygit2.settings.set_ssl_ciphers) + + try: + pygit2.settings.set_ssl_ciphers('DEFAULT') + except pygit2.GitError as e: + if "TLS backend doesn't support" in str(e): + pytest.skip(str(e)) + raise + + +@pytest.mark.skipif(sys.platform != 'win32', reason='Windows-specific feature') +def test_windows_sharemode() -> None: + original = pygit2.settings.windows_sharemode + try: + pygit2.settings.windows_sharemode = 1 + assert pygit2.settings.windows_sharemode == 1 + pygit2.settings.windows_sharemode = 2 + assert pygit2.settings.windows_sharemode == 2 + finally: + pygit2.settings.windows_sharemode = original