Skip to content

Commit c8e63da

Browse files
committed
feat(git): enhance git init support with all options and tests
- Add support for all git-init options (template, separate_git_dir, object_format, etc.) - Add comprehensive tests for each option - Fix path handling for separate_git_dir - Fix string formatting for bytes paths - Update docstrings with examples for all options
1 parent 46409a6 commit c8e63da

File tree

2 files changed

+184
-31
lines changed

2 files changed

+184
-31
lines changed

src/libvcs/cmd/git.py

+71-31
Original file line numberDiff line numberDiff line change
@@ -1031,7 +1031,7 @@ def init(
10311031
object_format: t.Literal["sha1", "sha256"] | None = None,
10321032
branch: str | None = None,
10331033
initial_branch: str | None = None,
1034-
shared: bool | None = None,
1034+
shared: bool | str | None = None,
10351035
quiet: bool | None = None,
10361036
bare: bool | None = None,
10371037
# libvcs special behavior
@@ -1042,60 +1042,100 @@ def init(
10421042
10431043
Parameters
10441044
----------
1045-
quiet : bool
1046-
``--quiet``
1047-
bare : bool
1048-
``--bare``
1049-
object_format :
1050-
Hash algorithm used for objects. SHA-256 is still experimental as of git
1051-
2.36.0.
1045+
template : str, optional
1046+
Directory from which templates will be used. The template directory
1047+
contains files and directories that will be copied to the $GIT_DIR
1048+
after it is created.
1049+
separate_git_dir : :attr:`libvcs._internal.types.StrOrBytesPath`, optional
1050+
Instead of placing the git repository in <directory>/.git/, place it in
1051+
the specified path.
1052+
object_format : "sha1" | "sha256", optional
1053+
Specify the hash algorithm to use. The default is sha1. Note that
1054+
sha256 is still experimental in git.
1055+
branch : str, optional
1056+
Use the specified name for the initial branch. If not specified, fall
1057+
back to the default name (currently "master").
1058+
initial_branch : str, optional
1059+
Alias for branch parameter. Specify the name for the initial branch.
1060+
shared : bool | str, optional
1061+
Specify that the git repository is to be shared amongst several users.
1062+
Can be 'false', 'true', 'umask', 'group', 'all', 'world',
1063+
'everybody', or an octal number.
1064+
quiet : bool, optional
1065+
Only print error and warning messages; all other output will be
1066+
suppressed.
1067+
bare : bool, optional
1068+
Create a bare repository. If GIT_DIR environment is not set, it is set
1069+
to the current working directory.
10521070
10531071
Examples
10541072
--------
1055-
>>> new_repo = tmp_path / 'example'
1056-
>>> new_repo.mkdir()
1057-
>>> git = Git(path=new_repo)
1073+
>>> git = Git(path=tmp_path)
10581074
>>> git.init()
10591075
'Initialized empty Git repository in ...'
1060-
>>> pathlib.Path(new_repo / 'test').write_text('foo', 'utf-8')
1061-
3
1062-
>>> git.run(['add', '.'])
1063-
''
10641076
1065-
Bare:
1077+
Create with a specific initial branch name:
10661078
1067-
>>> new_repo = tmp_path / 'example1'
1079+
>>> new_repo = tmp_path / 'branch_example'
10681080
>>> new_repo.mkdir()
10691081
>>> git = Git(path=new_repo)
1082+
>>> git.init(branch='main')
1083+
'Initialized empty Git repository in ...'
1084+
1085+
Create a bare repository:
1086+
1087+
>>> bare_repo = tmp_path / 'bare_example'
1088+
>>> bare_repo.mkdir()
1089+
>>> git = Git(path=bare_repo)
10701090
>>> git.init(bare=True)
10711091
'Initialized empty Git repository in ...'
1072-
>>> pathlib.Path(new_repo / 'HEAD').exists()
1073-
True
10741092
1075-
Existing repo:
1093+
Create with a separate git directory:
10761094
1077-
>>> git = Git(path=new_repo)
1078-
>>> git = Git(path=example_git_repo.path)
1079-
>>> git_remote_repo = create_git_remote_repo()
1080-
>>> git.init()
1081-
'Reinitialized existing Git repository in ...'
1095+
>>> repo_path = tmp_path / 'repo'
1096+
>>> git_dir = tmp_path / 'git_dir'
1097+
>>> repo_path.mkdir()
1098+
>>> git_dir.mkdir()
1099+
>>> git = Git(path=repo_path)
1100+
>>> git.init(separate_git_dir=str(git_dir.absolute()))
1101+
'Initialized empty Git repository in ...'
1102+
1103+
Create with shared permissions:
10821104
1105+
>>> shared_repo = tmp_path / 'shared_example'
1106+
>>> shared_repo.mkdir()
1107+
>>> git = Git(path=shared_repo)
1108+
>>> git.init(shared='group')
1109+
'Initialized empty shared Git repository in ...'
1110+
1111+
Create with a template directory:
1112+
1113+
>>> template_repo = tmp_path / 'template_example'
1114+
>>> template_repo.mkdir()
1115+
>>> git = Git(path=template_repo)
1116+
>>> git.init(template=str(tmp_path))
1117+
'Initialized empty Git repository in ...'
10831118
"""
1084-
required_flags: list[str] = [str(self.path)]
10851119
local_flags: list[str] = []
1120+
required_flags: list[str] = [str(self.path)]
10861121

10871122
if template is not None:
10881123
local_flags.append(f"--template={template}")
10891124
if separate_git_dir is not None:
1090-
local_flags.append(f"--separate-git-dir={separate_git_dir!r}")
1125+
if isinstance(separate_git_dir, pathlib.Path):
1126+
separate_git_dir = str(separate_git_dir.absolute())
1127+
local_flags.append(f"--separate-git-dir={separate_git_dir!s}")
10911128
if object_format is not None:
10921129
local_flags.append(f"--object-format={object_format}")
10931130
if branch is not None:
1094-
local_flags.extend(["--branch", branch])
1095-
if initial_branch is not None:
1131+
local_flags.extend(["--initial-branch", branch])
1132+
elif initial_branch is not None:
10961133
local_flags.extend(["--initial-branch", initial_branch])
1097-
if shared is True:
1098-
local_flags.append("--shared")
1134+
if shared is not None:
1135+
if isinstance(shared, bool):
1136+
local_flags.append("--shared")
1137+
else:
1138+
local_flags.append(f"--shared={shared}")
10991139
if quiet is True:
11001140
local_flags.append("--quiet")
11011141
if bare is True:

tests/cmd/test_git.py

+113
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,116 @@ def test_git_constructor(
1919
repo = git.Git(path=path_type(tmp_path))
2020

2121
assert repo.path == tmp_path
22+
23+
24+
def test_git_init_basic(tmp_path: pathlib.Path) -> None:
25+
"""Test basic git init functionality."""
26+
repo = git.Git(path=tmp_path)
27+
result = repo.init()
28+
assert "Initialized empty Git repository" in result
29+
assert (tmp_path / ".git").is_dir()
30+
31+
32+
def test_git_init_bare(tmp_path: pathlib.Path) -> None:
33+
"""Test git init with bare repository."""
34+
repo = git.Git(path=tmp_path)
35+
result = repo.init(bare=True)
36+
assert "Initialized empty Git repository" in result
37+
# Bare repos have files directly in the directory
38+
assert (tmp_path / "HEAD").exists()
39+
40+
41+
def test_git_init_template(tmp_path: pathlib.Path) -> None:
42+
"""Test git init with template directory."""
43+
template_dir = tmp_path / "template"
44+
template_dir.mkdir()
45+
(template_dir / "hooks").mkdir()
46+
(template_dir / "hooks" / "pre-commit").write_text("#!/bin/sh\nexit 0\n")
47+
48+
repo_dir = tmp_path / "repo"
49+
repo_dir.mkdir()
50+
repo = git.Git(path=repo_dir)
51+
result = repo.init(template=str(template_dir))
52+
53+
assert "Initialized empty Git repository" in result
54+
assert (repo_dir / ".git" / "hooks" / "pre-commit").exists()
55+
56+
57+
def test_git_init_separate_git_dir(tmp_path: pathlib.Path) -> None:
58+
"""Test git init with separate git directory."""
59+
repo_dir = tmp_path / "repo"
60+
git_dir = tmp_path / "git_dir"
61+
repo_dir.mkdir()
62+
git_dir.mkdir()
63+
64+
repo = git.Git(path=repo_dir)
65+
result = repo.init(separate_git_dir=str(git_dir.absolute()))
66+
67+
assert "Initialized empty Git repository" in result
68+
assert git_dir.is_dir()
69+
assert (git_dir / "HEAD").exists()
70+
71+
72+
def test_git_init_initial_branch(tmp_path: pathlib.Path) -> None:
73+
"""Test git init with custom initial branch name."""
74+
repo = git.Git(path=tmp_path)
75+
result = repo.init(branch="main")
76+
77+
assert "Initialized empty Git repository" in result
78+
# Check if HEAD points to the correct branch
79+
head_content = (tmp_path / ".git" / "HEAD").read_text()
80+
assert "ref: refs/heads/main" in head_content
81+
82+
83+
def test_git_init_shared(tmp_path: pathlib.Path) -> None:
84+
"""Test git init with shared repository settings."""
85+
repo = git.Git(path=tmp_path)
86+
87+
# Test boolean shared
88+
result = repo.init(shared=True)
89+
assert "Initialized empty shared Git repository" in result
90+
91+
# Test string shared value
92+
repo_dir = tmp_path / "shared_group"
93+
repo_dir.mkdir()
94+
repo = git.Git(path=repo_dir)
95+
result = repo.init(shared="group")
96+
assert "Initialized empty shared Git repository" in result
97+
98+
99+
def test_git_init_quiet(tmp_path: pathlib.Path) -> None:
100+
"""Test git init with quiet flag."""
101+
repo = git.Git(path=tmp_path)
102+
result = repo.init(quiet=True)
103+
# Quiet mode should suppress normal output
104+
assert result == "" or "Initialized empty Git repository" not in result
105+
106+
107+
def test_git_init_object_format(tmp_path: pathlib.Path) -> None:
108+
"""Test git init with different object formats."""
109+
repo = git.Git(path=tmp_path)
110+
111+
# Test with sha1 (default)
112+
result = repo.init(object_format="sha1")
113+
assert "Initialized empty Git repository" in result
114+
115+
# Note: sha256 test is commented out as it might not be supported in all
116+
# git versions
117+
# repo_dir = tmp_path / "sha256"
118+
# repo_dir.mkdir()
119+
# repo = git.Git(path=repo_dir)
120+
# result = repo.init(object_format="sha256")
121+
# assert "Initialized empty Git repository" in result
122+
123+
124+
def test_git_reinit(tmp_path: pathlib.Path) -> None:
125+
"""Test reinitializing an existing repository."""
126+
repo = git.Git(path=tmp_path)
127+
128+
# Initial init
129+
first_result = repo.init()
130+
assert "Initialized empty Git repository" in first_result
131+
132+
# Reinit
133+
second_result = repo.init()
134+
assert "Reinitialized existing Git repository" in second_result

0 commit comments

Comments
 (0)