giterator 0.3.0__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. giterator-0.4.0/.github/workflows/ci.yml +15 -0
  2. giterator-0.4.0/.github/workflows/release.yml +46 -0
  3. giterator-0.4.0/.gitignore +10 -0
  4. giterator-0.4.0/.python-version +1 -0
  5. giterator-0.4.0/.readthedocs.yml +10 -0
  6. giterator-0.4.0/AGENTS.md +59 -0
  7. giterator-0.4.0/CLAUDE.md +1 -0
  8. giterator-0.4.0/PKG-INFO +29 -0
  9. {giterator-0.3.0 → giterator-0.4.0}/README.rst +3 -4
  10. giterator-0.4.0/docs/Makefile +20 -0
  11. giterator-0.4.0/docs/changes.rst +39 -0
  12. giterator-0.4.0/docs/conf.py +25 -0
  13. giterator-0.4.0/docs/development.rst +58 -0
  14. giterator-0.4.0/happy.sh +19 -0
  15. giterator-0.4.0/pyproject.toml +68 -0
  16. {giterator-0.3.0 → giterator-0.4.0/src}/giterator/__main__.py +1 -0
  17. {giterator-0.3.0 → giterator-0.4.0/src}/giterator/cli.py +3 -4
  18. {giterator-0.3.0 → giterator-0.4.0/src}/giterator/clock.py +2 -1
  19. {giterator-0.3.0 → giterator-0.4.0/src}/giterator/git.py +46 -24
  20. giterator-0.4.0/src/giterator/testing.py +77 -0
  21. giterator-0.4.0/src/giterator/typing.py +3 -0
  22. giterator-0.4.0/tests/__init__.py +0 -0
  23. {giterator-0.3.0 → giterator-0.4.0}/tests/conftest.py +7 -4
  24. {giterator-0.3.0 → giterator-0.4.0}/tests/test_cli.py +18 -10
  25. giterator-0.4.0/tests/test_clock.py +17 -0
  26. giterator-0.4.0/tests/test_git.py +212 -0
  27. giterator-0.4.0/tests/test_testing.py +115 -0
  28. giterator-0.3.0/.carthorse.yml +0 -9
  29. giterator-0.3.0/.circleci/config.yml +0 -40
  30. giterator-0.3.0/.coveragerc +0 -3
  31. giterator-0.3.0/.gitignore +0 -8
  32. giterator-0.3.0/.readthedocs.yml +0 -16
  33. giterator-0.3.0/PKG-INFO +0 -48
  34. giterator-0.3.0/docs/Makefile +0 -78
  35. giterator-0.3.0/docs/changes.rst +0 -22
  36. giterator-0.3.0/docs/conf.py +0 -38
  37. giterator-0.3.0/docs/development.rst +0 -66
  38. giterator-0.3.0/giterator/testing.py +0 -58
  39. giterator-0.3.0/giterator/typing.py +0 -4
  40. giterator-0.3.0/giterator.egg-info/PKG-INFO +0 -48
  41. giterator-0.3.0/giterator.egg-info/SOURCES.txt +0 -35
  42. giterator-0.3.0/giterator.egg-info/dependency_links.txt +0 -1
  43. giterator-0.3.0/giterator.egg-info/entry_points.txt +0 -2
  44. giterator-0.3.0/giterator.egg-info/not-zip-safe +0 -1
  45. giterator-0.3.0/giterator.egg-info/requires.txt +0 -14
  46. giterator-0.3.0/giterator.egg-info/top_level.txt +0 -1
  47. giterator-0.3.0/setup.cfg +0 -4
  48. giterator-0.3.0/setup.py +0 -42
  49. giterator-0.3.0/tests/test_git.py +0 -193
  50. giterator-0.3.0/tests/test_testing.py +0 -65
  51. {giterator-0.3.0 → giterator-0.4.0}/LICENSE.rst +0 -0
  52. {giterator-0.3.0 → giterator-0.4.0}/docs/api.rst +0 -0
  53. {giterator-0.3.0 → giterator-0.4.0}/docs/index.rst +0 -0
  54. {giterator-0.3.0 → giterator-0.4.0}/docs/license.rst +0 -0
  55. {giterator-0.3.0 → giterator-0.4.0}/docs/use.rst +0 -0
  56. {giterator-0.3.0 → giterator-0.4.0/src}/giterator/__init__.py +0 -0
  57. /giterator-0.3.0/tests/__init__.py → /giterator-0.4.0/src/giterator/py.typed +0 -0
