Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 22 additions & 15 deletions crates/mergify-ci/src/scopes_detect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ use crate::git_refs::ReferencesSource;

pub struct ScopesOptions<'a> {
/// Explicit `--config <PATH>`. `None` triggers the
/// fallback chain (env var `MERGIFY_CONFIG_PATH`, then auto-
/// detection of `.mergify.yml` / `.mergify/config.yml` /
/// `.github/mergify.yml`).
/// fallback chain (env var `MERGIFY_CONFIG_PATH`, then the
/// auto-detection over
/// [`mergify_config::paths::DEFAULT_CONFIG_PATHS`]).
pub config: Option<&'a Path>,
/// Optional `--base`. Combined with `--head` to take the
/// "manual" References branch.
Expand Down Expand Up @@ -78,7 +78,7 @@ pub fn run(opts: ScopesOptions<'_>, output: &mut dyn Output) -> Result<(), CliEr
head,
write,
} = opts;
let config_path = resolve_config_path(config)?;
let config_path = resolve_config_path(config, output)?;
let cfg = config::load(&config_path)?;

let refs = resolve_refs(base, head, output)?;
Expand Down Expand Up @@ -111,13 +111,16 @@ pub fn run(opts: ScopesOptions<'_>, output: &mut dyn Output) -> Result<(), CliEr
Ok(())
}

