Skip to content

Commit eafd085

Browse files
authored
Pytest plugin: VCS Configuration for hg commit and git commit (#476)
# Problem Downstream packages like `vcspull` cannot appropriately mock `HGUSER` and `GIT_COMMITTER_NAME`, `GIT_AUTHOR_NAME`, `GIT_COMMITTER_EMAIL`, and `GIT_AUTHOR_EMAIL`. # Changes ## pytest plugin: Authorship fixtures - New, customizable session-scoped fixtures for default committer on Mercurial and Git: - Name: {func}`libvcs.pytest_plugin.vcs_name` - Email: {func}`libvcs.pytest_plugin.vcs_email` - User (e.g. _`user <email@tld>`_): {func}`libvcs.pytest_plugin.vcs_user` - For git only: {func}`libvcs.pytest_plugin.git_commit_envvars` ## pytest plugins: Default repos use authorship fixtures New repos will automatically apply these session-scoped fixtures.
2 parents c71ece3 + 673c1e0 commit eafd085

File tree

4 files changed

+193
-45
lines changed

4 files changed

+193
-45
lines changed

CHANGES

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,25 @@ $ pip install --user --upgrade --pre libvcs
1515

1616
<!-- Maintainers, insert changes / features for the next release here -->
1717

18+
### New features
19+
20+
#### pytest plugin: Authorship fixtures (#476)
21+
22+
- New, customizable session-scoped fixtures for default committer on Mercurial and Git:
23+
- Name: {func}`libvcs.pytest_plugin.vcs_name`
24+
- Email: {func}`libvcs.pytest_plugin.vcs_email`
25+
- User (e.g. _`user <email@tld>`_): {func}`libvcs.pytest_plugin.vcs_user`
26+
- For git only: {func}`libvcs.pytest_plugin.git_commit_envvars`
27+
28+
#### pytest plugins: Default repos use authorship fixtures (#476)
29+
30+
New repos will automatically apply these session-scoped fixtures.
31+
1832
## libvcs 0.32.3 (2024-10-13)
1933

2034
### Bug fixes
2135

22-
- Pytest fixtures `hg_remote_repo_single_commit_post_init()` and `git_remote_repo_single_commit_post_init()` now support passing `env` for VCS configuration.
36+
- Pytest fixtures `hg_remote_repo_single_commit_post_init()` and `git_remote_repo_single_commit_post_init()` now support passing `env` for VCS configuration.
2337

2438
Both functions accept `hgconfig` and `gitconfig` fixtures, now applied in the `hg_repo` and `git_repo` fixtures.
2539

docs/pytest-plugin.md

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ This pytest plugin works by providing {ref}`pytest fixtures <pytest:fixtures-api
3737

3838
## Recommended Fixtures
3939

40-
When the plugin is enabled and `pytest` is run, these fixtures are automatically used:
40+
When the plugin is enabled and `pytest` is run, these overridable fixtures are automatically used:
4141

4242
- Create temporary test directories for:
4343
- `/home/` ({func}`home_path`)
@@ -50,6 +50,11 @@ When the plugin is enabled and `pytest` is run, these fixtures are automatically
5050
- Set default VCS configurations:
5151
- Use {func}`hgconfig` for [`HGRCPATH`] via {func}`set_hgconfig`
5252
- Use {func}`gitconfig` for [`GIT_CONFIG`] via {func}`set_gitconfig`
53+
- Set default commit names and emails:
54+
- Name: {func}`vcs_name`
55+
- Email: {func}`vcs_email`
56+
- User (e.g. _`user <email@tld>`_): {func}`vcs_user`
57+
- For git only: {func}`git_commit_envvars`
5358

5459
These ensure that repositories can be cloned and created without unnecessary warnings.
5560

@@ -74,10 +79,19 @@ def setup(set_home: None):
7479
pass
7580
```
7681

77-
### Setting a Default VCS Configuration
82+
### VCS Configuration
7883

7984
#### Git
8085

86+
You can override the default author used in {func}`git_remote_repo` and other
87+
fixtures via {func}`vcs_name`, {func}`vcs_email`, and {func}`vcs_user`:
88+
89+
```
90+
@pytest.fixture(scope="session")
91+
def vcs_name() -> str:
92+
return "My custom name"
93+
```
94+
8195
Use the {func}`set_gitconfig` fixture with `autouse=True`:
8296

8397
```python
@@ -88,6 +102,27 @@ def setup(set_gitconfig: None):
88102
pass
89103
```
90104

105+
Sometimes, `set_getconfig` via `GIT_CONFIG` doesn't apply as expected. For those
106+
cases, you can use {func}`git_commit_envvars`:
107+
108+
```python
109+
import pytest
110+
111+
@pytest.fixture
112+
def my_git_repo(
113+
create_git_remote_repo: CreateRepoPytestFixtureFn,
114+
gitconfig: pathlib.Path,
115+
git_commit_envvars: "_ENV",
116+
) -> pathlib.Path:
117+
"""Copy the session-scoped Git repository to a temporary directory."""
118+
repo_path = create_git_remote_repo()
119+
git_remote_repo_single_commit_post_init(
120+
remote_repo_path=repo_path,
121+
env=git_commit_envvars,
122+
)
123+
return repo_path
124+
```
125+
91126
#### Mercurial
92127

93128
Use the {func}`set_hgconfig` fixture with `autouse=True`:

src/libvcs/pytest_plugin.py

Lines changed: 75 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,43 @@ def __init__(self, attempts: int, *args: object) -> None:
4646
)
4747