@@ -0,0 +1,15 @@
1
+ name: "Continuous Integration"
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ schedule:
8
+ - cron: "0 1 * * *"
9
+
10
+ jobs:
11
+ ci:
12
+ uses: cjw296/python-workflow/.github/workflows/uv-ci.yml@v2
13
+ with:
14
+ package: giterator
15
+ python-versions: '[ "3.11", "3.14" ]'
@@ -0,0 +1,46 @@
1
+ name: "Release"
2
+
3
+ on:
4
+ workflow_run:
5
+ workflows: [Continuous Integration]
6
+ types: [completed]
7
+ branches: [main]
8
+
9
+ jobs:
10
+ check:
11
+ runs-on: ubuntu-latest
12
+ if: ${{ github.repository == 'simplistix/giterator' && github.event.workflow_run.conclusion == 'success' }}
13
+ outputs:
14
+ tag: ${{ steps.carthorse.outputs.tag }}
15
+ steps:
16
+ - uses: cjw296/python-action/carthorse@v1
17
+ id: carthorse
18
+ with:
19
+ tag-format: 'v{version}'
20
+ check: "true"
21
+
22
+ release:
23
+ runs-on: ubuntu-latest
24
+ needs: check
25
+ if: ${{ needs.check.outputs.tag != '' }}
26
+ permissions:
27
+ contents: write
28
+ id-token: write
29
+ steps:
30
+ - name: Show tag
31
+ run: echo "${{ toJson(needs.check.outputs) }}"
32
+ shell: bash
33
+
34
+ - name: Download packages from CI run
35
+ uses: dawidd6/action-download-artifact@v9
36
+ with:
37
+ run_id: ${{ github.event.workflow_run.id }}
38
+ name: Packages
39
+ path: dist
40
+
41
+ - name: Publish package distributions to PyPI
42
+ uses: pypa/gh-action-pypi-publish@release/v1
43
+
44
+ - uses: cjw296/python-action/carthorse@v1
45
+ with:
46
+ tag-format: 'v{version}'
@@ -0,0 +1,10 @@
1
+ *.egg-info
2
+ .coverage
3
+ .coverage.*
4
+ _build/
5
+ .*cache/
6
+ __pycache__/
7
+ .venv
8
+ .claude
9
+ uv.lock
10
+ README.html
@@ -0,0 +1 @@
1
+ 3.11
@@ -0,0 +1,10 @@
1
+ version: 2
2
+
3
+ build:
4
+ os: ubuntu-24.04
5
+ commands:
6
+ - asdf plugin add uv
7
+ - asdf install uv latest
8
+ - asdf global uv latest
9
+ - uv sync --group docs
10
+ - uv run sphinx-build --fail-on-warning -T -b html docs $READTHEDOCS_OUTPUT/html
@@ -0,0 +1,59 @@
1
+ # Agent Instructions
2
+
3
+ ## Principles
4
+
5
+ - **Done means green**: a change is only complete when `./happy.sh` exits 0; do not commit until it does.
6
+ - **No unrelated failures**: if `./happy.sh` fails on something unrelated to your changes, do NOT assume it is a pre-existing problem and proceed anyway. Stop immediately and ask the user how to proceed.
7
+ - **Docs for everything public**: new functionality or public API changes must have accompanying docs in `docs/*.rst`
8
+ - **No em-dashes or parenthetical asides in prose**: in `docs/*.rst` prose and Python docstrings, never use em-dashes, and never tuck a clause inside parentheses; rephrase with commas or separate sentences. This does not apply to code comments or agent-facing notes such as this file, where both are fine.
9
+ - **No stacked headings in docs**: a heading in `docs/*.rst` must be followed by prose, never immediately by a sub-heading. Add a short lead-in or merge the levels.
10
+ - **Type-annotate public APIs**: all public functions and classes need type annotations; mypy is the gate
11
+ - **Use `compare()` in tests**: assert with `compare(actual, expected=...)`, using `StringComparison` for pattern matches. Bare `assert` only for booleans and `isinstance` (which type-narrows for mypy).
12
+ - **No `noqa`, ever**: this project has zero linter suppressions; don't add them. Fix the underlying issue instead.
13
+ - **No `docs/changes.rst` edits during development**: that file is updated at release time, not as part of feature work.
14
+
15
+ ## Project Overview
16
+
17
+ Giterator is a Python library and command line tool for doing git things: it wraps
18
+ command-line git to script repository operations and provides helpers for making
19
+ sample repositories in tests.
20
+
21
+ ## Environment
22
+
23
+ ```bash
24
+ uv sync --all-extras --all-groups # setup or after pulling
25
+ rm -rf .venv && uv sync --all-extras --all-groups # full reset
26
+ ```
27
+
28
+ ## Commands
29
+
30
+ ```bash
31
+ ./happy.sh # all checks: required before commit
32
+ uv run pytest # all tests
33
+ uv run pytest tests/test_git.py # single file
34
+ uv run pytest --cov --cov-report=term-missing # with coverage
35
+ uv run mypy src tests # type checking
36
+ uv run make -C docs html # build docs
37
+ uv build # build sdist + wheel
38
+ ```
39
+
40
+ ## Architecture
41
+
42
+ `src/giterator/`: all source. Key modules:
43
+
44
+ - `git.py`: `Git`, the main class wrapping command-line git; also `User` and `GitError`
45
+ - `testing.py`: `Repo`, for making sample repositories in tests
46
+ - `clock.py`: controllable time source used for commit timestamps
47
+ - `cli.py`: the `giterator` command line entry point
48
+ - `__main__.py`: `python -m giterator` support
49
+ - `typing.py`: type definitions
50
+
51
+ Config: `pyproject.toml`.
52
+
53
+ Testing specifics:
54
+
55
+ - Coverage must be 100% and tracks both `giterator` and `tests`.
56
+ - CLI tests run `python -m giterator` in subprocesses: `tests/conftest.py` sets
57
+ `COVERAGE_PROCESS_START` and the `coverage-enable-subprocess` dev dependency measures
58
+ them, so coverage runs in parallel mode.
59
+ - Tests exercise real git via temporary repositories, so a `git` binary is required.
@@ -0,0 +1 @@
1
+ AGENTS.md
@@ -0,0 +1,29 @@
1
+ Metadata-Version: 2.4
2
+ Name: giterator
3
+ Version: 0.4.0
4
+ Summary: Python tools for doing git things.
5
+ Project-URL: Homepage, https://github.com/simplistix/giterator
6
+ Project-URL: Documentation, https://giterator.readthedocs.org/en/latest/
7
+ Project-URL: Repository, https://github.com/simplistix/giterator
8
+ Project-URL: Changelog, https://giterator.readthedocs.io/en/latest/changes.html
9
+ Author-email: Chris Withers <chris@withers.org>
10
+ License: MIT
11
+ License-File: LICENSE.rst
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Requires-Python: >=3.11
16
+ Description-Content-Type: text/x-rst
17
+
18
+ giterator
19
+ =========
20
+
21
+ |CI|_ |Docs|_
22
+
23
+ .. |CI| image:: https://github.com/simplistix/giterator/actions/workflows/ci.yml/badge.svg
24
+ .. _CI: https://github.com/simplistix/giterator/actions/workflows/ci.yml
25
+
26
+ .. |Docs| image:: https://readthedocs.org/projects/giterator/badge/?version=latest
27
+ .. _Docs: http://giterator.readthedocs.org/en/latest/
28
+
29
+ Python tools for doing git things.
@@ -1,11 +1,10 @@
1
-
2
1
  giterator
