Skip to content

Commit cfe3ef9

Browse files
authored
fix(auth): pick the push credential from the resolved forge, not the host name (#1124)
1 parent 38e0c30 commit cfe3ef9

13 files changed

Lines changed: 261 additions & 94 deletions

File tree

‎docs/site/docs-en/configuration/config-file.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ Global settings that apply to all packages.
189189
| `releaseCommitMode` | string | `"commit"` | How to handle the release commit: `"commit"`, `"pr"`, or `"none"` |
190190
| `releaseCommitScope` | string | `"grouped"` | In a monorepo where several packages are bumped at once, whether to create a single `"grouped"` commit or one commit `"per-package"`. Only matters when multiple packages bump. |
191191
| `releaseCommitBody` | string | `"none"` | What goes in the body of the release commit, below the subject. `"none"` keeps the single-line subject. `"summary"` lists one line per released package with its commit count. `"full"` embeds the changelog section written for each package — under `"grouped"` scope each section is headed `## <package> <version>`. |
192-
| `forge` | string | `"auto"` | Git forge override: `"auto"` detects from the remote URL, and for an unrecognised host it probes the API over HTTPS to auto-detect a self-hosted **GitLab**, **GitHub Enterprise**, or **Gitea / Forgejo** instance (cached, ~2s, best-effort). Set `"github"`, `"gitlab"`, `"gitea"` (Gitea / Forgejo / Codeberg), or `"bitbucket"` (Bitbucket Cloud) to force a forge — needed only when the host isn't reachable over HTTPS or you want to skip probing. Gitea auth uses `GITEA_TOKEN` / `FORGEJO_TOKEN`; Bitbucket uses `BITBUCKET_TOKEN`. All cover release creation — on Bitbucket, which has no release object, the release is the annotated tag FerrFlow pushes. PR mode is GitHub/GitLab only. |
192+
| `forge` | string | `"auto"` | Git forge override: `"auto"` detects from the remote URL, and for an unrecognised host it probes the API over HTTPS to auto-detect a self-hosted **GitLab**, **GitHub Enterprise**, or **Gitea / Forgejo** instance (cached, ~2s, best-effort). The same answer picks the credential git pushes with, so a self-hosted GitLab pushes with `GITLAB_TOKEN` whatever its host name. Set `"github"`, `"gitlab"`, `"gitea"` (Gitea / Forgejo / Codeberg), or `"bitbucket"` (Bitbucket Cloud) to force a forge — needed only when the host isn't reachable over HTTPS or you want to skip probing. Gitea auth uses `GITEA_TOKEN` / `FORGEJO_TOKEN`; Bitbucket uses `BITBUCKET_TOKEN`. All cover release creation — on Bitbucket, which has no release object, the release is the annotated tag FerrFlow pushes. PR mode is GitHub/GitLab only. |
193193
| `skipCi` | boolean | depends on mode | Add `[skip ci]` to release commits. Defaults to `true` when mode is `"commit"`, `false` otherwise. |
194194
| `commitSkipMarkers` | array | `["[skip ci]", "[ci skip]", "[no ci]", "[skip actions]", "[actions skip]"]` | Markers that cause FerrFlow to skip a commit when computing the next version. Matched case-insensitively, subject line only. |
195195
| `commitFormats` | object | permissive conventional | Which commit subjects map to which bump level. Each of `major` / `minor` / `patch` takes a pattern string, a list of patterns, or `"all"` as a catch-all; `*` matches any run of characters (including `/`) and `?` exactly one. Resolution is major → minor → patch, first match wins. `caseSensitive` (default `true`) lowercases both sides when false. Defaults also accept capitalised and slash-separated variants (`Feat:`, `feat/`, `feature:`, `Fix/`, `Perf:`, `Refactor/`, and so on), listed in full under [permissive defaults](/docs/reference/conventional-commits). Breaking markers (`feat!:`, `fix(api)!:`, a `BREAKING CHANGE:` footer) are always detected regardless of what is configured. |

‎docs/site/docs-fr/configuration/config-file.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ Paramètres globaux qui s'appliquent à tous les packages.
189189
| `releaseCommitMode` | string | `"commit"` | Gestion du commit de release : `"commit"`, `"pr"` ou `"none"` |
190190
| `releaseCommitScope` | string | `"grouped"` | Dans un monorepo où plusieurs packages sont bumpés en même temps, créer un seul commit `"grouped"` ou un commit `"per-package"`. N'a d'effet que quand plusieurs packages bumpent. |
191191
| `releaseCommitBody` | string | `"none"` | Ce que contient le corps du commit de release, sous la ligne de sujet. `"none"` conserve le sujet sur une seule ligne. `"summary"` liste une ligne par package publié avec son nombre de commits. `"full"` intègre la section de changelog écrite pour chaque package — en scope `"grouped"`, chaque section est titrée `## <package> <version>`. |
192-
| `forge` | string | `"auto"` | Forçage du forge git : `"auto"` détecte depuis l'URL du remote, et pour un hôte non reconnu il sonde l'API en HTTPS pour auto-détecter une instance auto-hébergée de **GitLab**, **GitHub Enterprise** ou **Gitea / Forgejo** (mis en cache, ~2s, best-effort). Renseignez `"github"`, `"gitlab"`, `"gitea"` (Gitea / Forgejo / Codeberg) ou `"bitbucket"` (Bitbucket Cloud) pour forcer un forge — nécessaire uniquement si l'hôte n'est pas joignable en HTTPS ou pour éviter le sondage. L'auth Gitea utilise `GITEA_TOKEN` / `FORGEJO_TOKEN` ; Bitbucket utilise `BITBUCKET_TOKEN`. Tous couvrent la création de release — sur Bitbucket, qui n'a pas d'objet release, la release est le tag annoté que FerrFlow pousse. Le mode PR reste GitHub/GitLab. |
192+
| `forge` | string | `"auto"` | Forçage du forge git : `"auto"` détecte depuis l'URL du remote, et pour un hôte non reconnu il sonde l'API en HTTPS pour auto-détecter une instance auto-hébergée de **GitLab**, **GitHub Enterprise** ou **Gitea / Forgejo** (mis en cache, ~2s, best-effort). Ce même résultat choisit l'identifiant avec lequel git pousse : une instance GitLab auto-hébergée pousse avec `GITLAB_TOKEN`, quel que soit son nom d'hôte. Renseignez `"github"`, `"gitlab"`, `"gitea"` (Gitea / Forgejo / Codeberg) ou `"bitbucket"` (Bitbucket Cloud) pour forcer un forge — nécessaire uniquement si l'hôte n'est pas joignable en HTTPS ou pour éviter le sondage. L'auth Gitea utilise `GITEA_TOKEN` / `FORGEJO_TOKEN` ; Bitbucket utilise `BITBUCKET_TOKEN`. Tous couvrent la création de release — sur Bitbucket, qui n'a pas d'objet release, la release est le tag annoté que FerrFlow pousse. Le mode PR reste GitHub/GitLab. |
193193
| `skipCi` | boolean | dépend du mode | Ajouter `[skip ci]` aux commits de release. Par défaut `true` en mode `"commit"`, `false` sinon. |
194194
| `commitSkipMarkers` | array | `["[skip ci]", "[ci skip]", "[no ci]", "[skip actions]", "[actions skip]"]` | Marqueurs qui font ignorer un commit par FerrFlow lors du calcul de la prochaine version. Comparaison insensible à la casse, sur la ligne de sujet uniquement. |
195195
| `commitFormats` | object | conventionnel permissif | Quels sujets de commit correspondent à quel niveau de bump. Chacun de `major` / `minor` / `patch` accepte un motif, une liste de motifs, ou `"all"` comme fourre-tout ; `*` correspond à n'importe quelle suite de caractères (y compris `/`) et `?` à exactement un. Résolution : major → minor → patch, premier motif gagnant. `caseSensitive` (défaut `true`) met les deux côtés en minuscules quand il vaut `false`. Les défauts acceptent aussi les variantes capitalisées et séparées par une barre oblique (`Feat:`, `feat/`, `feature:`, `Fix/`, `Perf:`, `Refactor/`, etc.), listées en entier dans [défauts permissifs](/fr/docs/reference/conventional-commits). Les marqueurs de rupture (`feat!:`, `fix(api)!:`, un pied de page `BREAKING CHANGE:`) sont toujours détectés quelle que soit la configuration. |

‎src/git/auth.rs‎

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::process::Command;
22

33
use super::repo::Repository;
4+
use crate::config::ForgeKind;
45
use crate::forge::gitlab::GitLabToken;
56

67
#[cfg(test)]
@@ -32,24 +33,36 @@ fn is_gitlab(url: &str) -> bool {
3233
host_of(url).is_some_and(|host| host.contains("gitlab"))
3334
}
3435

35-
pub(super) fn token_for_url(url: &str) -> Option<(String, String)> {
36-
if let Ok(token) = std::env::var("FERRFLOW_TOKEN") {
37-
let user = if is_gitlab(url) {
36+
fn forge_of(url: &str, configured: ForgeKind) -> Option<ForgeKind> {
37+
match configured {
38+
ForgeKind::Auto if is_gitlab(url) => Some(ForgeKind::Gitlab),
39+
ForgeKind::Auto => crate::forge::detect_forge_with_probe(url),
40+
explicit => Some(explicit),
41+
}
42+
}
43+
44+
pub(super) fn token_for_url(url: &str, forge: ForgeKind) -> Option<(String, String)> {
45+
let ferrflow_token = std::env::var("FERRFLOW_TOKEN").ok();
46+
let gitlab_token = std::env::var("GITLAB_TOKEN").ok();
47+
let github_token = std::env::var("GITHUB_TOKEN").ok();
48+
if ferrflow_token.is_none() && gitlab_token.is_none() && github_token.is_none() {
49+
return None;
50+
}
51+
let is_gitlab = forge_of(url, forge) == Some(ForgeKind::Gitlab);
52+
if let Some(token) = ferrflow_token {
53+
let user = if is_gitlab {
3854
GitLabToken::of(&token).git_username()
3955
} else {
4056
"x-access-token"
4157
};
4258
return Some((user.to_string(), token));
4359
}
44-
if is_gitlab(url) {
45-
if let Ok(token) = std::env::var("GITLAB_TOKEN") {
46-
let user = GitLabToken::of(&token).git_username();
47-
return Some((user.to_string(), token));
48-
}
49-
} else if let Ok(token) = std::env::var("GITHUB_TOKEN") {
50-
return Some(("x-access-token".to_string(), token));
60+
if is_gitlab {
61+
let token = gitlab_token?;
62+
let user = GitLabToken::of(&token).git_username();
63+
return Some((user.to_string(), token));
5164
}
52-
None
65+
github_token.map(|token| ("x-access-token".to_string(), token))
5366
}
5467

5568
/// Where the credential reaches git. `/proc/<pid>/cmdline` is world-readable,
@@ -68,9 +81,9 @@ pub(super) const GIT_PASSWORD_VAR: &str = "FERRFLOW_GIT_PASSWORD";
6881
const CREDENTIAL_HELPER: &str =
6982
r#"!f() { echo "username=$FERRFLOW_GIT_USER"; echo "password=$FERRFLOW_GIT_PASSWORD"; }; f"#;
7083

71-
pub(super) fn configure_git_command(cmd: &mut Command, url: &str) {
84+
pub(super) fn configure_git_command(cmd: &mut Command, url: &str, forge: ForgeKind) {
7285
scrub_trace_env(cmd);
73-
if let Some((user, token)) = token_for_url(url) {
86+
if let Some((user, token)) = token_for_url(url, forge) {
7487
cmd.env(GIT_USER_VAR, user);
7588
cmd.env(GIT_PASSWORD_VAR, token);
7689
cmd.arg("-c")

‎src/git/fetch.rs‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
use anyhow::{Context, Result, anyhow};
22

33
use super::auth::{configure_git_command, get_remote_url};
4+
use super::remote::Remote;
45
use super::repo::Repository;
56

6-
pub fn fetch_tags(repo: &Repository, remote_name: &str) -> Result<()> {
7+
pub fn fetch_tags(repo: &Repository, remote: Remote<'_>) -> Result<()> {
8+
let Remote {
9+
name: remote_name,
10+
forge,
11+
} = remote;
712
let workdir = repo
813
.workdir()
914
.ok_or_else(|| anyhow!("Bare repositories are not supported"))?;
@@ -12,7 +17,7 @@ pub fn fetch_tags(repo: &Repository, remote_name: &str) -> Result<()> {
1217

1318
let mut cmd = std::process::Command::new("git");
1419
cmd.current_dir(workdir);
15-
configure_git_command(&mut cmd, &url);
20+
configure_git_command(&mut cmd, &url, forge);
1621
cmd.args(["fetch", "--tags", remote_name]);
1722

1823
let output = cmd

‎src/git/mod.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod commits;
55
mod diff;
66
mod fetch;
77
mod push;
8+
mod remote;
89
mod repo;
910
mod retry;
1011
mod shell;
@@ -32,6 +33,7 @@ pub use push::{
3233
delete_tag_if_unchanged, force_push_branch, force_push_tags, push, push_tags,
3334
release_branch_foreign_commit, reset_branch_to_remote, revert_commit, verify_remote_branch,
3435
};
36+
pub use remote::Remote;
3537
pub use repo::{Repository, get_repo_root, open_repo, resolve_current_branch};
3638
pub use retry::is_push_rejected_error;
3739
pub use tags::{

0 commit comments

Comments
 (0)