4848

49+
DEFAULT_VCS_NAME = "Test user"
50+
DEFAULT_VCS_EMAIL = "test@example.com"
51+
52+
53+
@pytest.fixture(scope="session")
54+
def vcs_name() -> str:
55+
"""Return default VCS name."""
56+
return DEFAULT_VCS_NAME
57+
58+
59+
@pytest.fixture(scope="session")
60+
def vcs_email() -> str:
61+
"""Return default VCS email."""
62+
return DEFAULT_VCS_EMAIL
63+
64+
65+
@pytest.fixture(scope="session")
66+
def vcs_user(vcs_name: str, vcs_email: str) -> str:
67+
"""Return default VCS user."""
68+
return f"{vcs_name} <{vcs_email}>"
69+
70+
71+
@pytest.fixture(scope="session")
72+
def git_commit_envvars(vcs_name: str, vcs_email: str) -> "_ENV":
73+
"""Return environment variables for `git commit`.
74+
75+
For some reason, `GIT_CONFIG` via {func}`set_gitconfig` doesn't work for `git
76+
commit`.
77+
"""
78+
return {
79+
"GIT_AUTHOR_NAME": vcs_name,
80+
"GIT_AUTHOR_EMAIL": vcs_email,
81+
"GIT_COMMITTER_NAME": vcs_name,
82+
"GIT_COMMITTER_EMAIL": vcs_email,
83+
}
84+
85+
4986
class RandomStrSequence:
5087
"""Create a random string sequence."""
5188