3
2
  =========
4
3
 
5
- |CircleCI|_ |Docs|_
4
+ |CI|_ |Docs|_
6
5
 
7
- .. |CircleCI| image:: https://circleci.com/gh/simplistix/giterator/tree/master.svg?style=shield
8
- .. _CircleCI: https://circleci.com/gh/simplistix/giterator/tree/master
6
+ .. |CI| image:: https://github.com/simplistix/giterator/actions/workflows/ci.yml/badge.svg
7
+ .. _CI: https://github.com/simplistix/giterator/actions/workflows/ci.yml
9
8
 
10
9
  .. |Docs| image:: https://readthedocs.org/projects/giterator/badge/?version=latest
11
10
  .. _Docs: http://giterator.readthedocs.org/en/latest/
@@ -0,0 +1,20 @@
1
+ # Minimal makefile for Sphinx documentation
2
+ #
3
+
4
+ # You can set these variables from the command line, and also
5
+ # from the environment for the first two.
6
+ SPHINXOPTS ?=
7
+ SPHINXBUILD ?= uv run sphinx-build
8
+ SOURCEDIR = .
9
+ BUILDDIR = _build
10
+
11
+ # Put it first so that "make" without argument is like "make help".
12
+ help:
13
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
14
+
15
+ .PHONY: help Makefile
16
+
17
+ # Catch-all target: route all unknown targets to Sphinx using the new
18
+ # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
19
+ %: Makefile
20
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
@@ -0,0 +1,39 @@
1
+ .. py:currentmodule:: giterator
2
+
3
+ Changes
4
+ =======
5
+
6
+ 0.4.0 (4 Jul 2026)
7
+ ------------------
8
+
9
+ - Moved to a `uv`__-based, ``pyproject.toml``-driven project layout, with ``main`` replacing
10
+ ``master`` as the default git branch.
11
+
12
+ __ https://docs.astral.sh/uv/
13
+
14
+ - :meth:`Repo.clone <giterator.testing.Repo.clone>` now always ensures a user is configured in
15
+ the clone, whether specified explicitly, inherited from the source repo, or falling back to
16
+ the same default as :meth:`Repo.make <giterator.testing.Repo.make>`, so commits made in
17
+ clones no longer depend on the git config of the machine the tests are running on.
18
+
19
+ - :meth:`Git.init` can now pin the name of the initial branch, and
20
+ :meth:`Repo.make <giterator.testing.Repo.make>` does so by default, using ``main``, so branch
21
+ names in test repos no longer depend on the git config of the machine the tests are running on.
22
+
23
+ 0.3.0 (4 Feb 2026)
24
+ ------------------
25
+
26
+ - General refresh.
27
+
28
+ - Add ``short`` parameter to methods that return commit hashes,
29
+ allowing the full commit hash to be returned.
30
+
31
+ 0.2.0 (1 Oct 2021)
32
+ ------------------
33
+
34
+ - Methods that create commits now return the newly-create commit hash.
35
+
36
+ 0.1.0 (28 Sep 2021)
37
+ -------------------
38
+
39
+ - Initial release
@@ -0,0 +1,25 @@
1
+ from importlib import metadata
2
+
3
+ extensions = [
4
+ 'sphinx.ext.autodoc',
5
+ 'sphinx.ext.intersphinx',
6
+ ]
7
+
8
+ intersphinx_mapping = {
9
+ 'python': ('https://docs.python.org/3/', None),
10
+ }
11
+
12
+ # General
13
+ source_suffix = '.rst'
14
+ master_doc = 'index'
15
+ project = 'giterator'
16
+ copyright = '2020 onwards Chris Withers'
17
+ version = release = metadata.version(project)
18
+ exclude_patterns = ['_build']
19
+ pygments_style = 'sphinx'
20
+ autodoc_member_order = 'bysource'
21
+
22
+ # Options for HTML output
23
+ html_theme = 'furo'
24
+ html_title = 'giterator'
25
+ htmlhelp_basename = project + 'doc'
@@ -0,0 +1,58 @@
1
+ Development
2
+ ===========
3
+
4
+ .. highlight:: bash
5
+
6
+ The latest development version of the documentation can be found here:
7
+
8
+ http://giterator.readthedocs.org/en/latest/
9
+
10
+ If you wish to contribute to this project, then you should fork the
11
+ repository found here:
12
+
13
+ https://github.com/simplistix/giterator/
14
+
15
+ Once that has been done and you have a checkout,
16
+ you can follow the instructions below to perform various development tasks.
17
+
18
+ For detailed development guidelines, code style requirements, and additional commands,
19
+ see ``AGENTS.md`` in the repository root.
20
+
21
+ Setting up a development environment
22
+ ------------------------------------
23
+
24
+ The recommended way to set up a development environment is to use `uv`__
25
+ to install all groups and extras:
26
+
27
+ __ https://docs.astral.sh/uv/
28
+
29
+ .. code-block:: bash
30
+
31
+ uv sync --all-extras --all-groups
32
+
33
+ Running the tests
34
+ -----------------
35
+
36
+ Once you've set up the environment, the tests can be run from the root of a
37
+ source checkout as follows:
38
+
39
+ .. code-block:: bash
40
+
41
+ uv run pytest
42
+
43
+ Building the documentation
44
+ --------------------------
45
+
46
+ The Sphinx documentation is built by doing the following from the
47
+ repository root:
48
+
49
+ .. code-block:: bash
50
+
51
+ uv run make -C docs html
52
+
53
+ Making a release
54
+ ----------------
55
+
56
+ To make a release, just update the version in ``pyproject.toml``,
57
+ update the change log, and push to https://github.com/simplistix/giterator
58
+ and the GitHub Actions release workflow should take care of the rest.
@@ -0,0 +1,19 @@
1
+ #!/bin/bash
2
+ set -ex
3
+
4
+ echo "=== Syncing dependencies ==="
5
+ uv sync --all-extras --all-groups
6
+
7
+ echo "=== Tests + Coverage ==="
8
+ uv run pytest --cov --cov-report=term-missing --cov-fail-under=100
9
+
10
+ echo "=== Formatting ==="
11
+ uv run ruff format .
12
+
13
+ echo "=== Type Checking ==="
14
+ uv run mypy src tests
15
+
16
+ echo "=== Docs Build ==="
17
+ uv run make -C docs clean html SPHINXOPTS=--fail-on-warning
18
+
19
+ echo "=== All checks passed! ==="
@@ -0,0 +1,68 @@
1
+ [project]
2
+ name = "giterator"
3
+ version = "0.4.0"
4
+ description = "Python tools for doing git things."
5
+ readme = "README.rst"
6
+ authors = [{ name = "Chris Withers", email = "chris@withers.org" }]
7
+ license = { text = "MIT" }
8
+ requires-python = ">=3.11"
9
+ dependencies = []
10
+ classifiers = [
11
+ "Intended Audience :: Developers",
12
+ "License :: OSI Approved :: MIT License",
13
+ "Programming Language :: Python :: 3",
14
+ ]
15
+
16
+ [project.urls]
17
+ Homepage = "https://github.com/simplistix/giterator"
18
+ Documentation = "https://giterator.readthedocs.org/en/latest/"
19
+ Repository = "https://github.com/simplistix/giterator"
20
+ Changelog = "https://giterator.readthedocs.io/en/latest/changes.html"
21
+
22
+ [project.scripts]
23
+ giterator = "giterator.cli:main"
24
+
25
+ [dependency-groups]
26
+ dev = [
27
+ "coverage-enable-subprocess>=1.0",
28
+ "mypy>=1.0",
29
+ "pytest>=8",
30
+ "pytest-cov>=6",
31
+ "ruff>=0.9",
32
+ "testfixtures>=11",
33
+ ]
34
+ docs = [
35
+ "furo>=2024",
36
+ "sphinx>=8",
37
+ ]
38
+
39
+ [build-system]
40
+ requires = ["hatchling"]
41
+ build-backend = "hatchling.build"
42
+
43
+ [tool.hatch.build.targets.wheel]
44
+ packages = ["src/giterator"]
45
+
46
+ [tool.pytest.ini_options]
47
+ addopts = "-p no:doctest"
48
+ testpaths = ["tests"]
49
+
50
+ [tool.mypy]
51
+ strict = true
52
+
53
+ [tool.ruff]
54
+ line-length = 100
55
+
56
+ [tool.ruff.format]
57
+ quote-style = "preserve"
58
+
59
+ [tool.coverage.run]
60
+ source = ["giterator", "tests"]
61
+ parallel = true
62
+
63
+ [tool.coverage.report]
64
+ exclude_lines = [
65
+ "pragma: no cover",
66
+ "if TYPE_CHECKING:",
67
+ "\\.\\.\\.",
68
+ ]
@@ -1,2 +1,3 @@
1
1
  from .cli import main
