git-tide 0.1.5__py3-none-any.whl
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.
- git_tide-0.1.5.dist-info/METADATA +157 -0
- git_tide-0.1.5.dist-info/RECORD +9 -0
- git_tide-0.1.5.dist-info/WHEEL +4 -0
- git_tide-0.1.5.dist-info/entry_points.txt +2 -0
- tide/__init__.py +1 -0
- tide/__main__.py +4 -0
- tide/cli.py +349 -0
- tide/core.py +821 -0
- tide/gitutils.py +340 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: git-tide
|
|
3
|
+
Version: 0.1.5
|
|
4
|
+
Summary: CLI for automated gitflow-style branching
|
|
5
|
+
Project-URL: Homepage, https://github.com/chadrik/git-tide
|
|
6
|
+
Author: Chad Dombrova, Matt Collie
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Requires-Dist: click==8.1.7
|
|
9
|
+
Requires-Dist: commitizen==3.16.0
|
|
10
|
+
Requires-Dist: tomli>=2.0.1; python_version < '3.11'
|
|
11
|
+
Provides-Extra: init
|
|
12
|
+
Requires-Dist: python-gitlab==4.8.0; extra == 'init'
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Git Tide: Automated gitflow merge cycles with semantic versioning
|
|
17
|
+
|
|
18
|
+
`tide` simplifies [gitflow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow) processes with automatic tagging and hot-fixing and promotion.
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- Automatic cascading merging of hotfixes from stable branches into experimental ones, e.g. alpha, beta, rc (this is the "ebb tide")
|
|
23
|
+
- - Automated "promotion" of experimental branches forward to their next branch. e.g. alpha to beta, beta to rc, and so on. (this is the "flood tide" or "flow")
|
|
24
|
+
- Uses [`commitizen`](https://github.com/commitizen-tools/commitizen) to create semver tags
|
|
25
|
+
- Avoids merge conflicts between branches by using tags to store and query the latest version
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Example flow
|
|
29
|
+
|
|
30
|
+
Here is an overview of the rules that `tide` applies during automation:
|
|
31
|
+
|
|
32
|
+
> NOTE: this document and repository use **develop**, **staging**, and **master** for it's gitflow branches
|
|
33
|
+
> but this behavior is configurable in the **pyproject.toml** and **.gitlab-ci.yml** files.
|
|
34
|
+
|
|
35
|
+
- New features should start as merge requests made against the `develop` branch (this is the default branch)
|
|
36
|
+
- Commits that are merged to `develop` auto-generate a tag with a beta suffix: e.g. `1.0.0b1`
|
|
37
|
+
- Commits that are merged to `staging` auto-generate a tag with a release candidate suffix: e.g. `1.0.0rc1`
|
|
38
|
+
- Hotfixes that are added to `master` automatically merge to `staging`
|
|
39
|
+
- Hotfixes that are added to `staging` automatically merge to `develop`
|
|
40
|
+
- Promoting the release candidate to a new official release can be done two ways:
|
|
41
|
+
- Every pipeline on the `staging` branch has a `promotion` job that can be manually run
|
|
42
|
+
- A scheduled pipeline can be setup to do the same
|
|
43
|
+
- Either way, when the `promotion` job runs it will do 3 things:
|
|
44
|
+
- Move the `master` branch to the tip of `staging` and create a new release, stripping off the `rc` suffix
|
|
45
|
+
- Move the `staging` branch to the tip of `develop` and create a new release, converting `b` to `rc`
|
|
46
|
+
- Restart `develop`, incrementing the minor version, e.g. from `1.1.0b4` to `1.2.0b0`.
|
|
47
|
+
|
|
48
|
+
### Sample development cycle
|
|
49
|
+
|
|
50
|
+
Below is the Git commit graph demonstrating a sample development cycle using this project's branching strategy,
|
|
51
|
+
including tags and hotfix propagation.
|
|
52
|
+
|
|
53
|
+
```plaintext
|
|
54
|
+
* auto-hotfix into develop: staging: add hotfix 2 - (HEAD -> develop, tag: 1.2.0rc0, tag: 1.2.0b2, staging)
|
|
55
|
+
|\
|
|
56
|
+
| * staging: add hotfix 2 - (tag: 1.1.0rc2, tag: 1.1.0, master)
|
|
57
|
+
* | auto-hotfix into develop: master: add hotfix - (tag: 1.2.0b1)
|
|
58
|
+
|\|
|
|
59
|
+
| * auto-hotfix into staging: master: add hotfix - (tag: 1.1.0rc1)
|
|
60
|
+
| |\
|
|
61
|
+
| | * master: add hotfix - (tag: 1.0.1)
|
|
62
|
+
* | | develop: add beta feature 2 - (tag: 1.2.0b0)
|
|
63
|
+
|/ /
|
|
64
|
+
* / develop: add feature 1 - (tag: 1.1.0rc0, tag: 1.1.0b0)
|
|
65
|
+
|/
|
|
66
|
+
* master: initial state - (tag: 1.0.0)
|
|
67
|
+
|
|
68
|
+
chronological tag order:
|
|
69
|
+
1.2.0rc0 promoting develop to staging!
|
|
70
|
+
1.1.0 promoting staging to master!
|
|
71
|
+
1.2.0b2 auto-hotfix into develop: staging: add hotfix 2
|
|
72
|
+
1.1.0rc2 staging: add hotfix 2
|
|
73
|
+
1.2.0b1 auto-hotfix into develop: master: add hotfix
|
|
74
|
+
1.1.0rc1 auto-hotfix into staging: master: add hotfix
|
|
75
|
+
1.0.1 master: add hotfix
|
|
76
|
+
1.2.0b0 develop: add beta feature 2
|
|
77
|
+
1.1.0rc0 promoting develop to staging!
|
|
78
|
+
1.1.0b0 develop: add feature 1
|
|
79
|
+
1.0.0 master: initial state
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Setting up a repo
|
|
83
|
+
|
|
84
|
+
In order for `tide` to work its magic, the Gitlab repo needs to be properly configured:
|
|
85
|
+
|
|
86
|
+
- Add a `[tool].tide` section to your `pyproject.toml` file to define the gitflow branch names and prerelease version you wish to use.
|
|
87
|
+
```toml
|
|
88
|
+
[tool.tide]
|
|
89
|
+
branches.beta = "develop"
|
|
90
|
+
branches.rc = "staging"
|
|
91
|
+
branches.stable = "master"
|
|
92
|
+
```
|
|
93
|
+
- For each project within your repo that you want to manage tagged releases, add a `project` entry:
|
|
94
|
+
```toml
|
|
95
|
+
[tool.tide]
|
|
96
|
+
project = "project_name"
|
|
97
|
+
```
|
|
98
|
+
If there is only one, then combine be sure to put all of your options under a single `[tool.tide]` section.
|
|
99
|
+
- Create a [Project Access Token](https://docs.gitlab.com/ee/user/project/settings/project_access_tokens.html) with the appropriate scope for being able to push tags, changes and create cicd variables.
|
|
100
|
+
- Run `tide init --access-token='YOUR_ACCESS_TOKEN'`
|
|
101
|
+
- Copy and modify the `.gitlab-ci.yml` file ensuring the branch variables match the ones in the `pyproject.toml` (TODO: automated generation of `.gitlab-ci.yml` in `tide init`)
|
|
102
|
+
|
|
103
|
+
## Development
|
|
104
|
+
|
|
105
|
+
### Setting up your local development environment
|
|
106
|
+
|
|
107
|
+
- clone the repository using either https or ssh depending on your preference and then run the following commands:
|
|
108
|
+
```bash
|
|
109
|
+
python -m venv venv
|
|
110
|
+
.\venv\Scripts\Activate
|
|
111
|
+
pip install -r requirements.txt
|
|
112
|
+
pre-commit install
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Local development info, tips, and tricks
|
|
116
|
+
|
|
117
|
+
`nox` is the primary interface for what we'll be doing day to day and under the hood.
|
|
118
|
+
|
|
119
|
+
Use `nox --list` to see the tasks that are available.
|
|
120
|
+
|
|
121
|
+
### Running the unit tests
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
nox -s unit_tests
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The primary test `tests/test_unit.py::test_dev_cycle` can be run in several modes:
|
|
128
|
+
- local: simulated using local repos
|
|
129
|
+
- remote: integration testing using real gitlab repos
|
|
130
|
+
- gitlab-ci-local: simulated using local repos, but using the `.gitlab-ci.yml` file for entry-point and env var control. Requires installation of https://github.com/firecow/gitlab-ci-local
|
|
131
|
+
|
|
132
|
+
Here's how you run it in remote mode:
|
|
133
|
+
```bash
|
|
134
|
+
EXEC_MODE="remote" ACCESS_TOKEN="your-access-token-here" nox -s unit_tests -- tests/test_unit.py::test_dev_cycle -vv -s
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Here's how you run it using `gitlab-ci-local`:
|
|
138
|
+
```bash
|
|
139
|
+
EXEC_MODE="gitlab-ci-local" nox -s unit_tests -- tests/test_unit.py::test_dev_cycle -vv -s
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Serving Documentation on GitLab Pages
|
|
143
|
+
|
|
144
|
+
Our project leverages GitLab Pages to host and serve the documentation generated by MkDocs. This setup ensures that all team members and stakeholders have access to the most current documentation relevant to the entire project.
|
|
145
|
+
|
|
146
|
+
#### How It Works
|
|
147
|
+
|
|
148
|
+
- **Unified Documentation:** We maintain a single, comprehensive documentation system that reflects the latest changes in our codebase.
|
|
149
|
+
|
|
150
|
+
- **Automated Builds:** The documentation is automatically built and updated whenever changes are made. This ensures that our documentation is always up-to-date with the latest codebase.
|
|
151
|
+
|
|
152
|
+
#### Benefits
|
|
153
|
+
|
|
154
|
+
- **Simplified Access:** Provides a single, consistent source of documentation for all stakeholders, simplifying access and reducing potential confusion.
|
|
155
|
+
- **Immediate Updates:** Changes to the documentation are immediately reflected upon updates, ensuring that the documentation always matches the current state of the project.
|
|
156
|
+
|
|
157
|
+
This streamlined approach to documentation allows our team to efficiently maintain and update project details, ensuring consistent and accessible information for everyone involved.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
tide/__init__.py,sha256=P976fB0eaD6inWZ19CcXgKWlqOQpp_anldhYxqpJqrw,55
|
|
2
|
+
tide/__main__.py,sha256=MSmt_5Xg84uHqzTN38JwgseJK8rsJn_11A8WD99VtEo,61
|
|
3
|
+
tide/cli.py,sha256=Il3OzcMtsvAYx8YMmgMFb54wl3e6PMwzilU0SkodwUo,10182
|
|
4
|
+
tide/core.py,sha256=zG9aQ1FCPSxRiI57JeqOAnX2eRl3P9bCTT-MMoPyxVE,26494
|
|
5
|
+
tide/gitutils.py,sha256=7tRfuGRYPtDImFMUmGCIKd1BLxfpO40IMKLx-rnFhxs,9123
|
|
6
|
+
git_tide-0.1.5.dist-info/METADATA,sha256=rX8ZJ4tPqX6xqAchb7GGfC0-CHbR0x3d0mycCUV65N4,7251
|
|
7
|
+
git_tide-0.1.5.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
8
|
+
git_tide-0.1.5.dist-info/entry_points.txt,sha256=GcxqhKkWJnEGVaoKzpajCFCyeXa2Ga8da-UbInkFUJY,39
|
|
9
|
+
git_tide-0.1.5.dist-info/RECORD,,
|
tide/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function
|
tide/__main__.py
ADDED
tide/cli.py
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
from __future__ import absolute_import, print_function, annotations
|
|
2
|
+
import click
|
|
3
|
+
import time
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
import re
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from .core import (
|
|
10
|
+
is_url,
|
|
11
|
+
get_next_version,
|
|
12
|
+
get_current_version,
|
|
13
|
+
get_modified_projects,
|
|
14
|
+
get_projects,
|
|
15
|
+
load_config,
|
|
16
|
+
promote as _promote,
|
|
17
|
+
Config,
|
|
18
|
+
Runtime,
|
|
19
|
+
Backend,
|
|
20
|
+
GitlabBackend,
|
|
21
|
+
TestGitlabBackend,
|
|
22
|
+
GitlabRuntime,
|
|
23
|
+
TestGitlabRuntime,
|
|
24
|
+
LocalRuntime,
|
|
25
|
+
ENVVAR_PREFIX,
|
|
26
|
+
HOTFIX_MESSAGE,
|
|
27
|
+
)
|
|
28
|
+
from .gitutils import git, checkout
|
|
29
|
+
|
|
30
|
+
CONFIG: Config
|
|
31
|
+
CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"]}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def set_config(config: Config) -> Config:
|
|
35
|
+
"""
|
|
36
|
+
Set the global configuration object.
|
|
37
|
+
"""
|
|
38
|
+
global CONFIG
|
|
39
|
+
CONFIG = config
|
|
40
|
+
return config
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_backend(url: str | None = None) -> Backend:
|
|
44
|
+
"""
|
|
45
|
+
Return a Backend corresponding to where the current python process is pushing/pulling.
|
|
46
|
+
"""
|
|
47
|
+
if os.environ.get("GITLAB_CI", "false") == "true" or (url and "gitlab" in url):
|
|
48
|
+
return GitlabBackend(CONFIG)
|
|
49
|
+
else:
|
|
50
|
+
return TestGitlabBackend(CONFIG)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_runtime() -> Runtime:
|
|
54
|
+
"""
|
|
55
|
+
Return a Runtime corresponding to where the current python process is *running*
|
|
56
|
+
"""
|
|
57
|
+
if os.environ.get("GITLAB_CI", "false") == "true":
|
|
58
|
+
return GitlabRuntime(CONFIG)
|
|
59
|
+
# gitlab-ci-local and our unittests set this to false as an inidicator that
|
|
60
|
+
# we are testing gitlab, but not IN gitlab.
|
|
61
|
+
elif os.environ.get("GITLAB_CI") == "false":
|
|
62
|
+
return TestGitlabRuntime(CONFIG)
|
|
63
|
+
else:
|
|
64
|
+
return LocalRuntime(CONFIG)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@click.group(context_settings=CONTEXT_SETTINGS)
|
|
68
|
+
@click.option("--config", "-c", "config_path", metavar="CONFIG", type=str)
|
|
69
|
+
@click.option("--verbose", "-v", is_flag=True, default=False)
|
|
70
|
+
def cli(config_path: str, verbose: bool) -> None:
|
|
71
|
+
set_config(load_config(config_path, verbose))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@cli.command()
|
|
75
|
+
@click.option(
|
|
76
|
+
"--access-token",
|
|
77
|
+
required=True,
|
|
78
|
+
metavar="TOKEN",
|
|
79
|
+
help="Security token used to authenticate with the remote",
|
|
80
|
+
)
|
|
81
|
+
@click.option(
|
|
82
|
+
"--remote",
|
|
83
|
+
default="origin",
|
|
84
|
+
metavar="REMOTE",
|
|
85
|
+
show_default=True,
|
|
86
|
+
help=(
|
|
87
|
+
"The name of the git remote in the current git repo "
|
|
88
|
+
"(e.g. configured using `git remote`) or the remote URL. "
|
|
89
|
+
"Providing a URL implies --no-local"
|
|
90
|
+
),
|
|
91
|
+
)
|
|
92
|
+
@click.option(
|
|
93
|
+
"--save-token/--no-save-token",
|
|
94
|
+
default=True,
|
|
95
|
+
help="Whether to save the access token into the remote as a reusable "
|
|
96
|
+
"variable. If this is disabled, you must configure the ACCESS_TOKEN "
|
|
97
|
+
"manually.",
|
|
98
|
+
)
|
|
99
|
+
@click.option(
|
|
100
|
+
"--init-local/--no-local",
|
|
101
|
+
default=True,
|
|
102
|
+
help="Whether to initialize the local git repo",
|
|
103
|
+
)
|
|
104
|
+
@click.option(
|
|
105
|
+
"--init-remote/--no-remote",
|
|
106
|
+
default=True,
|
|
107
|
+
help="Whether to initialize the remote git repo (i.e. Gitlab)",
|
|
108
|
+
)
|
|
109
|
+
def init(
|
|
110
|
+
access_token: str,
|
|
111
|
+
remote: str,
|
|
112
|
+
save_token: bool,
|
|
113
|
+
init_local: bool,
|
|
114
|
+
init_remote: bool,
|
|
115
|
+
) -> None:
|
|
116
|
+
"""
|
|
117
|
+
Initialize the current git repo and its associated Gitlab project for use with tide.
|
|
118
|
+
|
|
119
|
+
This command must be run from a git repo, and the repo must have the Gitlab project setup
|
|
120
|
+
as a remote, either by being cloned from it, or via `git remote add`.
|
|
121
|
+
"""
|
|
122
|
+
import tempfile
|
|
123
|
+
|
|
124
|
+
if is_url(remote):
|
|
125
|
+
init_local = False
|
|
126
|
+
remote_url = remote
|
|
127
|
+
else:
|
|
128
|
+
# FIXME: print a user friendly error if we're not in a git repo.
|
|
129
|
+
# FIXME: handle remote not setup correctly
|
|
130
|
+
remote_url = git("remote", "get-url", remote, capture=True)
|
|
131
|
+
|
|
132
|
+
backend = get_backend(remote_url)
|
|
133
|
+
|
|
134
|
+
if init_local:
|
|
135
|
+
backend.init_local_repo(remote)
|
|
136
|
+
else:
|
|
137
|
+
# we may still need to create branches in the remote, so create a dummy clone
|
|
138
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
139
|
+
git("clone", f"--branch={CONFIG.stable}", remote_url, tmpdir)
|
|
140
|
+
pwd = os.getcwd()
|
|
141
|
+
try:
|
|
142
|
+
os.chdir(tmpdir)
|
|
143
|
+
backend.init_local_repo("origin")
|
|
144
|
+
finally:
|
|
145
|
+
os.chdir(pwd)
|
|
146
|
+
|
|
147
|
+
# FIXME: add pyproject.toml section? check if it exists? Hard to do automatically,
|
|
148
|
+
# because in a monorepo there could be many.
|
|
149
|
+
# FIXME: create a stub gitlab-ci.yml file if it doesn't exist?
|
|
150
|
+
if init_remote:
|
|
151
|
+
backend.init_remote_repo(remote_url, access_token, save_token)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@cli.command()
|
|
155
|
+
@click.option(
|
|
156
|
+
"--annotation",
|
|
157
|
+
default="automatic change detected",
|
|
158
|
+
show_default=True,
|
|
159
|
+
help="Message to pass for tag annotations.",
|
|
160
|
+
)
|
|
161
|
+
@click.option(
|
|
162
|
+
"--base-rev",
|
|
163
|
+
metavar="SHA",
|
|
164
|
+
help="The Git revision to compare against when identifying changed files.",
|
|
165
|
+
)
|
|
166
|
+
def autotag(annotation: str, base_rev: str | None) -> None:
|
|
167
|
+
"""
|
|
168
|
+
Tag the current branch with a new version number and push the tag to the remote repository.
|
|
169
|
+
"""
|
|
170
|
+
runtime = get_runtime()
|
|
171
|
+
branch = runtime.current_branch()
|
|
172
|
+
remote = runtime.get_remote()
|
|
173
|
+
|
|
174
|
+
backend = get_backend()
|
|
175
|
+
|
|
176
|
+
if not base_rev:
|
|
177
|
+
base_rev = runtime.get_base_rev()
|
|
178
|
+
|
|
179
|
+
projects = get_modified_projects(base_rev, verbose=CONFIG.verbose)
|
|
180
|
+
if projects:
|
|
181
|
+
for project_folder, project_name in projects:
|
|
182
|
+
# Auto-tag
|
|
183
|
+
tag = get_next_version(
|
|
184
|
+
CONFIG, remote, branch, project_name, project_folder, as_tag=True
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
# NOTE: this delay is necessary to create stable sorting of tags
|
|
188
|
+
# because git's time resolution is 1s (same as unix timestamp).
|
|
189
|
+
# https://stackoverflow.com/questions/28237043/what-is-the-resolution-of-gits-commit-date-or-author-date-timestamps
|
|
190
|
+
time.sleep(1.1)
|
|
191
|
+
|
|
192
|
+
click.echo(f"Creating new tag: {tag} on branch: {branch} {time.time()}")
|
|
193
|
+
git("tag", "-a", tag, "-m", annotation)
|
|
194
|
+
|
|
195
|
+
# FIXME: we may want to push all tags at once
|
|
196
|
+
click.echo(f"Pushing {tag} to remote")
|
|
197
|
+
backend.push(remote, tag)
|
|
198
|
+
else:
|
|
199
|
+
click.echo("No tags generated!", err=True)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@cli.command()
|
|
203
|
+
def hotfix() -> None:
|
|
204
|
+
"""
|
|
205
|
+
Merge hotfixes from a feature branch back to upstream branches.
|
|
206
|
+
"""
|
|
207
|
+
runtime = get_runtime()
|
|
208
|
+
branch = runtime.current_branch()
|
|
209
|
+
remote = runtime.get_remote()
|
|
210
|
+
upstream_branch = CONFIG.get_upstream_branch(branch)
|
|
211
|
+
if not upstream_branch:
|
|
212
|
+
click.echo(f"No branch upstream from {branch}. Skipping auto-merge")
|
|
213
|
+
return
|
|
214
|
+
|
|
215
|
+
# Auto-merge
|
|
216
|
+
# Record the current state
|
|
217
|
+
message = git("log", "--pretty=format: %s", "-1", capture=True)
|
|
218
|
+
# strip out previous Automerge formatting
|
|
219
|
+
match = re.match(
|
|
220
|
+
HOTFIX_MESSAGE.format(upstream_branch="[^:]+", message="(.*)$"), message
|
|
221
|
+
)
|
|
222
|
+
if match:
|
|
223
|
+
message = match.groups()[0]
|
|
224
|
+
|
|
225
|
+
git("checkout", "-B", f"{branch}_temp")
|
|
226
|
+
|
|
227
|
+
# Fetch the upstream branch
|
|
228
|
+
git("fetch", remote, upstream_branch)
|
|
229
|
+
|
|
230
|
+
checkout(remote, upstream_branch, create=True)
|
|
231
|
+
|
|
232
|
+
msg = HOTFIX_MESSAGE.format(upstream_branch=upstream_branch, message=message)
|
|
233
|
+
click.echo(msg)
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
git("merge", f"{branch}_temp", "-m", msg)
|
|
237
|
+
except subprocess.CalledProcessError:
|
|
238
|
+
click.echo("Conflicts:", err=True)
|
|
239
|
+
git("diff", "--name-only", "--diff-filter=U")
|
|
240
|
+
raise
|
|
241
|
+
|
|
242
|
+
# this will trigger a full pipeline for upstream_branch, and potentially another auto-merge
|
|
243
|
+
click.echo(f"Pushing {upstream_branch} to {remote}")
|
|
244
|
+
variables = {
|
|
245
|
+
f"{ENVVAR_PREFIX}_AUTOTAG_ANNOTATION": msg,
|
|
246
|
+
}
|
|
247
|
+
get_backend().push(remote, upstream_branch, variables=variables)
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
@cli.command()
|
|
251
|
+
def promote() -> None:
|
|
252
|
+
"""
|
|
253
|
+
Promote changes through the branch hierarchy.
|
|
254
|
+
|
|
255
|
+
e.g. from alpha -> beta -> rc -> stable.
|
|
256
|
+
"""
|
|
257
|
+
backend = get_backend()
|
|
258
|
+
runtime = get_runtime()
|
|
259
|
+
remote = runtime.get_remote()
|
|
260
|
+
if CONFIG.verbose:
|
|
261
|
+
click.echo(f"remote = {remote}")
|
|
262
|
+
_promote(CONFIG, backend, runtime)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
@cli.command
|
|
266
|
+
@click.option("--modified", "-m", is_flag=True, default=False)
|
|
267
|
+
# FIXME: add output format
|
|
268
|
+
# FIXME: add option to write to file
|
|
269
|
+
def projects(modified: bool) -> None:
|
|
270
|
+
"""
|
|
271
|
+
List project paths within the repo.
|
|
272
|
+
|
|
273
|
+
A project is a folder with a pyproject.toml file with a `tool.commitizen` section.
|
|
274
|
+
"""
|
|
275
|
+
if modified:
|
|
276
|
+
runtime = get_runtime()
|
|
277
|
+
projects_ = get_modified_projects(
|
|
278
|
+
runtime.get_base_rev(), verbose=CONFIG.verbose
|
|
279
|
+
)
|
|
280
|
+
else:
|
|
281
|
+
projects_ = get_projects()
|
|
282
|
+
|
|
283
|
+
for project_dir, project_name in projects_:
|
|
284
|
+
if project_name is None:
|
|
285
|
+
project_name = "[unset]"
|
|
286
|
+
click.echo(f"{project_name} = {project_dir}")
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@cli.command
|
|
290
|
+
@click.option(
|
|
291
|
+
"--path",
|
|
292
|
+
default=".",
|
|
293
|
+
type=click.Path(exists=True, file_okay=False),
|
|
294
|
+
show_default=True,
|
|
295
|
+
help="Folder within the repository. A pyproject.toml should reside in the "
|
|
296
|
+
"specified directory",
|
|
297
|
+
)
|
|
298
|
+
@click.option(
|
|
299
|
+
"--branch",
|
|
300
|
+
default=None,
|
|
301
|
+
help=(
|
|
302
|
+
"The release branch to use to determine the next version. "
|
|
303
|
+
"Defaults to the current branch."
|
|
304
|
+
),
|
|
305
|
+
)
|
|
306
|
+
@click.option(
|
|
307
|
+
"--remote",
|
|
308
|
+
default="origin",
|
|
309
|
+
show_default=True,
|
|
310
|
+
help="The git remote to use to when determining the next version.",
|
|
311
|
+
)
|
|
312
|
+
@click.option("--next", "-n", is_flag=True, default=False)
|
|
313
|
+
@click.option("--as-tag", "-t", is_flag=True, default=False)
|
|
314
|
+
def version(
|
|
315
|
+
path: str, branch: str | None, remote: str, next: bool, as_tag: bool
|
|
316
|
+
) -> None:
|
|
317
|
+
"""
|
|
318
|
+
Get the project version
|
|
319
|
+
"""
|
|
320
|
+
projects = dict(get_projects())
|
|
321
|
+
folder = Path(path)
|
|
322
|
+
try:
|
|
323
|
+
project_name = projects[folder]
|
|
324
|
+
except KeyError:
|
|
325
|
+
raise click.ClickException(
|
|
326
|
+
f"There is not a project at path={folder}. "
|
|
327
|
+
"Ensure there is a pyproject.toml file with a [tool.tide] section "
|
|
328
|
+
"and a `project` entry"
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
if next:
|
|
332
|
+
if branch is None:
|
|
333
|
+
runtime = get_runtime()
|
|
334
|
+
branch = runtime.current_branch()
|
|
335
|
+
|
|
336
|
+
click.echo(
|
|
337
|
+
get_next_version(CONFIG, remote, branch, project_name, folder, as_tag)
|
|
338
|
+
)
|
|
339
|
+
else:
|
|
340
|
+
click.echo(get_current_version(CONFIG, project_name, folder, as_tag))
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def main() -> None:
|
|
344
|
+
import shutil
|
|
345
|
+
|
|
346
|
+
return cli(
|
|
347
|
+
auto_envvar_prefix=ENVVAR_PREFIX,
|
|
348
|
+
max_content_width=shutil.get_terminal_size().columns,
|
|
349
|
+
)
|