@@ -110,13 +147,12 @@ def set_home(
110147
monkeypatch.setenv("HOME", str(user_path))
111148

112149

113-
vcs_email = "libvcs@git-pull.com"
114-
115-
116150
@pytest.fixture(scope="session")
117151
@skip_if_git_missing
118152
def gitconfig(
119153
user_path: pathlib.Path,
154+
vcs_email: str,
155+
vcs_name: str,
120156
) -> pathlib.Path:
121157
"""Return git configuration, pytest fixture."""
122158
gitconfig = user_path / ".gitconfig"
@@ -129,7 +165,7 @@ def gitconfig(
129165
f"""
130166
[user]
131167
email = {vcs_email}
132-
name = {getpass.getuser()}
168+
name = {vcs_name}
133169
[color]
134170
diff = auto
135171
""",
@@ -155,14 +191,15 @@ def set_gitconfig(
155191
@skip_if_hg_missing
156192
def hgconfig(
157193
user_path: pathlib.Path,
194+
vcs_user: str,
158195
) -> pathlib.Path:
159196
"""Return Mercurial configuration."""
160197
hgrc = user_path / ".hgrc"
161198
hgrc.write_text(
162199
textwrap.dedent(
163200
f"""
164201
[ui]
165-
username = libvcs tests <libvcs@git-pull.com>
202+
username = {vcs_user}
166203
merge = internal:merge
167204
168205
[trusted]
@@ -237,7 +274,11 @@ def unique_repo_name(remote_repos_path: pathlib.Path, max_retries: int = 15) ->
237274
class CreateRepoPostInitFn(Protocol):
238275
"""Typing for VCS repo creation callback."""
239276

240-
def __call__(self, remote_repo_path: pathlib.Path) -> None:
277+
def __call__(
278+
self,
279+
remote_repo_path: pathlib.Path,
280+
env: "_ENV | None" = None,
281+
) -> None:
241282
"""Ran after creating a repo from pytest fixture."""
242283
...
243284

@@ -263,6 +304,7 @@ def _create_git_remote_repo(
263304
remote_repo_path: pathlib.Path,
264305
remote_repo_post_init: Optional[CreateRepoPostInitFn] = None,
265306
init_cmd_args: InitCmdArgs = DEFAULT_GIT_REMOTE_REPO_CMD_ARGS,
307+
env: "_ENV | None" = None,
266308
) -> pathlib.Path:
267309
if init_cmd_args is None:
268310
init_cmd_args = []
@@ -272,7 +314,7 @@ def _create_git_remote_repo(
272314
)
273315

274316
if remote_repo_post_init is not None and callable(remote_repo_post_init):
275-
remote_repo_post_init(remote_repo_path=remote_repo_path)
317+
remote_repo_post_init(remote_repo_path=remote_repo_path, env=env)
276318

277319
return remote_repo_path
278320

@@ -402,26 +444,29 @@ def git_remote_repo_single_commit_post_init(
402444
run(
403445
["touch", testfile_filename],
404446
cwd=remote_repo_path,
405-
env={"GITCONFIG": str(gitconfig)},
447+
env=env,
448+
)
449+
run(["git", "add", testfile_filename], cwd=remote_repo_path, env=env)
450+
run(
451+
["git", "commit", "-m", "test file for dummyrepo"],
452+
cwd=remote_repo_path,
453+
env=env,
406454
)
407-
run(["git", "add", testfile_filename], cwd=remote_repo_path)
408-
run(["git", "commit", "-m", "test file for dummyrepo"], cwd=remote_repo_path)
409455

410456

411457
@pytest.fixture(scope="session")
412458
@skip_if_git_missing
413459
def git_remote_repo(
414460
create_git_remote_repo: CreateRepoPytestFixtureFn,
415461
gitconfig: pathlib.Path,
462+
git_commit_envvars: "_ENV",
416463
) -> pathlib.Path:
417464
"""Copy the session-scoped Git repository to a temporary directory."""
418465
# TODO: Cache the effect of of this in a session-based repo
419466
repo_path = create_git_remote_repo()
420467
git_remote_repo_single_commit_post_init(
421468
remote_repo_path=repo_path,
422-
env={
423-
"GITCONFIG": str(gitconfig),
424-
},
469+
env=git_commit_envvars,
425470
)
426471
return repo_path
427472

@@ -596,6 +641,7 @@ def empty_hg_repo(
596641
def create_hg_remote_repo(
597642
remote_repos_path: pathlib.Path,
598643
empty_hg_repo: pathlib.Path,
644+
hgconfig: pathlib.Path,
599645
) -> CreateRepoPytestFixtureFn:
600646
"""Pre-made hg repo, bare, used as a file:// remote to checkout and commit to."""
601647

@@ -612,7 +658,10 @@ def fn(
612658
shutil.copytree(empty_hg_repo, remote_repo_path)
613659

614660
if remote_repo_post_init is not None and callable(remote_repo_post_init):
615-
remote_repo_post_init(remote_repo_path=remote_repo_path)
661+
remote_repo_post_init(
662+
remote_repo_path=remote_repo_path,
663+
env={"HGRCPATH": str(hgconfig)},
664+
)
616665

617666
assert empty_hg_repo.exists()
618667

@@ -633,7 +682,8 @@ def hg_remote_repo(
633682
"""Pre-made, file-based repo for push and pull."""
634683
repo_path = create_hg_remote_repo()
635684
hg_remote_repo_single_commit_post_init(
636-
remote_repo_path=repo_path, env={"HGRCPATH": str(hgconfig)}
685+
remote_repo_path=repo_path,
686+
env={"HGRCPATH": str(hgconfig)},
637687
)
638688
return repo_path
639689

@@ -731,6 +781,8 @@ def add_doctest_fixtures(
731781
doctest_namespace: dict[str, Any],
732782
tmp_path: pathlib.Path,
733783
set_home: pathlib.Path,
784+
git_commit_envvars: "_ENV",
785+
hgconfig: pathlib.Path,
734786
create_git_remote_repo: CreateRepoPytestFixtureFn,
735787
create_svn_remote_repo: CreateRepoPytestFixtureFn,
736788
create_hg_remote_repo: CreateRepoPytestFixtureFn,
@@ -745,7 +797,10 @@ def add_doctest_fixtures(
745797
if shutil.which("git"):
746798
doctest_namespace["create_git_remote_repo"] = functools.partial(
747799
create_git_remote_repo,
748-
remote_repo_post_init=git_remote_repo_single_commit_post_init,
800+
remote_repo_post_init=functools.partial(
801+
git_remote_repo_single_commit_post_init,
802+
env=git_commit_envvars,
803+
),
749804
init_cmd_args=None,
750805
)
751806
doctest_namespace["create_git_remote_repo_bare"] = create_git_remote_repo
@@ -760,5 +815,8 @@ def add_doctest_fixtures(
760815
doctest_namespace["create_hg_remote_repo_bare"] = create_hg_remote_repo
761816
doctest_namespace["create_hg_remote_repo"] = functools.partial(
762817
create_hg_remote_repo,
763-
remote_repo_post_init=hg_remote_repo_single_commit_post_init,
818+
remote_repo_post_init=functools.partial(
819+
hg_remote_repo_single_commit_post_init,
820+
env={"HGRCPATH": str(hgconfig)},
821+
),
764822
)

0 commit comments

Comments
 (0)