2
+
2
3
  main()
@@ -2,11 +2,10 @@ from argparse import ArgumentParser, Namespace
2
2
 
3
3
 
4
4
  class Command:
5
-
6
- def add_args(self, parser: ArgumentParser):
5
+ def add_args(self, parser: ArgumentParser) -> None:
7
6
  pass
8
7
 
9
- def __call__(self, args: Namespace):
8
+ def __call__(self, args: Namespace) -> None:
10
9
  pass
11
10
 
12
11
 
@@ -30,6 +29,6 @@ def parse_args() -> Namespace:
30
29
  return parser.parse_args()
31
30
 
32
31
 
33
- def main():
32
+ def main() -> None:
34
33
  args = parse_args()
35
34
  args.command(args)
@@ -7,7 +7,7 @@ class Clock:
7
7
  not uniformly.
8
8
  """
9
9
 
10
- def __init__(self):
10
+ def __init__(self) -> None:
11
11
  self._now = datetime(2001, 1, 1, 0).astimezone(timezone.utc)
12
12
  self._current_delta = 10
13
13
 
@@ -15,3 +15,4 @@ class Clock:
15
15
  now = self._now
16
16
  self._now = now + timedelta(seconds=self._current_delta)
17
17
  self._current_delta += 10
18
+ return now
@@ -1,7 +1,9 @@
1
+ from __future__ import annotations
2
+
1
3
  from os import makedirs
2
4
  from pathlib import Path
3
5
  from subprocess import check_output, STDOUT, CalledProcessError
4
- from typing import Union, Dict, List, Optional
6
+ from typing import Self
5
7
 
6
8
  from .typing import Date
7
9
 
@@ -29,15 +31,17 @@ class Git:
29
31
  :param path: The path to an existing work tree or local repo.
30
32
  """
