> Historic PR-documentation is not maintained along with new PRs — treat it as a snapshot, not as current documentation. ## The Problem Two methods of the git and Gitea access are above the Kotlin target of 15: | Method | Mutation-CRAP | Complexity | Mutation score | |--------------------------------------------------|---------------|------------|----------------| | `GitService.authenticated` | 30.12 | 6 | 0.13 | | `GiteaClient.GiteaPullRequest.toOpenPullRequest` | 16.04 | 16 | 0.95 | `authenticated` decides which remote git commands get the configured credentials. No test looked at that decision: the git tests run against local fixture repositories, whose origin is a path, so every fetch ran without credentials and 7 of 8 mutants survived. `toOpenPullRequest` maps Gitea's answer and is well tested, but every absent field is a branch of its own. ## Non-Goals - No change in behaviour. - The other groups above 15 follow in their own PRs. ## The Scenarios ### Feature: remote git commands get credentials only where they can work #### Scenario#122.01: only an HTTP(S) origin with account and token fetches through the askpass script So that the token reaches git exactly where git can use it, and never a prompt blocks a fetch. - **Given** a repository whose top level configures `git.account` and `git.token` - **When** its origin is an HTTPS or an HTTP URL - **Then** the fetch runs with `GIT_ASKPASS`, `WERKATOR_GIT_ACCOUNT`, `WERKATOR_GIT_TOKEN` and `GIT_TERMINAL_PROMPT=0` - **and** the credentials are read from the repository's top level, also when the command runs in a subdirectory - **and** the askpass script exists while the command runs and is gone afterwards - **When** the origin is an SSH URL or there is no origin - **Then** the fetch runs with `GIT_TERMINAL_PROMPT=0` alone, and no configuration is read - **When** the account or the token is blank - **Then** the fetch runs with `GIT_TERMINAL_PROMPT=0` alone ##### Verified by - [GitServiceCredentialsUnitTest](../../src/test/kotlin/de/hoennig/werkator/git/GitServiceCredentialsUnitTest.kt) ### Feature: open pull requests are read whatever Gitea leaves out #### Scenario#122.02: a pull request without head or base reads as a fork, and the title prefix is matched in any case So that a sparse or older answer never marks a foreign branch as the repository's own, nor misses a draft. - **Given** Gitea answers a pull request without `head` and `base`, titled `[wip] lower case` - **and** one whose head has no `repo_id` - **and** one whose title only mentions `WIP:` in its middle - **When** the open pull requests are read - **Then** the first is a draft from a fork with empty refs - **and** the second is from a fork, since nothing says its branch is the repository's own - **and** the third is no draft ##### Verified by - [GiteaClientIntegrationTest](../../src/test/kotlin/de/hoennig/werkator/gitea/GiteaClientIntegrationTest.kt) `a pull request without head or base reads as a fork, and the title prefix is matched in any case (Scenario#122.02)`, and the Scenario#117.02 test ## The Solution `GitService.credentialsFor` answers which credentials a repository's remote commands get, null for none; `authenticated` runs the command with the askpass script or with `NO_PROMPT`. `GiteaPullRequest.toOpenPullRequest` replaces an absent head or base with an empty branch once, the branch answers its ref and commit itself, and `isDraft` holds the fallback on the title. A local PIT run of `GitService` and `GiteaClient` afterwards: | Method | Mutation-CRAP | Complexity | Mutation score | |--------------------------------------------------|---------------|------------|----------------| | `GitService.authenticated` | 2.15 | 2 | 0.67 | | `GitService.credentialsFor` | 9 | 9 | 1.00 | | `GiteaClient.GiteaPullRequest.toOpenPullRequest` | 6 | 6 | 1.00 | | `GiteaClient.GiteaPullRequest.isDraft` | 3.03 | 3 | 0.86 | The highest score left in both classes is 12 (`GitService.originBranchCommitTimes`). The project maximum is now `NginxProxyManager.resolveSettings` at 27.44, so the gate goes down from 31 to 28. ## Additional Changes - `maxMutationCrap` in `build.gradle.kts` lowered from 31 to 28. - Version 1.37.3 with its entry in `releases.html`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: mhoennig <michael@hoennig.de> Reviewed-on: #122
89 lines
4.8 KiB
Markdown
89 lines
4.8 KiB
Markdown
# PR#122: Git Credentials and the Reading of Pull Requests Split Up and Tested
|
|
|
|
> **WARNING:** This document describes only the change applied in this PR.
|
|
> It may already be outdated once the next PR is merged.
|
|
> Historic PR-documentation is not maintained along with new PRs — treat it as a snapshot, not as current documentation.
|
|
|
|
|
|
## The Problem
|
|
|
|
Two methods of the git and Gitea access are above the Kotlin target of 15:
|
|
|
|
| Method | Mutation-CRAP | Complexity | Mutation score |
|
|
|--------------------------------------------------|---------------|------------|----------------|
|
|
| `GitService.authenticated` | 30.12 | 6 | 0.13 |
|
|
| `GiteaClient.GiteaPullRequest.toOpenPullRequest` | 16.04 | 16 | 0.95 |
|
|
|
|
`authenticated` decides which remote git commands get the configured credentials.
|
|
No test looked at that decision: the git tests run against local fixture repositories, whose origin is a path, so every fetch ran without credentials and 7 of 8 mutants survived.
|
|
`toOpenPullRequest` maps Gitea's answer and is well tested, but every absent field is a branch of its own.
|
|
|
|
## Non-Goals
|
|
|
|
- No change in behaviour.
|
|
- The other groups above 15 follow in their own PRs.
|
|
|
|
## The Scenarios
|
|
|
|
### Feature: remote git commands get credentials only where they can work
|
|
|
|
#### Scenario#122.01: only an HTTP(S) origin with account and token fetches through the askpass script
|
|
|
|
So that the token reaches git exactly where git can use it, and never a prompt blocks a fetch.
|
|
|
|
- **Given** a repository whose top level configures `git.account` and `git.token`
|
|
- **When** its origin is an HTTPS or an HTTP URL
|
|
- **Then** the fetch runs with `GIT_ASKPASS`, `WERKATOR_GIT_ACCOUNT`, `WERKATOR_GIT_TOKEN` and `GIT_TERMINAL_PROMPT=0`
|
|
- **and** the credentials are read from the repository's top level, also when the command runs in a subdirectory
|
|
- **and** the askpass script exists while the command runs and is gone afterwards
|
|
- **When** the origin is an SSH URL or there is no origin
|
|
- **Then** the fetch runs with `GIT_TERMINAL_PROMPT=0` alone, and no configuration is read
|
|
- **When** the account or the token is blank
|
|
- **Then** the fetch runs with `GIT_TERMINAL_PROMPT=0` alone
|
|
|
|
##### Verified by
|
|
|
|
- [GitServiceCredentialsUnitTest](../../src/test/kotlin/de/hoennig/werkator/git/GitServiceCredentialsUnitTest.kt)
|
|
|
|
### Feature: open pull requests are read whatever Gitea leaves out
|
|
|
|
#### Scenario#122.02: a pull request without head or base reads as a fork, and the title prefix is matched in any case
|
|
|
|
So that a sparse or older answer never marks a foreign branch as the repository's own, nor misses a draft.
|
|
|
|
- **Given** Gitea answers a pull request without `head` and `base`, titled `[wip] lower case`
|
|
- **and** one whose head has no `repo_id`
|
|
- **and** one whose title only mentions `WIP:` in its middle
|
|
- **When** the open pull requests are read
|
|
- **Then** the first is a draft from a fork with empty refs
|
|
- **and** the second is from a fork, since nothing says its branch is the repository's own
|
|
- **and** the third is no draft
|
|
|
|
##### Verified by
|
|
|
|
- [GiteaClientIntegrationTest](../../src/test/kotlin/de/hoennig/werkator/gitea/GiteaClientIntegrationTest.kt) `a pull request without head or base reads as a fork, and the title prefix is matched in any case (Scenario#122.02)`, and the Scenario#117.02 test
|
|
|
|
## The Solution
|
|
|
|
`GitService.credentialsFor` answers which credentials a repository's remote commands get, null for none; `authenticated` runs the command with the askpass script or with `NO_PROMPT`.
|
|
`GiteaPullRequest.toOpenPullRequest` replaces an absent head or base with an empty branch once, the branch answers its ref and commit itself, and `isDraft` holds the fallback on the title.
|
|
|
|
A local PIT run of `GitService` and `GiteaClient` afterwards:
|
|
|
|
| Method | Mutation-CRAP | Complexity | Mutation score |
|
|
|--------------------------------------------------|---------------|------------|----------------|
|
|
| `GitService.authenticated` | 2.15 | 2 | 0.67 |
|
|
| `GitService.credentialsFor` | 9 | 9 | 1.00 |
|
|
| `GiteaClient.GiteaPullRequest.toOpenPullRequest` | 6 | 6 | 1.00 |
|
|
| `GiteaClient.GiteaPullRequest.isDraft` | 3.03 | 3 | 0.86 |
|
|
|
|
The highest score left in both classes is 12 (`GitService.originBranchCommitTimes`).
|
|
The project maximum is now `NginxProxyManager.resolveSettings` at 27.44, so the gate goes down from 31 to 28.
|
|
|
|
## Additional Changes
|
|
|
|
- `maxMutationCrap` in `build.gradle.kts` lowered from 31 to 28.
|
|
- Version 1.37.3 with its entry in `releases.html`.
|
|
|
|
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|