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
44 changes: 44 additions & 0 deletions e2e/lockfile/test_lockfile_locked_mode
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,47 @@ backend = "aqua:jqlang/jq"
EOF

assert_contains "mise install --locked --dry-run 2>&1" "would install"

# --- Test 4: locked=true with MISE_LOCKFILE=false errors (contradictory config) ---
cat <<EOF >mise.lock
[[tools.jq]]
version = "1.7.1"
backend = "aqua:jqlang/jq"
"platforms.$PLATFORM" = { url = "https://example.com/jq-1.7.1.tar.gz" }
EOF

MISE_LOCKFILE=0 assert_fail_contains "mise install --locked --dry-run 2>&1" "locked mode requires lockfile to be enabled"

# --- Test 5: locked setting via config with lockfile=false errors ---
# Unset MISE_LOCKFILE so the env var doesn't override the config file setting
unset MISE_LOCKFILE

cat <<'EOF' >mise.toml
[tools]
jq = "1.7.1"
[settings]
locked = true
lockfile = false
EOF

assert_fail_contains "mise install --dry-run 2>&1" "locked mode requires lockfile to be enabled"

# --- Test 6: locked=true with lockfile unset (not in config) works fine ---
cat <<'EOF' >mise.toml
[tools]
jq = "1.7.1"
[settings]
locked = true
EOF

cat <<EOF >mise.lock
[[tools.jq]]
version = "1.7.1"
backend = "aqua:jqlang/jq"
"platforms.$PLATFORM" = { url = "https://example.com/jq-1.7.1.tar.gz" }
EOF

assert_contains "mise install --dry-run 2>&1" "would install"
Comment thread
cursor[bot] marked this conversation as resolved.

# Restore for any subsequent tests
export MISE_LOCKFILE=1
1 change: 0 additions & 1 deletion schema/mise.json
Original file line number Diff line number Diff line change
Expand Up @@ -871,7 +871,6 @@
"type": "boolean"
},
"lockfile": {
"default": true,
"description": "Create and read lockfiles for tool versions.",
"type": "boolean"
},
Expand Down
6 changes: 5 additions & 1 deletion settings.toml
Original file line number Diff line number Diff line change
Expand Up @@ -925,7 +925,6 @@ env = "MISE_LOCKED"
type = "Bool"

[lockfile]
default = true
description = "Create and read lockfiles for tool versions."
docs = """
Read/update lockfiles for tool versions. This is useful when you'd like to have loose versions in mise.toml like this:
Expand All @@ -950,8 +949,13 @@ The lockfile is named the same as the config file but with `.lock` instead of `.
- `mise.toml` -> `mise.lock`
- `mise.local.toml` -> `mise.local.lock`
- `.config/mise.toml` -> `.config/mise.lock`

When set to `true`, lockfiles are read and written. When set to `false`, lockfiles are explicitly disabled—this
will cause an error if `locked = true` is also set, since locked mode requires reading lockfiles.
When unset (the default), lockfiles are enabled (same as `true`) but there is no conflict with `locked` mode.
"""
env = "MISE_LOCKFILE"
optional = true
type = "Bool"