31
33
 
32
- _user: User = None
34
+ _user: User | None = None
33
35
 
34
- def __init__(self, path: Union[Path, str]):
36
+ def __init__(self, path: Path | str):
35
37
  if not isinstance(path, Path):
36
38
  path = Path(path)
37
39
  #: The path where this instance is located.
38
40
  self.path: Path = path
39
41
 
40
- def __call__(self, *command, env: dict = None, cwd: Path = None) -> str:
42
+ def __call__(
43
+ self, *command: str, env: dict[str, str] | None = None, cwd: Path | None = None
44
+ ) -> str:
41
45
  """
42
46
  Run a git command in this repo. For example:
43
47
 
@@ -46,38 +50,56 @@ class Git:
46
50
  Git(...)('log', '-1')
47
51
  """
48
52
  try:
49
- output = check_output(
50
- ('git',) + command, cwd=cwd or self.path, stderr=STDOUT, env=env
51
- )
53
+ output = check_output(('git',) + command, cwd=cwd or self.path, stderr=STDOUT, env=env)
52
54
  except CalledProcessError as e:
53
55
  raise GitError(
54
- f"{' '.join(e.cmd)!r} gave return code {e.returncode}:\n\n"
55
- f"{e.output.decode()}\n\n"
56
+ f"{' '.join(e.cmd)!r} gave return code {e.returncode}:\n\n{e.output.decode()}\n\n"
56
57
  ) from None
