Skip to content

Commit db5fc86

Browse files
committed
Fetch commit from origin when not in local repo for ddev port-commit (DataDog#23703)
* Resolve ddev port-commit input by fetching from origin when not local When the commit passed to `ddev release port-commit` is not in the local object database, fall back to `git fetch origin <sha>` before aborting. GitHub allows fetching any reachable commit by SHA, so this lets us port commits that live on branches the local `remote.origin.fetch` refspec does not track (a common configuration in this repo). The fetch is precise — we never need to know which branch the commit is on, since cherry-pick only needs the commit object. * Add changelog entry for DataDog#23703 * Abort early on abbreviated SHAs and clarify test names Address review feedback: - Abbreviated SHAs cannot be fetched from GitHub via uploadpack (allowReachableSHA1InWant only honours full 40-char SHAs), so detect the abbreviated-hex case before attempting the fetch and abort with a clear "pass the full 40-character SHA" message. Avoids the confusing `fatal: couldn't find remote ref` stderr from git on a doomed fetch. - Rename `test_command_aborts_when_commit_does_not_exist` to `test_command_aborts_when_commit_missing_after_fetch`. The original name suggested it covered the "commit truly missing" case, but with the mock setup it actually exercises the edge where the fetch succeeds but rev-parse still fails. The new name reflects that. - Add `test_command_aborts_on_abbreviated_sha_not_local` for the new early-abort path. - Use a full 40-char SHA constant for the fetch-fallback tests so they reach the fetch path under the new early-abort check. * Use contextlib.suppress instead of except: pass * Annotate Application.abort as NoReturn The original `app.abort` calls `ctx.exit` (which raises `SystemExit`), so the method never returns. Mark it with `-> NoReturn` so callers don't need a trailing `raise AssertionError('unreachable')` to satisfy mypy when the abort is the last statement on a code path. Also add an explicit `raise SystemExit(code)` so mypy can see the no-return at the implementation level (the `__exit_func` field is untyped) and as a safety net if a buggy `exit_func` is ever passed in. Removes the two `raise AssertionError('unreachable')` lines from `_resolve_commit_or_fetch` that were only there for type checking. * Type Application.exit_func and abort so callers don't need a trailing raise Annotate the `exit_func` callback as `Callable[[int], NoReturn]` (matching click's `Context.exit`) and mark `Application.abort` as `-> NoReturn`. Mypy now infers that calling `self.__exit_func(code)` doesn't return, so the abort path is correctly recognised as terminal without needing an unreachable `raise SystemExit(code)` inside the method body. Typing the `__init__` body surfaced two pre-existing issues: - `self.__config = {}` needed an explicit `dict[str, Any]` annotation. - `serve_openmetrics_payload.py` was constructing `DockerAgent` with `app.platform` (a `Platform`) where it expects an `Application`. This has been broken since 2024 — `AgentInterface.platform` accesses `self.app.platform`, which would raise `AttributeError` on a Platform instance at runtime. Pass `app` directly. Typing-only imports (`Any`, `Callable`, `NoReturn`) live behind `TYPE_CHECKING` since the file already uses `from __future__ import annotations`. * Skip fetch fallback in --dry-run mode Preserves the dry-run contract: when the commit is not in the local object database, abort with a clear message instead of running `git fetch origin <sha>` during plan resolution. Tells the user to re-run without --dry-run or pre-fetch the commit manually. Adds `test_command_aborts_in_dry_run_when_commit_not_local` covering the new path and asserting no fetch is attempted. * Add PR-or-commit input support to ddev port-commit The command now accepts a PR number, an explicit `PR-<number>` token, or a GitHub PR URL in addition to a commit SHA. Detection is tiered: - `PR-<digits>` or a PR URL is treated as an explicit PR reference. - Pure-digit inputs are tried as a PR first when a GitHub token is configured; a 404 falls back to commit resolution. - Anything else goes straight to commit resolution. When the input resolves to a PR, the command verifies the PR is merged and that its merge commit has a single parent (i.e. the PR was squash-merged); anything else aborts with a clear message instructing the user to pass the specific commit they want to backport. Refactors `_resolve_commit_or_fetch` to raise `_CommitNotResolvable` instead of aborting, so the new orchestrator can wrap the error with a unified "PR or commit" message when pure-digit input also fails commit resolution. The single abort happens at the call site in `resolve_port_plan`. Adds: - `AsyncGitHubClient.get_pull_request` for the GitHub lookup. - `FakeAsyncGitHubClient.get_pull_request` plus a built-in 404 default so tests that don't care about PR lookup auto-fall-through. - New regex constants and helpers. - `[COMMIT_OR_PR]` metavar and updated docstring on the click command. * Add tests and changelog entry for PR-or-commit input * Address round-1 review feedback for PR-or-commit input Apply reviewer findings on top of the PR-or-commit input change: - Wrap `_resolve_commit_or_fetch` inside `_resolve_pr_to_commit` so a non-resolvable merge commit reports `PR #N ...` instead of leaking the raw SHA the user never typed. - Handle `pydantic.ValidationError` from `get_pull_request`, and split 401 / 403 out of the generic HTTP error arm with an actionable hint about `github.token`. - Move the abbreviated-SHA check ahead of the dry-run gate in `_resolve_commit_or_fetch` so abbreviated input gives the real diagnosis on the first run instead of forcing a retry. - Guard the `rev-list --parents` call in `_abort_if_merge_commit` against `OSError`, matching the rest of the module's careful style. - Tighten the token-required abort for explicit PR inputs to flag the `--no-pr` carve-out (the lookup is needed regardless of PR creation). - Drop `_fetch_pr`'s dependency on the full `Application`; only the token is needed. - Annotate `Application.abort` parameters (`text: str`, `code: int`, `**kwargs: Any`) to finish the typing surface. - Strip the raw input in `_resolve_input` so trailing whitespace from a copy-paste no longer drops the user to commit resolution. - Drop `[ ]` from the click metavar; click adds them for optional arguments and they were rendering doubled in --help. - Replace the test helper's `lambda` exit function with a real `NoReturn` callable so it matches the new `Application.__init__` signature. Also corrects the pre-existing `Applicatione` typo. - Add `http://`, fragment, and trailing-whitespace variants to the parametrized PR-input test. - Remove an em-dash from a test comment. * Address round-2 review feedback - Convert `_abort_if_merge_commit`'s try/except to try/else so the parent-count binding is structurally explicit rather than relying on `Application.__exit_func` being `NoReturn` at runtime. The typing contract still holds, but a future caller wiring a non-terminating exit function would now surface as a structural error instead of an `UnboundLocalError`. - Add `test_command_emits_pr_context_when_pure_digit_pr_merge_commit_missing` pinning the path the round-1 f-1 fix was filed for: pure-digit input + PR found + non-resolvable merge commit should abort with a message that names `PR #N`, not the raw SHA the user never typed. * Drop redundant negative assertion in PR-context regression test The positive assertion already validates that the PR-context message fires. The negative assertion was meant to guard against the raw commit-resolution message firing alone, but that message is in fact suffixed onto the PR-context wrap, so any `not in` / `startswith` form either passed trivially (Rich console wrapping) or failed for the wrong reason. Drop it. * Consolidate the PR's changelog into a single entry * Quiet httpx and show a progress line during PR resolution The httpx logger emits an INFO line for every request, which leaks through to the CLI as `INFO: HTTP Request: GET ... "HTTP/1.1 200 OK"` just before our own status messages. Drop httpx to WARNING at command entry so only failures are surfaced. Also print `Resolving PR #N via GitHub...` before the lookup so the user sees something happen when the command pauses for the API call, matching the existing "Commit X not found locally; fetching from origin" pattern. * Force `git add` when resolving `.in-toto/` conflicts `.in-toto/` is gitignored in this repo (and most repos that ship signed packages), so `git add <path>` refuses to stage the file even though the path is already tracked in HEAD. `_resolve_in_toto_conflict` gets here only for paths git itself flagged as `--diff-filter=U`, so forcing the add is safe and matches the rest of the function's "keep target branch's .in-toto" intent. Without this, cherry-picking any commit that touches a `.in-toto/` file (i.e. every release commit) aborts mid-workflow with `Command '['git', 'add', '.in-toto/...']' returned non-zero exit status 1`. d54c6b8
1 parent 0fe93e0 commit db5fc86

2 files changed

Lines changed: 2 additions & 2 deletions

File tree

ddev/cli/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@
137137
</code></pre></div> <p><strong>Options:</strong></p> <table> <thead> <tr> <th>Name</th> <th>Type</th> <th>Description</th> <th>Default</th> </tr> </thead> <tbody> <tr> <td><code>--message</code>, <code>-m</code></td> <td>text</td> <td>The changelog text</td> <td>None</td> </tr> <tr> <td><code>--help</code></td> <td>boolean</td> <td>Show this message and exit.</td> <td><code>False</code></td> </tr> </tbody> </table> <h4 id=ddev-release-changelog-show>ddev release changelog show<a class=headerlink href=#ddev-release-changelog-show title="Permanent link">&para;</a></h4> <p>Print the section of TARGET's CHANGELOG.md that corresponds to VERSION.</p> <p>The output is the markdown content between the <code>## VERSION</code> heading and the next <code>##</code> heading, with surrounding blank lines stripped. Useful for populating GitHub release notes from the just-built changelog.</p> <p><strong>Usage:</strong></p> <div class=highlight><pre><span></span><code>ddev release changelog show [OPTIONS] TARGET VERSION
138138
</code></pre></div> <p><strong>Options:</strong></p> <table> <thead> <tr> <th>Name</th> <th>Type</th> <th>Description</th> <th>Default</th> </tr> </thead> <tbody> <tr> <td><code>--file</code>, <code>-f</code></td> <td>file</td> <td>Write the extracted section to this file (overwrites if it exists) instead of stdout</td> <td>None</td> </tr> <tr> <td><code>--help</code></td> <td>boolean</td> <td>Show this message and exit.</td> <td><code>False</code></td> </tr> </tbody> </table> <h3 id=ddev-release-list>ddev release list<a class=headerlink href=#ddev-release-list title="Permanent link">&para;</a></h3> <p>Show all versions of an integration.</p> <p><strong>Usage:</strong></p> <div class=highlight><pre><span></span><code>ddev release list [OPTIONS] INTEGRATION
139139
</code></pre></div> <p><strong>Options:</strong></p> <table> <thead> <tr> <th>Name</th> <th>Type</th> <th>Description</th> <th>Default</th> </tr> </thead> <tbody> <tr> <td><code>--help</code></td> <td>boolean</td> <td>Show this message and exit.</td> <td><code>False</code></td> </tr> </tbody> </table> <h3 id=ddev-release-make>ddev release make<a class=headerlink href=#ddev-release-make title="Permanent link">&para;</a></h3> <p>Perform a set of operations needed to release checks:</p> <ul> <li>update the version in <code>__about__.py</code></li> <li>update the changelog</li> <li>update the <code>requirements-agent-release.txt</code> file</li> <li>update in-toto metadata</li> <li>commit the above changes</li> </ul> <p>You can release everything at once by setting the check to <code>all</code>.</p> <p>If you run into issues signing: - Ensure you did <code>gpg --import &lt;YOUR_KEY_ID&gt;.gpg.pub</code></p> <p><strong>Usage:</strong></p> <div class=highlight><pre><span></span><code>ddev release make [OPTIONS] CHECKS...
140-
</code></pre></div> <p><strong>Options:</strong></p> <table> <thead> <tr> <th>Name</th> <th>Type</th> <th>Description</th> <th>Default</th> </tr> </thead> <tbody> <tr> <td><code>--version</code></td> <td>text</td> <td>N/A</td> <td>None</td> </tr> <tr> <td><code>--end</code></td> <td>text</td> <td>N/A</td> <td>None</td> </tr> <tr> <td><code>--new</code></td> <td>boolean</td> <td>Ensure versions are at 1.0.0</td> <td><code>False</code></td> </tr> <tr> <td><code>--skip-sign</code></td> <td>boolean</td> <td>Skip the signing of release metadata</td> <td><code>False</code></td> </tr> <tr> <td><code>--sign-only</code></td> <td>boolean</td> <td>Only sign release metadata</td> <td><code>False</code></td> </tr> <tr> <td><code>--exclude</code></td> <td>text</td> <td>Comma-separated list of checks to skip</td> <td>None</td> </tr> <tr> <td><code>--allow-master</code></td> <td>boolean</td> <td>Allow ddev to commit directly to master. Forbidden for core.</td> <td><code>False</code></td> </tr> <tr> <td><code>--help</code></td> <td>boolean</td> <td>Show this message and exit.</td> <td><code>False</code></td> </tr> </tbody> </table> <h3 id=ddev-release-port-commit>ddev release port-commit<a class=headerlink href=#ddev-release-port-commit title="Permanent link">&para;</a></h3> <p>Backport a commit onto a target branch.</p> <p>Cherry-picks COMMIT_HASH onto <code>--target-branch</code> (default <code>master</code>) on a new branch named <code>&lt;github-user&gt;/&lt;prefix&gt;-&lt;sha[:10]&gt;-&lt;suffix&gt;</code>, preserving <code>.in-toto</code> files from the target branch so package signatures stay intact. Pushes the branch and, unless <code>--no-pr</code> is set, opens a pull request titled <code>[Backport] &lt;subject&gt;</code> and labeled with <code>--pr-labels</code>.</p> <p>If COMMIT_HASH is omitted, the current HEAD commit is used after confirmation.</p> <p>The GitHub user for the branch prefix is taken from <code>ddev config</code> (<code>github.user</code>) or the <code>DD_GITHUB_USER</code> / <code>GITHUB_USER</code> / <code>GITHUB_ACTOR</code> environment variables.</p> <p><strong>Usage:</strong></p> <div class=highlight><pre><span></span><code>ddev release port-commit [OPTIONS] [COMMIT_HASH]
140+
</code></pre></div> <p><strong>Options:</strong></p> <table> <thead> <tr> <th>Name</th> <th>Type</th> <th>Description</th> <th>Default</th> </tr> </thead> <tbody> <tr> <td><code>--version</code></td> <td>text</td> <td>N/A</td> <td>None</td> </tr> <tr> <td><code>--end</code></td> <td>text</td> <td>N/A</td> <td>None</td> </tr> <tr> <td><code>--new</code></td> <td>boolean</td> <td>Ensure versions are at 1.0.0</td> <td><code>False</code></td> </tr> <tr> <td><code>--skip-sign</code></td> <td>boolean</td> <td>Skip the signing of release metadata</td> <td><code>False</code></td> </tr> <tr> <td><code>--sign-only</code></td> <td>boolean</td> <td>Only sign release metadata</td> <td><code>False</code></td> </tr> <tr> <td><code>--exclude</code></td> <td>text</td> <td>Comma-separated list of checks to skip</td> <td>None</td> </tr> <tr> <td><code>--allow-master</code></td> <td>boolean</td> <td>Allow ddev to commit directly to master. Forbidden for core.</td> <td><code>False</code></td> </tr> <tr> <td><code>--help</code></td> <td>boolean</td> <td>Show this message and exit.</td> <td><code>False</code></td> </tr> </tbody> </table> <h3 id=ddev-release-port-commit>ddev release port-commit<a class=headerlink href=#ddev-release-port-commit title="Permanent link">&para;</a></h3> <p>Backport a commit onto a target branch.</p> <p>Cherry-picks COMMIT_OR_PR onto <code>--target-branch</code> (default <code>master</code>) on a new branch named <code>&lt;github-user&gt;/&lt;prefix&gt;-&lt;sha[:10]&gt;-&lt;suffix&gt;</code>, preserving <code>.in-toto</code> files from the target branch so package signatures stay intact. Pushes the branch and, unless <code>--no-pr</code> is set, opens a pull request titled <code>[Backport] &lt;subject&gt;</code> and labeled with <code>--pr-labels</code>.</p> <p>COMMIT_OR_PR accepts: a full 40-character commit SHA, a PR number (e.g. <code>23703</code>), an explicit <code>PR-&lt;number&gt;</code> token, or a GitHub PR URL. Pure-digit inputs are tried as a PR first when a GitHub token is configured, and fall back to commit resolution on 404. If omitted, the current HEAD commit is used after confirmation.</p> <p>The GitHub user for the branch prefix is taken from <code>ddev config</code> (<code>github.user</code>) or the <code>DD_GITHUB_USER</code> / <code>GITHUB_USER</code> / <code>GITHUB_ACTOR</code> environment variables.</p> <p><strong>Usage:</strong></p> <div class=highlight><pre><span></span><code>ddev release port-commit [OPTIONS] COMMIT_OR_PR
141141
</code></pre></div> <p><strong>Options:</strong></p> <table> <thead> <tr> <th>Name</th> <th>Type</th> <th>Description</th> <th>Default</th> </tr> </thead> <tbody> <tr> <td><code>-t</code>, <code>--target-branch</code></td> <td>text</td> <td>Target branch to port to.</td> <td><code>master</code></td> </tr> <tr> <td><code>-p</code>, <code>--branch-prefix</code></td> <td>text</td> <td>Branch name prefix.</td> <td><code>port</code></td> </tr> <tr> <td><code>-s</code>, <code>--branch-suffix</code></td> <td>text</td> <td>Branch name suffix. Defaults to <code>to-&lt;target-branch&gt;</code>.</td> <td>None</td> </tr> <tr> <td><code>-l</code>, <code>--pr-labels</code></td> <td>text</td> <td>Comma-separated PR labels.</td> <td><code>qa/skip-qa</code></td> </tr> <tr> <td><code>--no-pr</code></td> <td>boolean</td> <td>Don't create a pull request.</td> <td><code>False</code></td> </tr> <tr> <td><code>--draft</code></td> <td>boolean</td> <td>Open the PR as a draft.</td> <td><code>False</code></td> </tr> <tr> <td><code>--verify</code></td> <td>boolean</td> <td>Run commit hooks (skipped by default).</td> <td><code>False</code></td> </tr> <tr> <td><code>--dry-run</code></td> <td>boolean</td> <td>Print every step instead of executing it.</td> <td><code>False</code></td> </tr> <tr> <td><code>--help</code></td> <td>boolean</td> <td>Show this message and exit.</td> <td><code>False</code></td> </tr> </tbody> </table> <h3 id=ddev-release-show>ddev release show<a class=headerlink href=#ddev-release-show title="Permanent link">&para;</a></h3> <p>To avoid GitHub's public API rate limits, you need to set <code>github.user</code>/<code>github.token</code> in your config file or use the <code>DD_GITHUB_USER</code>/<code>DD_GITHUB_TOKEN</code> environment variables.</p> <p><strong>Usage:</strong></p> <div class=highlight><pre><span></span><code>ddev release show [OPTIONS] COMMAND [ARGS]...
142142
</code></pre></div> <p><strong>Options:</strong></p> <table> <thead> <tr> <th>Name</th> <th>Type</th> <th>Description</th> <th>Default</th> </tr> </thead> <tbody> <tr> <td><code>--help</code></td> <td>boolean</td> <td>Show this message and exit.</td> <td><code>False</code></td> </tr> </tbody> </table> <h4 id=ddev-release-show-changes>ddev release show changes<a class=headerlink href=#ddev-release-show-changes title="Permanent link">&para;</a></h4> <p>Show all the pending PRs for a given check.</p> <p><strong>Usage:</strong></p> <div class=highlight><pre><span></span><code>ddev release show changes [OPTIONS] CHECK
143143
</code></pre></div> <p><strong>Options:</strong></p> <table> <thead> <tr> <th>Name</th> <th>Type</th> <th>Description</th> <th>Default</th> </tr> </thead> <tbody> <tr> <td><code>--tag-pattern</code></td> <td>text</td> <td>The regex pattern for the format of the tag. Required if the tag doesn't follow semver</td> <td>None</td> </tr> <tr> <td><code>--tag-prefix</code></td> <td>text</td> <td>Specify the prefix of the tag to use if the tag doesn't follow semver</td> <td>None</td> </tr> <tr> <td><code>--dry-run</code>, <code>-n</code></td> <td>boolean</td> <td>Run the command in dry-run mode</td> <td><code>False</code></td> </tr> <tr> <td><code>--since</code></td> <td>text</td> <td>The git ref to use instead of auto-detecting the tag to view changes since</td> <td>None</td> </tr> <tr> <td><code>--help</code></td> <td>boolean</td> <td>Show this message and exit.</td> <td><code>False</code></td> </tr> </tbody> </table> <h4 id=ddev-release-show-ready>ddev release show ready<a class=headerlink href=#ddev-release-show-ready title="Permanent link">&para;</a></h4> <p>Show all the checks that can be released.</p> <p><strong>Usage:</strong></p> <div class=highlight><pre><span></span><code>ddev release show ready [OPTIONS]

search/search_index.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)