[log_level]
Expand Down
4 changes: 2 additions & 2 deletions src/backend/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ impl HttpBackend {
) -> Result<()> {
let settings = Settings::get();
let filename = file_path.file_name().unwrap().to_string_lossy();
let lockfile_enabled = settings.lockfile;
let lockfile_enabled = settings.lockfile_enabled();

let platform_key = self.get_platform_key();
let platform_info = tv.lock_platforms.entry(platform_key).or_default();
Expand Down Expand Up @@ -652,7 +652,7 @@ impl Backend for HttpBackend {

// For lockfile checksum verification
let settings = Settings::get();
let lockfile_enabled = settings.lockfile;
let lockfile_enabled = settings.lockfile_enabled();
let has_lockfile_checksum = tv
.lock_platforms
.get(&platform_key)
Expand Down
11 changes: 9 additions & 2 deletions src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,13 @@ pub trait Backend: Debug + Send + Sync {
// Exempt tool stubs from lockfile requirements since they are ephemeral
// Also exempt backends that don't support URL locking (e.g., Rust uses rustup)
// This must run before the dry-run check so that --locked --dry-run still validates
let settings = Settings::get();
if (ctx.locked || settings.locked) && settings.lockfile == Some(false) {
bail!(
"locked mode requires lockfile to be enabled\n\
hint: Remove `lockfile = false` or set `lockfile = true`, or disable locked mode"
);
}
if ctx.locked && !tv.request.source().is_tool_stub() && self.supports_lockfile_url() {
let platform_key = self.get_platform_key();
let has_lockfile_url = tv
Expand Down Expand Up @@ -1335,7 +1342,7 @@ pub trait Backend: Debug + Send + Sync {
) -> Result<()> {
let settings = Settings::get();
let filename = file.file_name().unwrap().to_string_lossy().to_string();
let lockfile_enabled = settings.lockfile;
let lockfile_enabled = settings.lockfile_enabled();

// Get the platform key for this tool and platform
let platform_key = self.get_platform_key();
Expand Down Expand Up @@ -1517,7 +1524,7 @@ pub fn http_install_operation_count(
if has_checksum_opt {
count += 1;
}
let lockfile_enabled = settings.lockfile;
let lockfile_enabled = settings.lockfile_enabled();
let has_lockfile_checksum = tv
.lock_platforms
.get(platform_key)
Expand Down
4 changes: 2 additions & 2 deletions src/backend/s3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ impl S3Backend {
) -> Result<()> {
let settings = Settings::get();
let filename = file_path.file_name().unwrap().to_string_lossy();
let lockfile_enabled = settings.lockfile;
let lockfile_enabled = settings.lockfile_enabled();

let platform_key = self.get_platform_key();
let platform_info = tv.lock_platforms.entry(platform_key).or_default();
Expand Down Expand Up @@ -466,7 +466,7 @@ impl Backend for S3Backend {

// For lockfile checksum verification
let settings = Settings::get();
let lockfile_enabled = settings.lockfile;
let lockfile_enabled = settings.lockfile_enabled();
let has_lockfile_checksum = tv
.lock_platforms
.get(&platform_key)
Expand Down
2 changes: 1 addition & 1 deletion src/backend/ubi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ impl Backend for UbiBackend {
} else {
bail!("Invalid checksum: {platform_key}");
}
} else if Settings::get().lockfile {
} else if Settings::get().lockfile_enabled() {
ctx.pr
.set_message(format!("checksum generate {platform_key}"));
let hash = hash::file_hash_blake3(file, Some(ctx.pr.as_ref()))?;
Expand Down
4 changes: 4 additions & 0 deletions src/config/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,10 @@ impl Settings {
crate::config::config_file::config_root::reset();
}

pub fn lockfile_enabled(&self) -> bool {
self.lockfile.unwrap_or(true)
}

pub fn ensure_experimental(&self, what: &str) -> Result<()> {
if !self.experimental {
bail!("{what} is experimental. Enable it with `mise settings experimental=true`");
Expand Down
10 changes: 6 additions & 4 deletions src/lockfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ fn extract_env_from_config_path(path: &Path) -> Option<String> {
}

pub fn update_lockfiles(config: &Config, ts: &Toolset, new_versions: &[ToolVersion]) -> Result<()> {
if !Settings::get().lockfile || Settings::get().locked {
if !Settings::get().lockfile_enabled() || Settings::get().locked {
return Ok(());
}

Expand Down Expand Up @@ -677,7 +677,7 @@ fn determine_target_platforms_from_lockfile(lockfile: Option<&Lockfile>) -> Vec<
/// so the lockfile is complete and doesn't change when other developers on different
/// platforms run `mise install`.
pub async fn auto_lock_new_versions(_config: &Config, new_versions: &[ToolVersion]) -> Result<()> {
if !Settings::get().lockfile || Settings::get().locked || new_versions.is_empty() {
if !Settings::get().lockfile_enabled() || Settings::get().locked || new_versions.is_empty() {
return Ok(());
}

Expand Down Expand Up @@ -1066,7 +1066,8 @@ pub fn get_locked_version(
prefix: &str,
request_options: &BTreeMap<String, String>,
) -> Result<Option<LockfileTool>> {
if !Settings::get().lockfile {
let settings = Settings::get();
if !settings.lockfile_enabled() {
return Ok(None);
}

Expand Down Expand Up @@ -1145,7 +1146,8 @@ pub fn get_locked_version(
/// Get the backend for a tool from the lockfile, ignoring options.
/// This is used for backend discovery where we just need any entry's backend.
pub fn get_locked_backend(config: &Config, short: &str) -> Option<String> {
if !Settings::get().lockfile {
let settings = Settings::get();
if !settings.lockfile_enabled() {
return None;
}

Expand Down
Loading