57
58
  return output.decode()
58
59
 
59
60
  git = __call__
60
61
 
61
- def _set_user(self, user: Optional[User]):
62
+ def _set_user(self, user: User | None) -> None:
62
63
  if user:
63
64
  self._user = user
64
65
  self('config', 'user.name', user.name)
65
66
  self('config', 'user.email', user.email)
66
67
 
67
- def init(self, user: User = None) -> None:
68
+ def init(self, user: User | None = None, branch: str | None = None) -> None:
68
69
  """
69
70
  Create an empty Git repository or reinitialize an existing one.
70
71
  If the path doesn't exist, it will be created. This includes any missing
71
72
  parent directories.
72
73
 
73
74
  :param user: The user to configure in the local repo.
75
+ :param branch: The name to use for the initial branch. If not specified,
76
+ the machine's git default is used.
74
77
  """
75
78
  makedirs(self.path, exist_ok=True)
76
- self('init')
79
+ command = ['init']
80
+ if branch:
81
+ command.extend(['-b', branch])
82
+ self(*command)
77
83
  self._set_user(user)
78
84
 
79
85
  @classmethod
80
- def clone(cls, source: Union[str, Path, 'Git'], path: Union[str, Path], user: User = None):
86
+ def clone(
87
+ cls,
88
+ source: str | Path | Git,
89
+ path: str | Path,
90
+ user: User | None = None,
91
+ ) -> Self:
92
+ """
93
+ Clone the ``source`` repo to the ``path`` specified.
94
+
95
+ :param source: The repo to clone, either as a path or a :class:`Git` instance.
96
+ :param path: Where to clone to. Relative paths are resolved relative to the
97
+ parent of ``source``.
98
+ :param user: The user to configure in the local clone. If not specified and
99
+ ``source`` is a :class:`Git` instance, the user from ``source``, if any,
100
+ is configured. Otherwise, no user is configured and commits in the clone
101
+ will depend on git's normal identity discovery.
102
+ """
81
103
  if isinstance(source, Git):