/// Auto-detection mirrors Python's
/// `detector.get_mergify_config_path` (which is the same triple
/// `.mergify.yml`, `.mergify/config.yml`, `.github/mergify.yml`
/// that `mergify config validate` uses), with `MERGIFY_CONFIG_PATH`
/// honored ahead of it. Empty env var falls back to auto-detect
/// — matches Python.
fn resolve_config_path(explicit: Option<&Path>) -> Result<PathBuf, CliError> {
/// Auto-detection defers to [`mergify_config::paths`], so
/// `ci scopes` searches exactly what `mergify config validate`
/// searches — including the duplicate-configuration warning.
/// `MERGIFY_CONFIG_PATH` is honored ahead of it; an empty value
/// falls back to auto-detect, which is what the `gha-mergify-ci`
/// action relies on.
fn resolve_config_path(
explicit: Option<&Path>,
output: &mut dyn Output,
) -> Result<PathBuf, CliError> {
if let Some(path) = explicit {
if path.is_file() {
return Ok(path.to_path_buf());
Expand All @@ -136,7 +139,7 @@ fn resolve_config_path(explicit: Option<&Path>) -> Result<PathBuf, CliError> {
}
return Ok(p);
}
mergify_config::paths::resolve_config_path(None)
mergify_config::paths::resolve_config_path(None, output)
}

/// `(base, head)` resolution mirrors Python's branch in
Expand Down Expand Up @@ -310,7 +313,9 @@ mod tests {

#[test]
fn resolve_config_path_errors_on_missing_explicit() {
let err = resolve_config_path(Some(Path::new("/no/such/file.yml"))).unwrap_err();
let mut captured = Captured::human();
let err = resolve_config_path(Some(Path::new("/no/such/file.yml")), &mut captured.output)
.unwrap_err();
assert!(matches!(err, CliError::Configuration(_)));
assert!(err.to_string().contains("does not exist"));
}
Expand All @@ -328,8 +333,9 @@ mod tests {
// so this function owns the lookup — and the empty branch
// here must fall through to autodetect rather than report
// a malformed env var.
let mut captured = Captured::human();
let result = env::testing::with_var("MERGIFY_CONFIG_PATH", Some(""), || {
resolve_config_path(None)
resolve_config_path(None, &mut captured.output)
});
// Either autodetect found a real config (cargo test runs
// from a workspace that contains `.mergify.yml`, so this is
Expand All @@ -352,9 +358,10 @@ mod tests {
// value that doesn't exist, the error must name the env
// var + the bogus path so the user can spot the typo
// without having to dig.
let mut captured = Captured::human();
let err =
env::testing::with_var("MERGIFY_CONFIG_PATH", Some("/no/such/.mergify.yml"), || {
resolve_config_path(None).unwrap_err()
resolve_config_path(None, &mut captured.output).unwrap_err()
});
let msg = err.to_string();
assert!(msg.contains("MERGIFY_CONFIG_PATH="), "got: {msg}");
Expand Down
37 changes: 31 additions & 6 deletions crates/mergify-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3831,11 +3831,28 @@ struct InternalStackRemoteChangesArgs {
author: String,
}

/// `--config-file`'s long help. Same reason as
/// [`SCOPES_CONFIG_HELP`]: the list of searched paths belongs to the
/// resolver, not to a doc comment that can fall behind it.
static CONFIG_FILE_HELP: std::sync::LazyLock<String> = std::sync::LazyLock::new(|| {
format!(
"Path to the Mergify configuration file.\n\nWhen omitted, the first of these that \
exists is used: {}. A repository carrying more than one gets a warning on stderr \
naming the file in use and the ones ignored.",
mergify_config::paths::DEFAULT_CONFIG_PATHS.join(", "),
)
});

#[derive(clap::Args)]
struct ConfigArgs {
/// Path to the Mergify configuration file (auto-detected if not
/// provided).
#[arg(long = "config-file", short = 'f', global = true)]
#[arg(
long = "config-file",
short = 'f',
global = true,
long_help = CONFIG_FILE_HELP.as_str(),
)]
config_file: Option<PathBuf>,

#[command(subcommand)]
Expand Down Expand Up @@ -3967,12 +3984,20 @@ struct GitRefsCliArgs {
#[derive(clap::Args)]
struct QueueInfoCliArgs {}

/// `--config`'s long help, built from the resolver's own search
/// list so the two cannot drift apart. clap wants a `&'static str`;
/// a `LazyLock` is what turns a runtime `join` into one.
static SCOPES_CONFIG_HELP: std::sync::LazyLock<String> = std::sync::LazyLock::new(|| {
format!(
"Path to YAML config file.\n\nFalls back to the MERGIFY_CONFIG_PATH environment \
variable, then auto-detects the first of these that exists: {}.",
mergify_config::paths::DEFAULT_CONFIG_PATHS.join(", "),
)
});

#[derive(clap::Args)]
struct ScopesCliArgs {
/// Path to YAML config file. Falls back to the
/// `MERGIFY_CONFIG_PATH` env var, then auto-detects
/// `.mergify.yml`, `.mergify/config.yml`, or
/// `.github/mergify.yml`.
/// Path to YAML config file (auto-detected when omitted).
//
// The env var lookup is intentionally *not* delegated to
// clap's `env = ...` attribute: callers (notably the
Expand All @@ -3989,7 +4014,7 @@ struct ScopesCliArgs {
// attribute, which covers this one) and
// `resolve_config_path_treats_empty_env_var_as_unset`
// (lower-level resolver).
#[arg(long)]
#[arg(long, long_help = SCOPES_CONFIG_HELP.as_str())]
config: Option<PathBuf>,

/// Base git reference to use to look for changed files.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ expression: schema
"id": "config_file",
"kind": "option",
"long": "config-file",
"longHelp": "Path to the Mergify configuration file (auto-detected if not provided)",
"longHelp": "Path to the Mergify configuration file.\n\nWhen omitted, the first of these that exists is used: .mergify.yml, .mergify.yaml, .mergify/config.yml, .mergify/config.yaml, .github/mergify.yml, .github/mergify.yaml. A repository carrying more than one gets a warning on stderr naming the file in use and the ones ignored.",
"numArgs": "1",
"possibleValues": [],
"required": false,
Expand Down Expand Up @@ -540,11 +540,11 @@ expression: schema
"default": null,
"env": null,
"global": false,
"help": "Path to YAML config file. Falls back to the `MERGIFY_CONFIG_PATH` env var, then auto-detects `.mergify.yml`, `.mergify/config.yml`, or `.github/mergify.yml`",
"help": "Path to YAML config file (auto-detected when omitted)",
"id": "config",
"kind": "option",
"long": "config",
"longHelp": "Path to YAML config file. Falls back to the `MERGIFY_CONFIG_PATH` env var, then auto-detects `.mergify.yml`, `.mergify/config.yml`, or `.github/mergify.yml`",
"longHelp": "Path to YAML config file.\n\nFalls back to the MERGIFY_CONFIG_PATH environment variable, then auto-detects the first of these that exists: .mergify.yml, .mergify.yaml, .mergify/config.yml, .mergify/config.yaml, .github/mergify.yml, .github/mergify.yaml.",
"numArgs": "1",
"possibleValues": [],
"required": false,
Expand Down
Loading
Loading