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.
- giterator-0.4.0/.github/workflows/ci.yml +15 -0
- giterator-0.4.0/.github/workflows/release.yml +46 -0
- giterator-0.4.0/.gitignore +10 -0
- giterator-0.4.0/.python-version +1 -0
- giterator-0.4.0/.readthedocs.yml +10 -0
- giterator-0.4.0/AGENTS.md +59 -0
- giterator-0.4.0/CLAUDE.md +1 -0
- giterator-0.4.0/PKG-INFO +29 -0
- {giterator-0.3.0 → giterator-0.4.0}/README.rst +3 -4
- giterator-0.4.0/docs/Makefile +20 -0
- giterator-0.4.0/docs/changes.rst +39 -0
- giterator-0.4.0/docs/conf.py +25 -0
- giterator-0.4.0/docs/development.rst +58 -0
- giterator-0.4.0/happy.sh +19 -0
- giterator-0.4.0/pyproject.toml +68 -0
- {giterator-0.3.0 → giterator-0.4.0/src}/giterator/__main__.py +1 -0
- {giterator-0.3.0 → giterator-0.4.0/src}/giterator/cli.py +3 -4
- {giterator-0.3.0 → giterator-0.4.0/src}/giterator/clock.py +2 -1
- {giterator-0.3.0 → giterator-0.4.0/src}/giterator/git.py +46 -24
- giterator-0.4.0/src/giterator/testing.py +77 -0
- giterator-0.4.0/src/giterator/typing.py +3 -0
- giterator-0.4.0/tests/__init__.py +0 -0
- {giterator-0.3.0 → giterator-0.4.0}/tests/conftest.py +7 -4
- {giterator-0.3.0 → giterator-0.4.0}/tests/test_cli.py +18 -10
- giterator-0.4.0/tests/test_clock.py +17 -0
- giterator-0.4.0/tests/test_git.py +212 -0
- giterator-0.4.0/tests/test_testing.py +115 -0
- giterator-0.3.0/.carthorse.yml +0 -9
- giterator-0.3.0/.circleci/config.yml +0 -40
- giterator-0.3.0/.coveragerc +0 -3
- giterator-0.3.0/.gitignore +0 -8
- giterator-0.3.0/.readthedocs.yml +0 -16
- giterator-0.3.0/PKG-INFO +0 -48
- giterator-0.3.0/docs/Makefile +0 -78
- giterator-0.3.0/docs/changes.rst +0 -22
- giterator-0.3.0/docs/conf.py +0 -38
- giterator-0.3.0/docs/development.rst +0 -66
- giterator-0.3.0/giterator/testing.py +0 -58
- giterator-0.3.0/giterator/typing.py +0 -4
- giterator-0.3.0/giterator.egg-info/PKG-INFO +0 -48
- giterator-0.3.0/giterator.egg-info/SOURCES.txt +0 -35
- giterator-0.3.0/giterator.egg-info/dependency_links.txt +0 -1
- giterator-0.3.0/giterator.egg-info/entry_points.txt +0 -2
- giterator-0.3.0/giterator.egg-info/not-zip-safe +0 -1
- giterator-0.3.0/giterator.egg-info/requires.txt +0 -14
- giterator-0.3.0/giterator.egg-info/top_level.txt +0 -1
- giterator-0.3.0/setup.cfg +0 -4
- giterator-0.3.0/setup.py +0 -42
- giterator-0.3.0/tests/test_git.py +0 -193
- giterator-0.3.0/tests/test_testing.py +0 -65
- {giterator-0.3.0 → giterator-0.4.0}/LICENSE.rst +0 -0
- {giterator-0.3.0 → giterator-0.4.0}/docs/api.rst +0 -0
- {giterator-0.3.0 → giterator-0.4.0}/docs/index.rst +0 -0
- {giterator-0.3.0 → giterator-0.4.0}/docs/license.rst +0 -0
- {giterator-0.3.0 → giterator-0.4.0}/docs/use.rst +0 -0
- {giterator-0.3.0 → giterator-0.4.0/src}/giterator/__init__.py +0 -0
- /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 @@
|
|
|
1
|
+
3.11
|
|
@@ -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
|
giterator-0.4.0/PKG-INFO
ADDED
|
@@ -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
|
-
|
|
|
4
|
+
|CI|_ |Docs|_
|
|
6
5
|
|
|
7
|
-
.. |
|
|
8
|
-
..
|
|
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.
|
giterator-0.4.0/happy.sh
ADDED
|
@@ -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
|
+
]
|
|
@@ -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
|
|
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:
|
|
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__(
|
|
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:
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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) ->
|
|
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) ->
|
|
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) ->
|
|
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) ->
|
|
179
|
+
def branch_hashes(self) -> dict[str, str]:
|
|
158
180
|
"""
|
|
159
181
|
Return a mapping of branch name to commit hash.
|
|
160
182
|
"""
|