82
104
  user = user or source._user
83
105
  source = source.path
@@ -89,15 +111,15 @@ class Git:
89
111
  return git
90
112
 
91
113
  @staticmethod
92
- def _coerce_date(dt):
114
+ def _coerce_date(dt: Date) -> str:
93
115
  return dt if isinstance(dt, str) else dt.isoformat()
94
116
 
95
117
  def commit(
96
- self,
97
- msg: str,
98
- author_date: Date = None,
99
- commit_date: Date = None,
100
- short: bool = True,
118
+ self,
119
+ msg: str,
120
+ author_date: Date | None = None,
121
+ commit_date: Date | None = None,
122
+ short: bool = True,
101
123
  ) -> str:
102
124
  """
103
125
  Commit changes in this repo, including and new or deleted files.
@@ -111,7 +133,7 @@ class Git:
111
133
  command = ['commit', '-m', msg]
112
134
  if author_date:
113
135
  command.extend(['--date', self._coerce_date(author_date)])
114
- env = {}
136
+ env: dict[str, str] = {}
115
137
  if commit_date:
116
138
  env['GIT_COMMITTER_DATE'] = self._coerce_date(commit_date)
117
139
  self(*command, env=env)
@@ -130,13 +152,13 @@ class Git:
130
152
  """
131
153
  self('tag', name)
132
154
 
133
- def tags(self) -> List[str]:
155
+ def tags(self) -> list[str]:
134
156
  """
135
157
  Return a list of tags in this repo.
136
158
  """
137
159
  return self('tag').split()
138
160
 
139
- def tag_hashes(self) -> Dict[str, str]:
161
+ def tag_hashes(self) -> dict[str, str]:
140
162
  """
141
163
  Return a mapping of tag name to commit hash.
142
164
  """
@@ -148,13 +170,13 @@ class Git:
148
170
  """
149
171
  self('checkout', '-b', name)
150
172
 
151
- def branches(self) -> List[str]:
173
+ def branches(self) -> list[str]:
152
174
  """
153
175
  Return a list of branches in this repo.
154
176
  """
155
177
  return self('for-each-ref', '--format', '%(refname:short)', 'refs/heads/').split()
156
178
 
157
- def branch_hashes(self) -> Dict[str, str]:
179
+ def branch_hashes(self) -> dict[str, str]:
158
180
  """
159
181
  Return a mapping of branch name to commit hash.
160
182
  """