giterator 0.2.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.2.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.2.0 → giterator-0.4.0/src}/giterator/__main__.py +1 -0
  17. {giterator-0.2.0 → giterator-0.4.0/src}/giterator/cli.py +3 -4
  18. {giterator-0.2.0 → giterator-0.4.0/src}/giterator/clock.py +2 -1
  19. {giterator-0.2.0 → giterator-0.4.0/src}/giterator/git.py +56 -23
  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.2.0 → giterator-0.4.0}/tests/conftest.py +11 -3
  24. {giterator-0.2.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.2.0/.carthorse.yml +0 -9
  29. giterator-0.2.0/.circleci/config.yml +0 -40
  30. giterator-0.2.0/.coveragerc +0 -2
  31. giterator-0.2.0/.gitignore +0 -8
  32. giterator-0.2.0/.readthedocs.yml +0 -8
  33. giterator-0.2.0/PKG-INFO +0 -32
  34. giterator-0.2.0/docs/Makefile +0 -78
  35. giterator-0.2.0/docs/changes.rst +0 -14
  36. giterator-0.2.0/docs/conf.py +0 -38
  37. giterator-0.2.0/docs/development.rst +0 -66
  38. giterator-0.2.0/giterator/testing.py +0 -51
  39. giterator-0.2.0/giterator/typing.py +0 -4
  40. giterator-0.2.0/giterator.egg-info/PKG-INFO +0 -32
  41. giterator-0.2.0/giterator.egg-info/SOURCES.txt +0 -35
  42. giterator-0.2.0/giterator.egg-info/dependency_links.txt +0 -1
  43. giterator-0.2.0/giterator.egg-info/entry_points.txt +0 -3
  44. giterator-0.2.0/giterator.egg-info/not-zip-safe +0 -1
  45. giterator-0.2.0/giterator.egg-info/requires.txt +0 -13
  46. giterator-0.2.0/giterator.egg-info/top_level.txt +0 -1
  47. giterator-0.2.0/setup.cfg +0 -4
  48. giterator-0.2.0/setup.py +0 -42
  49. giterator-0.2.0/tests/test_git.py +0 -188
  50. giterator-0.2.0/tests/test_testing.py +0 -58
  51. {giterator-0.2.0 → giterator-0.4.0}/LICENSE.rst +0 -0
  52. {giterator-0.2.0 → giterator-0.4.0}/docs/api.rst +0 -0
  53. {giterator-0.2.0 → giterator-0.4.0}/docs/index.rst +0 -0
  54. {giterator-0.2.0 → giterator-0.4.0}/docs/license.rst +0 -0
  55. {giterator-0.2.0 → giterator-0.4.0}/docs/use.rst +0 -0
  56. {giterator-0.2.0 → giterator-0.4.0/src}/giterator/__init__.py +0 -0
  57. /giterator-0.2.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,29 +111,40 @@ 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
- def commit(self, msg: str, author_date: Date = None, commit_date: Date = None) -> str:
117
+ def commit(
118
+ self,
119
+ msg: str,
120
+ author_date: Date | None = None,
121
+ commit_date: Date | None = None,
122
+ short: bool = True,
123
+ ) -> str:
96
124
  """
97
125
  Commit changes in this repo, including and new or deleted files.
98
126
 
99
127
  :param msg: The commit message.
100
128
  :param author_date: The author date.
101
129
  :param commit_date: The commit date. Defaults to author date if not specified.
130
+ :param short: Return the short commit hash instead of the full 40-character hash.
102
131
  """
103
132
  self('add', '.')
104
133
  command = ['commit', '-m', msg]
105
134
  if author_date:
106
135
  command.extend(['--date', self._coerce_date(author_date)])
107
- env = {}
136
+ env: dict[str, str] = {}
108
137
  if commit_date:
109
138
  env['GIT_COMMITTER_DATE'] = self._coerce_date(commit_date)
110
139
  self(*command, env=env)
111
- return self.rev_parse('HEAD')
140
+ return self.rev_parse('HEAD', short)
112
141
 
113
- def rev_parse(self, label: str):
114
- return self('rev-parse', '--verify', '-q', '--short', label).strip()
142
+ def rev_parse(self, label: str, short: bool = True) -> str:
143
+ command = ['rev-parse', '--verify', '-q']
144
+ if short:
145
+ command.append('--short')
146
+ command.append(label)
147
+ return self(*command).strip()
115
148
 
116
149
  def tag(self, name: str) -> None:
117
150
  """
@@ -119,13 +152,13 @@ class Git:
119
152
  """
120
153
  self('tag', name)
121
154
 
122
- def tags(self) -> List[str]:
155
+ def tags(self) -> list[str]:
123
156
  """
124
157
  Return a list of tags in this repo.
125
158
  """
126
159
  return self('tag').split()
127
160
 
128
- def tag_hashes(self) -> Dict[str, str]:
161
+ def tag_hashes(self) -> dict[str, str]:
129
162
  """
130
163
  Return a mapping of tag name to commit hash.
131
164
  """
@@ -137,13 +170,13 @@ class Git:
137
170
  """
138
171
  self('checkout', '-b', name)
139
172
 
140
- def branches(self) -> List[str]:
173
+ def branches(self) -> list[str]:
141
174
  """
142
175
  Return a list of branches in this repo.
143
176
  """
144
177
  return self('for-each-ref', '--format', '%(refname:short)', 'refs/heads/').split()
145
178
 
146
- def branch_hashes(self) -> Dict[str, str]:
179
+ def branch_hashes(self) -> dict[str, str]:
147
180
  """
148
181
  Return a mapping of branch name to commit hash.
149
182
  """