Skip to content

Commit 4988ad1

Browse files
committed
Add ddev release port-commit command
1 parent f869e87 commit 4988ad1

5 files changed

Lines changed: 1054 additions & 0 deletions

File tree

ddev/changelog.d/23686.added

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add `ddev release port-commit` command to backport a commit to a target branch.

ddev/src/ddev/cli/release/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from ddev.cli.release.branch import branch
1212
from ddev.cli.release.changelog import changelog
1313
from ddev.cli.release.list_versions import list_versions
14+
from ddev.cli.release.port_commit import port_commit
1415
from ddev.cli.release.show import show
1516
from ddev.cli.release.stats import stats
1617

@@ -28,6 +29,7 @@ def release():
2829
release.add_command(changelog)
2930
release.add_command(list_versions)
3031
release.add_command(make)
32+
release.add_command(port_commit)
3133
release.add_command(show)
3234
release.add_command(stats)
3335
release.add_command(tag)
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# (C) Datadog, Inc. 2026-present
2+
# All rights reserved
3+
# Licensed under a 3-clause BSD style license (see LICENSE)
4+
from __future__ import annotations
5+
6+
from typing import TYPE_CHECKING
7+
8+
import click
9+
10+
if TYPE_CHECKING:
11+
from ddev.cli.application import Application
12+
13+
14+
@click.command(name='port-commit', short_help='Backport a commit onto a target branch')
15+
@click.pass_obj
16+
@click.argument('commit_hash', required=False)
17+
@click.option('-t', '--target-branch', default='master', show_default=True, help='Target branch to port to.')
18+
@click.option('-p', '--branch-prefix', default='port', show_default=True, help='Branch name prefix.')
19+
@click.option('-s', '--branch-suffix', default=None, help='Branch name suffix. Defaults to `to-<target-branch>`.')
20+
@click.option(
21+
'-l',
22+
'--pr-labels',
23+
default='qa/skip-qa',
24+
show_default=True,
25+
help='Comma-separated PR labels.',
26+
)
27+
@click.option('--no-pr', is_flag=True, default=False, help="Don't create a pull request.")
28+
@click.option('--draft', is_flag=True, default=False, help='Open the PR as a draft.')
29+
@click.option('--verify', is_flag=True, default=False, help='Run commit hooks (skipped by default).')
30+
@click.option('--dry-run', is_flag=True, default=False, help='Print every step instead of executing it.')
31+
def port_commit(
32+
app: Application,
33+
commit_hash: str | None,
34+
target_branch: str,
35+
branch_prefix: str,
36+
branch_suffix: str | None,
37+
pr_labels: str,
38+
no_pr: bool,
39+
draft: bool,
40+
verify: bool,
41+
dry_run: bool,
42+
) -> None:
43+
"""
44+
Backport a commit onto a target branch.
45+
46+
Cherry-picks COMMIT_HASH onto `--target-branch` (default `master`) on a new branch named
47+
`<github-user>/<prefix>-<sha[:10]>-<suffix>`, preserving `.in-toto` files from the target
48+
branch so package signatures stay intact. Pushes the branch and, unless `--no-pr` is set,
49+
opens a pull request titled `[Backport] <subject>` and labeled with `--pr-labels`.
50+
51+
If COMMIT_HASH is omitted, the current HEAD commit is used after confirmation.
52+
53+
The GitHub user for the branch prefix is taken from `ddev config` (`github.user`) or the
54+
`DD_GITHUB_USER` / `GITHUB_USER` / `GITHUB_ACTOR` environment variables.
55+
"""
56+
from ddev.cli.release.port_commit_workflow import PortStepError, build_port_steps, resolve_port_plan
57+
58+
plan = resolve_port_plan(
59+
app,
60+
commit_hash=commit_hash,
61+
target_branch=target_branch,
62+
branch_prefix=branch_prefix,
63+
branch_suffix=branch_suffix,
64+
pr_labels=pr_labels,
65+
no_pr=no_pr,
66+
draft=draft,
67+
verify=verify,
68+
dry_run=dry_run,
69+
)
70+
steps, pr_step = build_port_steps(app, plan)
71+
72+
try:
73+
for step in steps:
74+
step.run()
75+
except PortStepError as e:
76+
app.abort(str(e))
77+
78+
if pr_step is not None and not plan.dry_run:
79+
app.display_success(f'Pull request created: {pr_step.pr_url}')
80+
app.display_success('All done.')

0 commit comments

Comments
 (0)