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
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
GiteaClientIntegrationTesta 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.
> 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)
mi
changed title from WIP: Git credentials and the reading of pull requests split up and tested to Git credentials and the reading of pull requests split up and tested, gate down to 28 (v1.37.3)2026-10-05 17:39:57 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The Problem
Two methods of the git and Gitea access are above the Kotlin target of 15:
GitService.authenticatedGiteaClient.GiteaPullRequest.toOpenPullRequestauthenticateddecides 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.
toOpenPullRequestmaps Gitea's answer and is well tested, but every absent field is a branch of its own.Non-Goals
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.
git.accountandgit.tokenGIT_ASKPASS,WERKATOR_GIT_ACCOUNT,WERKATOR_GIT_TOKENandGIT_TERMINAL_PROMPT=0GIT_TERMINAL_PROMPT=0alone, and no configuration is readGIT_TERMINAL_PROMPT=0aloneVerified by
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.
headandbase, titled[wip] lower caserepo_idWIP:in its middleVerified by
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 testThe Solution
GitService.credentialsForanswers which credentials a repository's remote commands get, null for none;authenticatedruns the command with the askpass script or withNO_PROMPT.GiteaPullRequest.toOpenPullRequestreplaces an absent head or base with an empty branch once, the branch answers its ref and commit itself, andisDraftholds the fallback on the title.A local PIT run of
GitServiceandGiteaClientafterwards:GitService.authenticatedGitService.credentialsForGiteaClient.GiteaPullRequest.toOpenPullRequestGiteaClient.GiteaPullRequest.isDraftThe highest score left in both classes is 12 (
GitService.originBranchCommitTimes).The project maximum is now
NginxProxyManager.resolveSettingsat 27.44, so the gate goes down from 31 to 28.Additional Changes
maxMutationCrapinbuild.gradle.ktslowered from 31 to 28.releases.html.🤖 Generated with Claude Code
WIP: Git credentials and the reading of pull requests split up and testedto Git credentials and the reading of pull requests split up and tested, gate down to 28 (v1.37.3)