Skip to content

cmd(git): More commands #465

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
feat: enhance Git.init with ref-format and improved validation
- Add ref-format parameter support for git init
- Add make_parents parameter to control directory creation
- Improve type hints and validation for template and shared parameters
- Add comprehensive tests for all shared values and octal permissions
- Add validation for octal number range in shared parameter
  • Loading branch information
tony committed Feb 25, 2025
commit 908b72a84f64b5198ae4f8d7734ea3990e1e56f9
54 changes: 46 additions & 8 deletions src/libvcs/cmd/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -1031,26 +1031,29 @@ def pull(
def init(
self,
*,
template: str | None = None,
template: str | pathlib.Path | None = None,
separate_git_dir: StrOrBytesPath | None = None,
object_format: t.Literal["sha1", "sha256"] | None = None,
branch: str | None = None,
initial_branch: str | None = None,
shared: bool
| Literal[false, true, umask, group, all, world, everybody]
| str
| t.Literal["false", "true", "umask", "group", "all", "world", "everybody"]
| str # Octal number string (e.g., "0660")
| None = None,
quiet: bool | None = None,
bare: bool | None = None,
ref_format: t.Literal["files", "reftable"] | None = None,
default: bool | None = None,
# libvcs special behavior
check_returncode: bool | None = None,
make_parents: bool = True,
**kwargs: t.Any,
) -> str:
"""Create empty repo. Wraps `git init <https://git-scm.com/docs/git-init>`_.

Parameters
----------
template : str, optional
template : str | pathlib.Path, optional
Directory from which templates will be used. The template directory
contains files and directories that will be copied to the $GIT_DIR
after it is created. The template directory will be one of the
Expand Down Expand Up @@ -1084,17 +1087,27 @@ def init(
- umask: Use permissions specified by umask
- group: Make the repository group-writable
- all, world, everybody: Same as world, make repo readable by all users
- An octal number: Explicit mode specification (e.g., "0660")
- An octal number string: Explicit mode specification (e.g., "0660")
quiet : bool, optional
Only print error and warning messages; all other output will be
suppressed. Useful for scripting.
bare : bool, optional
Create a bare repository. If GIT_DIR environment is not set, it is set
to the current working directory. Bare repositories have no working
tree and are typically used as central repositories.
ref_format : "files" | "reftable", optional
Specify the reference storage format. Requires git version >= 2.37.0.
- files: Classic format with packed-refs and loose refs (default)
- reftable: New format that is more efficient for large repositories
default : bool, optional
Use default permissions for directories and files. This is the same as
running git init without any options.
check_returncode : bool, optional
If True, check the return code of the git command and raise a
CalledProcessError if it is non-zero.
make_parents : bool, default: True
If True, create the target directory if it doesn't exist. If False,
raise an error if the directory doesn't exist.

Returns
-------
Expand All @@ -1105,6 +1118,10 @@ def init(
------
CalledProcessError
If the git command fails and check_returncode is True.
ValueError
If invalid parameters are provided.
FileNotFoundError
If make_parents is False and the target directory doesn't exist.

Examples
--------
Expand Down Expand Up @@ -1146,6 +1163,14 @@ def init(
>>> git.init(shared='group')
'Initialized empty shared Git repository in ...'

Create with octal permissions:

>>> shared_repo = tmp_path / 'shared_octal_example'
>>> shared_repo.mkdir()
>>> git = Git(path=shared_repo)
>>> git.init(shared='0660')
'Initialized empty shared Git repository in ...'

Create with a template directory:

>>> template_repo = tmp_path / 'template_example'
Expand Down Expand Up @@ -1218,18 +1243,31 @@ def init(
shared_str.isdigit()
and len(shared_str) <= 4
and all(c in string.octdigits for c in shared_str)
and int(shared_str, 8) <= 0o777 # Validate octal range
)
):
msg = (
f"Invalid shared value. Must be one of {valid_shared_values} "
"or an octal number"
"or a valid octal number between 0000 and 0777"
)
raise ValueError(msg)
local_flags.append(f"--shared={shared}")

if quiet is True:
local_flags.append("--quiet")
if bare is True:
local_flags.append("--bare")
if ref_format is not None:
local_flags.append(f"--ref-format={ref_format}")
if default is True:
local_flags.append("--default")

# libvcs special behavior
if make_parents and not self.path.exists():
self.path.mkdir(parents=True)
elif not self.path.exists():
msg = f"Directory does not exist: {self.path}"
raise FileNotFoundError(msg)

return self.run(
["init", *local_flags, "--", *required_flags],
Expand Down Expand Up @@ -2863,7 +2901,7 @@ def set_url(
)


GitRemoteManagerLiteral = Literal[
GitRemoteManagerLiteral = t.Literal[
"--verbose",
"add",
"rename",
Expand Down Expand Up @@ -2933,7 +2971,7 @@ def run(
# Pass-through to run()
log_in_real_time: bool = False,
check_returncode: bool | None = None,
**kwargs: Any,
**kwargs: t.Any,
) -> str:
"""Run a command against a git repository's remotes.

Expand Down
82 changes: 82 additions & 0 deletions tests/cmd/test_git.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,85 @@ def test_git_init_validation_errors(tmp_path: pathlib.Path) -> None:
# Test invalid octal number for shared
with pytest.raises(ValueError, match="Invalid shared value"):
repo.init(shared="8888") # Invalid octal number

# Test octal number out of range
with pytest.raises(ValueError, match="Invalid shared value"):
repo.init(shared="1000") # Octal number > 0777

# Test non-existent directory with make_parents=False
non_existent = tmp_path / "non_existent"
with pytest.raises(FileNotFoundError, match="Directory does not exist"):
repo = git.Git(path=non_existent)
repo.init(make_parents=False)


def test_git_init_shared_octal(tmp_path: pathlib.Path) -> None:
"""Test git init with shared octal permissions."""
repo = git.Git(path=tmp_path)

# Test valid octal numbers
for octal in ["0660", "0644", "0755"]:
repo_dir = tmp_path / f"shared_{octal}"
repo_dir.mkdir()
repo = git.Git(path=repo_dir)
result = repo.init(shared=octal)
assert "Initialized empty shared Git repository" in result


def test_git_init_shared_values(tmp_path: pathlib.Path) -> None:
"""Test git init with all valid shared values."""
valid_values = ["false", "true", "umask", "group", "all", "world", "everybody"]

for value in valid_values:
repo_dir = tmp_path / f"shared_{value}"
repo_dir.mkdir()
repo = git.Git(path=repo_dir)
result = repo.init(shared=value)
# The output message varies between git versions and shared values
assert any(
msg in result
for msg in [
"Initialized empty Git repository",
"Initialized empty shared Git repository",
]
)


def test_git_init_ref_format(tmp_path: pathlib.Path) -> None:
"""Test git init with different ref formats."""
repo = git.Git(path=tmp_path)

# Test with files format (default)
result = repo.init()
assert "Initialized empty Git repository" in result

# Test with reftable format (requires git >= 2.37.0)
repo_dir = tmp_path / "reftable"
repo_dir.mkdir()
repo = git.Git(path=repo_dir)
try:
result = repo.init(ref_format="reftable")
assert "Initialized empty Git repository" in result
except Exception as e:
if "unknown option" in str(e):
pytest.skip("ref-format option not supported in this git version")
raise


def test_git_init_make_parents(tmp_path: pathlib.Path) -> None:
"""Test git init with make_parents flag."""
deep_path = tmp_path / "a" / "b" / "c"

# Test with make_parents=True (default)
repo = git.Git(path=deep_path)
result = repo.init()
assert "Initialized empty Git repository" in result
assert deep_path.exists()
assert (deep_path / ".git").is_dir()

# Test with make_parents=False on existing directory
existing_path = tmp_path / "existing"
existing_path.mkdir()
repo = git.Git(path=existing_path)
result = repo.init(make_parents=False)
assert "Initialized empty Git repository" in result
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy