git-epic 0.1.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.
@@ -0,0 +1,2 @@
1
+ include README.md pyproject.toml publish.sh
2
+ recursive-include tests *.py
@@ -0,0 +1,205 @@
1
+ Metadata-Version: 2.4
2
+ Name: git-epic
3
+ Version: 0.1.0
4
+ Summary: Epic-based Git workflows and GitLab merge requests
5
+ Author: Wonderteam
6
+ Project-URL: Repository, https://gitlab.wonderbeauties.com/wonderteam/git-epic
7
+ Project-URL: Issues, https://gitlab.wonderbeauties.com/wonderteam/git-epic/-/issues
8
+ Keywords: git,cli,gitlab,epic,workflow
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Topic :: Software Development :: Version Control :: Git
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+ Provides-Extra: dev
22
+ Requires-Dist: build<2,>=1.2; extra == "dev"
23
+ Requires-Dist: ruff<1,>=0.11; extra == "dev"
24
+ Requires-Dist: twine<7,>=6; extra == "dev"
25
+
26
+ # git-epic
27
+
28
+ A Python CLI for our epic-based Git workflow, with no runtime Python dependencies.
29
+ Git discovers the `git-epic`
30
+ executable automatically, so commands use `git epic …`.
31
+
32
+ ## Install
33
+
34
+ Requires Python 3.11+, Git 2.31+, and [glab](https://gitlab.com/gitlab-org/cli).
35
+ Authenticate once with your GitLab host:
36
+
37
+ ```sh
38
+ glab auth login --hostname gitlab.wonderbeauties.com
39
+ ```
40
+
41
+ After the first PyPI release, install with [pipx](https://pipx.pypa.io/):
42
+
43
+ ```sh
44
+ pipx install git-epic
45
+ pipx ensurepath
46
+ git epic -h
47
+ ```
48
+
49
+ For local development before publication:
50
+
51
+ ```sh
52
+ pipx install .
53
+ pipx ensurepath
54
+ git epic -h
55
+ ```
56
+
57
+ If `ensurepath` changes your PATH, open a new terminal. Upgrade with
58
+ `pipx upgrade git-epic`. The package is not yet published; `pipx install .` works
59
+ from a local checkout today.
60
+
61
+ ## Commands
62
+
63
+ Use `git epic -h` for CLI help. Git reserves `git epic --help` for a system manual
64
+ page; this MVP does not install one. Direct invocation also supports
65
+ `git-epic --help`.
66
+
67
+ | Command | Behavior |
68
+ | --- | --- |
69
+ | `git epic init` | Write shared `.git-epic.json`; defaults to `production`, `origin`, and GitLab. |
70
+ | `git epic start <name>` | Fetch production; create and switch to local `epic/<name>`. |
71
+ | `git epic task start <name>` | From an epic, fetch and fast-forward remote epic changes if possible; create `task/<epic>/<name>`. |
72
+ | `git epic task submit` | Push committed task changes; create or return its parent epic MR. Publish the local parent epic if it is not yet on the remote. |
73
+ | `git epic sync` | From an epic, fetch and merge production. Push the result yourself or with `release`. |
74
+ | `git epic status` | Fetch; show current task/epic, labelled local/remote epic drift, related open MRs (including task MRs when on an epic), and their latest reported CI status. |
75
+ | `git epic release` | Push committed epic changes; create or return an MR targeting production. **Never merges or deploys.** |
76
+
77
+ GitLab calls PRs **merge requests (MRs)**. Submission commands also push new commits
78
+ when returning an existing MR; they do not create a duplicate.
79
+
80
+ Names are single segments: letters, numbers, dots, underscores, and hyphens, starting
81
+ with a letter or number. Git's branch-name restrictions also apply.
82
+
83
+ For repositories using a different production branch or remote:
84
+
85
+ ```sh
86
+ git epic init --production main --remote origin
87
+ git add .git-epic.json
88
+ git commit -m "Configure git-epic"
89
+ git push
90
+ ```
91
+
92
+ Commit this file on production before creating epics so all branches and teammates
93
+ inherit it. It contains no credentials. Existing configuration is never overwritten;
94
+ edit the file to change settings. An initial production commit must already exist on
95
+ the remote; `init` does not bootstrap an empty application repository.
96
+
97
+ ## Typical workflow
98
+
99
+ ```sh
100
+ git epic start checkout
101
+ git epic task start payment
102
+ # Implement and commit your task.
103
+ git epic task submit
104
+ # Review, CI, and merge the task MR in GitLab.
105
+ git switch epic/checkout
106
+ # Bring merged tasks into the local epic.
107
+ git fetch origin
108
+ git merge --ff-only origin/epic/checkout
109
+ git epic sync
110
+ git push origin epic/checkout
111
+ # Deploy OTF and obtain QA approval through the existing team process.
112
+ git epic release
113
+ # Review, required CI, and merge in GitLab; CI/CD deploys production.
114
+ ```
115
+
116
+ OTF deployment and QA approval are external to this CLI. Approval belongs to an
117
+ exact commit/build. If the candidate or production changes, update and revalidate
118
+ before merging. `release` refuses an epic that is behind current production, but
119
+ the hosting platform/CI must also enforce release checks at merge time. The CLI
120
+ does not record or verify QA approval.
121
+
122
+ ## Git behavior
123
+
124
+ - Branch and submission operations require a clean working tree and no unfinished
125
+ merge/rebase/cherry-pick/revert. `status` allows uncommitted changes.
126
+ - Fetch updates remote-tracking refs. No command force-pushes, resets, deletes a
127
+ branch, merges an MR, or deploys.
128
+ - `start` creates a local epic. Submission publishes branches with ordinary pushes;
129
+ non-fast-forward pushes are rejected rather than overwriting teammates' work.
130
+ - `task start` stops if local and remote epic histories have diverged. Reconcile
131
+ the epic using Git before retrying. `release` similarly requires your local epic
132
+ to include remote epic work before a push can succeed.
133
+ - If `sync` conflicts, resolve and commit, or use `git merge --abort`.
134
+ - An MR/API failure after a push leaves the branch published; retry the command.
135
+ - GitLab authentication is managed by `glab`, and Git authentication by Git.
136
+
137
+ ## Development and release
138
+
139
+ ```sh
140
+ python3 -m venv .venv
141
+ source .venv/bin/activate
142
+ python -m pip install -e '.[dev]'
143
+ ruff check .
144
+ ruff format --check .
145
+ python -m unittest discover -s tests -v
146
+ python -m build
147
+ python -m twine check --strict dist/*.whl dist/*.tar.gz
148
+ ```
149
+
150
+ Tests create temporary local Git repositories and a fake `glab`; they never create
151
+ real MRs or touch production. GitLab API behavior is delegated to the installed
152
+ `glab`. CI checks Python 3.11 and 3.14, builds a wheel and source distribution,
153
+ validates metadata with Twine, and smoke-tests the installed wheel via Git.
154
+
155
+ ## Publish to PyPI
156
+
157
+ ### Local publication helper
158
+
159
+ From a clean, committed checkout with a configured Git upstream, run:
160
+
161
+ ```sh
162
+ ./publish.sh
163
+ ```
164
+
165
+ The helper uses `uv` to prompt for a PEP 440 version (press Enter to keep the
166
+ current version for the first release), update the version and README release-tag
167
+ examples, run tests, clean and build distributions, validate with Twine, upload
168
+ to PyPI, and commit/push the release changes. Set `PYPI_API_TOKEN` or
169
+ `TWINE_PASSWORD` in your shell; otherwise Twine prompts for authentication.
170
+ It does not create a release tag or trigger the CI publication job.
171
+
172
+ ### GitLab CI publication
173
+
174
+ The distribution name is **`git-epic`**, import package is **`git_epic`**, and CLI
175
+ entry point is **`git-epic`**. Version is defined once in
176
+ `src/git_epic/__init__.py`.
177
+
178
+ 1. Create a PyPI account and an API token. For the first release, a token with
179
+ account scope is needed; after creating the project, use a project-scoped token.
180
+ 2. In GitLab CI/CD variables, add **`PYPI_API_TOKEN`** as a masked, protected
181
+ variable. Protect the release tag pattern (`v*`) so tagged jobs can access it.
182
+ 3. Update the version, then push a matching tag, for example `v0.1.0`.
183
+ 4. After verification and build jobs pass, manually run **`publish:pypi`**.
184
+
185
+ The job uploads only the validated wheel and source distribution from the build
186
+ job. A release tag must match the package version. PyPI does not allow reusing
187
+ an uploaded version: use a new version for subsequent releases.
188
+
189
+ An optional **`publish:testpypi`** job uses **`TESTPYPI_API_TOKEN`** and the separate
190
+ TestPyPI registry. TestPyPI accounts and tokens are independent of PyPI. A test
191
+ installation uses `pipx install --index-url https://test.pypi.org/simple/ git-epic`.
192
+
193
+ For a manual upload of locally built, validated distributions:
194
+
195
+ ```sh
196
+ export TWINE_USERNAME=__token__
197
+ # Set TWINE_PASSWORD to your PyPI API token in your shell.
198
+ python -m twine upload --non-interactive dist/*.whl dist/*.tar.gz
199
+ ```
200
+
201
+ Credentials are supplied by the shell or CI, never stored in project files. PyPI
202
+ Trusted Publishing currently supports GitLab.com, not this self-managed GitLab
203
+ host, so the release jobs use API tokens. Package-name availability is determined
204
+ by PyPI at upload time. A public PyPI release includes the source code even when
205
+ the GitLab repository is private.
@@ -0,0 +1,180 @@
1
+ # git-epic
2
+
3
+ A Python CLI for our epic-based Git workflow, with no runtime Python dependencies.
4
+ Git discovers the `git-epic`
5
+ executable automatically, so commands use `git epic …`.
6
+
7
+ ## Install
8
+
9
+ Requires Python 3.11+, Git 2.31+, and [glab](https://gitlab.com/gitlab-org/cli).
10
+ Authenticate once with your GitLab host:
11
+
12
+ ```sh
13
+ glab auth login --hostname gitlab.wonderbeauties.com
14
+ ```
15
+
16
+ After the first PyPI release, install with [pipx](https://pipx.pypa.io/):
17
+
18
+ ```sh
19
+ pipx install git-epic
20
+ pipx ensurepath
21
+ git epic -h
22
+ ```
23
+
24
+ For local development before publication:
25
+
26
+ ```sh
27
+ pipx install .
28
+ pipx ensurepath
29
+ git epic -h
30
+ ```
31
+
32
+ If `ensurepath` changes your PATH, open a new terminal. Upgrade with
33
+ `pipx upgrade git-epic`. The package is not yet published; `pipx install .` works
34
+ from a local checkout today.
35
+
36
+ ## Commands
37
+
38
+ Use `git epic -h` for CLI help. Git reserves `git epic --help` for a system manual
39
+ page; this MVP does not install one. Direct invocation also supports
40
+ `git-epic --help`.
41
+
42
+ | Command | Behavior |
43
+ | --- | --- |
44
+ | `git epic init` | Write shared `.git-epic.json`; defaults to `production`, `origin`, and GitLab. |
45
+ | `git epic start <name>` | Fetch production; create and switch to local `epic/<name>`. |
46
+ | `git epic task start <name>` | From an epic, fetch and fast-forward remote epic changes if possible; create `task/<epic>/<name>`. |
47
+ | `git epic task submit` | Push committed task changes; create or return its parent epic MR. Publish the local parent epic if it is not yet on the remote. |
48
+ | `git epic sync` | From an epic, fetch and merge production. Push the result yourself or with `release`. |
49
+ | `git epic status` | Fetch; show current task/epic, labelled local/remote epic drift, related open MRs (including task MRs when on an epic), and their latest reported CI status. |
50
+ | `git epic release` | Push committed epic changes; create or return an MR targeting production. **Never merges or deploys.** |
51
+
52
+ GitLab calls PRs **merge requests (MRs)**. Submission commands also push new commits
53
+ when returning an existing MR; they do not create a duplicate.
54
+
55
+ Names are single segments: letters, numbers, dots, underscores, and hyphens, starting
56
+ with a letter or number. Git's branch-name restrictions also apply.
57
+
58
+ For repositories using a different production branch or remote:
59
+
60
+ ```sh
61
+ git epic init --production main --remote origin
62
+ git add .git-epic.json
63
+ git commit -m "Configure git-epic"
64
+ git push
65
+ ```
66
+
67
+ Commit this file on production before creating epics so all branches and teammates
68
+ inherit it. It contains no credentials. Existing configuration is never overwritten;
69
+ edit the file to change settings. An initial production commit must already exist on
70
+ the remote; `init` does not bootstrap an empty application repository.
71
+
72
+ ## Typical workflow
73
+
74
+ ```sh
75
+ git epic start checkout
76
+ git epic task start payment
77
+ # Implement and commit your task.
78
+ git epic task submit
79
+ # Review, CI, and merge the task MR in GitLab.
80
+ git switch epic/checkout
81
+ # Bring merged tasks into the local epic.
82
+ git fetch origin
83
+ git merge --ff-only origin/epic/checkout
84
+ git epic sync
85
+ git push origin epic/checkout
86
+ # Deploy OTF and obtain QA approval through the existing team process.
87
+ git epic release
88
+ # Review, required CI, and merge in GitLab; CI/CD deploys production.
89
+ ```
90
+
91
+ OTF deployment and QA approval are external to this CLI. Approval belongs to an
92
+ exact commit/build. If the candidate or production changes, update and revalidate
93
+ before merging. `release` refuses an epic that is behind current production, but
94
+ the hosting platform/CI must also enforce release checks at merge time. The CLI
95
+ does not record or verify QA approval.
96
+
97
+ ## Git behavior
98
+
99
+ - Branch and submission operations require a clean working tree and no unfinished
100
+ merge/rebase/cherry-pick/revert. `status` allows uncommitted changes.
101
+ - Fetch updates remote-tracking refs. No command force-pushes, resets, deletes a
102
+ branch, merges an MR, or deploys.
103
+ - `start` creates a local epic. Submission publishes branches with ordinary pushes;
104
+ non-fast-forward pushes are rejected rather than overwriting teammates' work.
105
+ - `task start` stops if local and remote epic histories have diverged. Reconcile
106
+ the epic using Git before retrying. `release` similarly requires your local epic
107
+ to include remote epic work before a push can succeed.
108
+ - If `sync` conflicts, resolve and commit, or use `git merge --abort`.
109
+ - An MR/API failure after a push leaves the branch published; retry the command.
110
+ - GitLab authentication is managed by `glab`, and Git authentication by Git.
111
+
112
+ ## Development and release
113
+
114
+ ```sh
115
+ python3 -m venv .venv
116
+ source .venv/bin/activate
117
+ python -m pip install -e '.[dev]'
118
+ ruff check .
119
+ ruff format --check .
120
+ python -m unittest discover -s tests -v
121
+ python -m build
122
+ python -m twine check --strict dist/*.whl dist/*.tar.gz
123
+ ```
124
+
125
+ Tests create temporary local Git repositories and a fake `glab`; they never create
126
+ real MRs or touch production. GitLab API behavior is delegated to the installed
127
+ `glab`. CI checks Python 3.11 and 3.14, builds a wheel and source distribution,
128
+ validates metadata with Twine, and smoke-tests the installed wheel via Git.
129
+
130
+ ## Publish to PyPI
131
+
132
+ ### Local publication helper
133
+
134
+ From a clean, committed checkout with a configured Git upstream, run:
135
+
136
+ ```sh
137
+ ./publish.sh
138
+ ```
139
+
140
+ The helper uses `uv` to prompt for a PEP 440 version (press Enter to keep the
141
+ current version for the first release), update the version and README release-tag
142
+ examples, run tests, clean and build distributions, validate with Twine, upload
143
+ to PyPI, and commit/push the release changes. Set `PYPI_API_TOKEN` or
144
+ `TWINE_PASSWORD` in your shell; otherwise Twine prompts for authentication.
145
+ It does not create a release tag or trigger the CI publication job.
146
+
147
+ ### GitLab CI publication
148
+
149
+ The distribution name is **`git-epic`**, import package is **`git_epic`**, and CLI
150
+ entry point is **`git-epic`**. Version is defined once in
151
+ `src/git_epic/__init__.py`.
152
+
153
+ 1. Create a PyPI account and an API token. For the first release, a token with
154
+ account scope is needed; after creating the project, use a project-scoped token.
155
+ 2. In GitLab CI/CD variables, add **`PYPI_API_TOKEN`** as a masked, protected
156
+ variable. Protect the release tag pattern (`v*`) so tagged jobs can access it.
157
+ 3. Update the version, then push a matching tag, for example `v0.1.0`.
158
+ 4. After verification and build jobs pass, manually run **`publish:pypi`**.
159
+
160
+ The job uploads only the validated wheel and source distribution from the build
161
+ job. A release tag must match the package version. PyPI does not allow reusing
162
+ an uploaded version: use a new version for subsequent releases.
163
+
164
+ An optional **`publish:testpypi`** job uses **`TESTPYPI_API_TOKEN`** and the separate
165
+ TestPyPI registry. TestPyPI accounts and tokens are independent of PyPI. A test
166
+ installation uses `pipx install --index-url https://test.pypi.org/simple/ git-epic`.
167
+
168
+ For a manual upload of locally built, validated distributions:
169
+
170
+ ```sh
171
+ export TWINE_USERNAME=__token__
172
+ # Set TWINE_PASSWORD to your PyPI API token in your shell.
173
+ python -m twine upload --non-interactive dist/*.whl dist/*.tar.gz
174
+ ```
175
+
176
+ Credentials are supplied by the shell or CI, never stored in project files. PyPI
177
+ Trusted Publishing currently supports GitLab.com, not this self-managed GitLab
178
+ host, so the release jobs use API tokens. Package-name availability is determined
179
+ by PyPI at upload time. A public PyPI release includes the source code even when
180
+ the GitLab repository is private.
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ cd -- "$(dirname -- "${BASH_SOURCE[0]}")"
5
+
6
+ printf '\nGit Epic — PyPI Publication Helper\n----------------------------------\n'
7
+
8
+ if ! command -v uv >/dev/null 2>&1; then
9
+ printf 'Error: Install uv before running this script.\n' >&2
10
+ exit 1
11
+ fi
12
+
13
+ if ! git rev-parse --verify HEAD >/dev/null 2>&1; then
14
+ printf 'Error: Commit and push the initial project before publishing a release.\n' >&2
15
+ exit 1
16
+ fi
17
+
18
+ if [[ -n "$(git status --porcelain --untracked-files=all --ignore-submodules=none)" ]]; then
19
+ printf 'Error: Commit or stash your changes before publishing.\n' >&2
20
+ exit 1
21
+ fi
22
+
23
+ if ! git symbolic-ref --quiet HEAD >/dev/null ||
24
+ ! git rev-parse --verify '@{upstream}' >/dev/null 2>&1; then
25
+ printf 'Error: Check out a branch with a configured upstream before publishing.\n' >&2
26
+ exit 1
27
+ fi
28
+
29
+ # uv manages the interpreter and build tools without a global pip installation.
30
+ release_python() {
31
+ uv run --no-project --python 3.11 \
32
+ --with 'build>=1.2,<2' \
33
+ --with 'twine>=6,<7' \
34
+ --with 'packaging>=24' \
35
+ python "$@"
36
+ }
37
+
38
+ CURRENT_VERSION=$(release_python - <<'PY'
39
+ import ast
40
+ from pathlib import Path
41
+
42
+ tree = ast.parse(Path("src/git_epic/__init__.py").read_text(encoding="utf-8"))
43
+ for node in tree.body:
44
+ if isinstance(node, ast.Assign) and any(
45
+ isinstance(target, ast.Name) and target.id == "__version__" for target in node.targets
46
+ ):
47
+ print(ast.literal_eval(node.value))
48
+ break
49
+ else:
50
+ raise SystemExit("Error: Could not find __version__.")
51
+ PY
52
+ )
53
+
54
+ printf 'Current version: %s\n' "$CURRENT_VERSION"
55
+ read -r -p "Enter release version [$CURRENT_VERSION]: " NEW_VERSION
56
+ NEW_VERSION=${NEW_VERSION:-$CURRENT_VERSION}
57
+
58
+ NEW_VERSION=$(release_python - "$CURRENT_VERSION" "$NEW_VERSION" <<'PY'
59
+ import sys
60
+ from packaging.version import InvalidVersion, Version
61
+
62
+ try:
63
+ current, new = map(Version, sys.argv[1:])
64
+ except InvalidVersion as error:
65
+ raise SystemExit(f"Error: {error}") from error
66
+ if new < current:
67
+ raise SystemExit("Error: Release version cannot be older than the current version.")
68
+ if new.local is not None:
69
+ raise SystemExit("Error: PyPI does not accept local version identifiers.")
70
+ print(new)
71
+ PY
72
+ )
73
+
74
+ printf 'Updating version to %s...\n' "$NEW_VERSION"
75
+ release_python - "$CURRENT_VERSION" "$NEW_VERSION" <<'PY'
76
+ import re
77
+ import sys
78
+ from pathlib import Path
79
+
80
+ current, new = sys.argv[1:]
81
+ path = Path("src/git_epic/__init__.py")
82
+ text, count = re.subn(
83
+ r'^__version__ = "[^"]+"$', f'__version__ = "{new}"',
84
+ path.read_text(encoding="utf-8"), flags=re.MULTILINE,
85
+ )
86
+ if count != 1:
87
+ raise SystemExit("Error: Expected exactly one __version__ assignment.")
88
+ path.write_text(text, encoding="utf-8")
89
+ # Update release-tag examples only, leaving unrelated numbers intact.
90
+ readme = Path("README.md")
91
+ readme.write_text(
92
+ readme.read_text(encoding="utf-8").replace(f"`v{current}`", f"`v{new}`"),
93
+ encoding="utf-8",
94
+ )
95
+ PY
96
+
97
+ printf 'Running tests...\n'
98
+ PYTHONPATH="$PWD/src" release_python -m unittest discover -s tests -v
99
+
100
+ printf 'Cleaning old builds...\n'
101
+ release_python - <<'PY'
102
+ import shutil
103
+ from pathlib import Path
104
+
105
+ for path in (Path("dist"), Path("build"), Path("src/git_epic.egg-info")):
106
+ if path.exists():
107
+ shutil.rmtree(path)
108
+ PY
109
+
110
+ printf 'Building and validating package...\n'
111
+ release_python -m build --installer uv
112
+ release_python -m twine check --strict dist/*.whl dist/*.tar.gz
113
+
114
+ printf 'Uploading to PyPI...\n'
115
+ export TWINE_USERNAME="${TWINE_USERNAME:-__token__}"
116
+ if [[ -z "${TWINE_PASSWORD:-}" && -n "${PYPI_API_TOKEN:-}" ]]; then
117
+ export TWINE_PASSWORD="$PYPI_API_TOKEN"
118
+ fi
119
+ release_python -m twine upload --repository-url https://upload.pypi.org/legacy/ \
120
+ dist/*.whl dist/*.tar.gz
121
+
122
+ printf 'Committing and pushing release changes...\n'
123
+ git add -- src/git_epic/__init__.py README.md
124
+ if ! git diff --cached --quiet; then
125
+ git commit -m "Release v${NEW_VERSION}"
126
+ fi
127
+ git push
128
+
129
+ printf '\nSuccessfully published and pushed git-epic %s!\n' "$NEW_VERSION"
@@ -0,0 +1,48 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "git-epic"
7
+ dynamic = ["version"]
8
+ description = "Epic-based Git workflows and GitLab merge requests"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ dependencies = []
12
+ authors = [{name = "Wonderteam"}]
13
+ keywords = ["git", "cli", "gitlab", "epic", "workflow"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Environment :: Console",
17
+ "Intended Audience :: Developers",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3 :: Only",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Programming Language :: Python :: 3.14",
24
+ "Topic :: Software Development :: Version Control :: Git",
25
+ ]
26
+
27
+ [project.scripts]
28
+ git-epic = "git_epic.cli:main"
29
+
30
+ [project.optional-dependencies]
31
+ dev = ["build>=1.2,<2", "ruff>=0.11,<1", "twine>=6,<7"]
32
+
33
+ [project.urls]
34
+ Repository = "https://gitlab.wonderbeauties.com/wonderteam/git-epic"
35
+ Issues = "https://gitlab.wonderbeauties.com/wonderteam/git-epic/-/issues"
36
+
37
+ [tool.setuptools.dynamic]
38
+ version = {attr = "git_epic.__version__"}
39
+
40
+ [tool.setuptools.packages.find]
41
+ where = ["src"]
42
+
43
+ [tool.ruff]
44
+ target-version = "py311"
45
+ line-length = 100
46
+
47
+ [tool.ruff.lint]
48
+ select = ["E", "F", "I", "UP", "B"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """Epic-based Git workflows."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())
@@ -0,0 +1,56 @@
1
+ """Command-line entry point, available as git-epic and git epic."""
2
+
3
+ import argparse
4
+ import sys
5
+
6
+ from . import __version__, workflow
7
+ from .process import EpicError
8
+
9
+
10
+ def parser() -> argparse.ArgumentParser:
11
+ root = argparse.ArgumentParser(
12
+ prog="git epic", description="Epic-based Git workflows and GitLab merge requests"
13
+ )
14
+ root.add_argument("--version", "-V", action="version", version=__version__)
15
+ commands = root.add_subparsers(dest="command", required=True)
16
+ init = commands.add_parser("init", help="Configure this repository")
17
+ init.add_argument("--production", default="production", metavar="BRANCH")
18
+ init.add_argument("--remote", default="origin")
19
+ start = commands.add_parser("start", help="Create an epic from latest production")
20
+ start.add_argument("name")
21
+ task = commands.add_parser("task", help="Work on tasks within an epic")
22
+ tasks = task.add_subparsers(dest="task_command", required=True)
23
+ task_start = tasks.add_parser("start", help="Create a task from the current epic")
24
+ task_start.add_argument("name")
25
+ tasks.add_parser("submit", help="Push the task and create or return its epic MR")
26
+ commands.add_parser("sync", help="Merge latest production into the current epic")
27
+ commands.add_parser("status", help="Show branch context, production drift, MRs, and CI")
28
+ commands.add_parser(
29
+ "release",
30
+ help="Push the epic and create or return its production MR; never merge or deploy",
31
+ )
32
+ return root
33
+
34
+
35
+ def main(argv: list[str] | None = None) -> int:
36
+ args = parser().parse_args(argv)
37
+ try:
38
+ if args.command == "init":
39
+ workflow.init(args.production, args.remote)
40
+ elif args.command == "start":
41
+ workflow.start(args.name)
42
+ elif args.command == "task":
43
+ if args.task_command == "start":
44
+ workflow.task_start(args.name)
45
+ else:
46
+ workflow.task_submit()
47
+ else:
48
+ {"sync": workflow.sync, "status": workflow.status, "release": workflow.release}[
49
+ args.command
50
+ ]()
51
+ except (EpicError, OSError) as error:
52
+ print(f"git-epic: {error}", file=sys.stderr)
53
+ return 1
54
+ except KeyboardInterrupt:
55
+ return 130
56
+ return 0