diff --git a/crates/uv-resolver/src/exclude_newer.rs b/crates/uv-resolver/src/exclude_newer.rs index 6a666e5c797..ae9ed57ec4f 100644 --- a/crates/uv-resolver/src/exclude_newer.rs +++ b/crates/uv-resolver/src/exclude_newer.rs @@ -545,7 +545,9 @@ impl ExcludeNewer { match (&self.global, &other.global) { (Some(self_global), Some(other_global)) => { if let Some(change) = compare_exclude_newer_value(self_global, other_global) { - return Some(ExcludeNewerChange::GlobalChanged(change)); + if !change.is_relative_timestamp_change() { + return Some(ExcludeNewerChange::GlobalChanged(change)); + } } } (None, Some(global)) => { diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index ce4a200f453..90a575743f4 100644 --- a/crates/uv-resolver/src/lock/mod.rs +++ b/crates/uv-resolver/src/lock/mod.rs @@ -1161,10 +1161,18 @@ impl Lock { if !exclude_newer.is_empty() { // Always serialize global exclude-newer as a string if let Some(global) = &exclude_newer.global { - options_table.insert("exclude-newer", value(global.to_string())); - // Serialize the original span if present if let Some(span) = global.span() { + // When a relative span is present, write a no-op timestamp to avoid + // merge conflicts in the lockfile. In a future version of uv, we'll drop + // this field entirely but it's retained for backwards compatibility for now. + let mut noop = value("0001-01-01T00:00:00Z"); + if let Item::Value(ref mut v) = noop { + v.decor_mut().set_suffix(" # This has no effect and is included for backwards compatibility when using relative exclude-newer values."); + } + options_table.insert("exclude-newer", noop); options_table.insert("exclude-newer-span", value(span.to_string())); + } else { + options_table.insert("exclude-newer", value(global.to_string())); } } @@ -2349,10 +2357,13 @@ struct ExcludeNewerWire { impl From for ExcludeNewer { fn from(wire: ExcludeNewerWire) -> Self { + let global = match (wire.exclude_newer, wire.exclude_newer_span) { + (Some(timestamp), span) => Some(ExcludeNewerValue::new(timestamp, span)), + (None, Some(span)) => Some(ExcludeNewerValue::new(Timestamp::UNIX_EPOCH, Some(span))), + (None, None) => None, + }; Self { - global: wire - .exclude_newer - .map(|timestamp| ExcludeNewerValue::new(timestamp, wire.exclude_newer_span)), + global, package: wire.exclude_newer_package, } } diff --git a/crates/uv/tests/it/lock_exclude_newer_relative.rs b/crates/uv/tests/it/lock_exclude_newer_relative.rs index 08638743022..02fa57baa4d 100644 --- a/crates/uv/tests/it/lock_exclude_newer_relative.rs +++ b/crates/uv/tests/it/lock_exclude_newer_relative.rs @@ -48,7 +48,7 @@ fn lock_exclude_newer_relative() -> Result<()> { requires-python = ">=3.12" [options] - exclude-newer = "2024-04-10T00:00:00Z" + exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. exclude-newer-span = "P3W" [[package]] @@ -116,7 +116,7 @@ fn lock_exclude_newer_relative() -> Result<()> { requires-python = ">=3.12" [options] - exclude-newer = "2024-04-17T00:00:00Z" + exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. exclude-newer-span = "P2W" [[package]] @@ -165,7 +165,7 @@ fn lock_exclude_newer_relative() -> Result<()> { requires-python = ">=3.12" [options] - exclude-newer = "2024-05-18T00:00:00Z" + exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. exclude-newer-span = "P2W" [[package]] @@ -560,7 +560,7 @@ fn lock_exclude_newer_relative_pyproject() -> Result<()> { requires-python = ">=3.12" [options] - exclude-newer = "2024-04-10T00:00:00Z" + exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. exclude-newer-span = "P3W" [[package]] @@ -714,7 +714,7 @@ fn lock_exclude_newer_relative_global_and_package() -> Result<()> { requires-python = ">=3.12" [options] - exclude-newer = "2024-04-10T00:00:00Z" + exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. exclude-newer-span = "P3W" [options.exclude-newer-package] @@ -907,7 +907,7 @@ fn lock_exclude_newer_relative_global_and_package() -> Result<()> { requires-python = ">=3.12" [options] - exclude-newer = "2024-04-10T00:00:00Z" + exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. exclude-newer-span = "P3W" [options.exclude-newer-package] @@ -1235,7 +1235,7 @@ fn lock_exclude_newer_relative_no_timestamp_in_lockfile() -> Result<()> { requires-python = ">=3.12" [options] - exclude-newer = "2024-04-10T00:00:00Z" + exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. exclude-newer-span = "P3W" [[package]] @@ -1260,7 +1260,7 @@ fn lock_exclude_newer_relative_no_timestamp_in_lockfile() -> Result<()> { "#); // Manually remove the exclude-newer timestamp from the lockfile, leaving the span. - let lock = lock.replace("exclude-newer = \"2024-04-10T00:00:00Z\"\n", ""); + let lock = lock.replace("exclude-newer = \"0001-01-01T00:00:00Z\" # This has no effect and is included for backwards compatibility when using relative exclude-newer values.\n", ""); context.temp_dir.child("uv.lock").write_str(&lock)?; // The lockfile now has no exclude-newer, but `pyproject.toml` still configures one, @@ -1274,11 +1274,10 @@ fn lock_exclude_newer_relative_no_timestamp_in_lockfile() -> Result<()> { ----- stdout ----- ----- stderr ----- - Resolving despite existing lockfile due to addition of global exclude newer 2024-04-10T00:00:00Z Resolved 2 packages in [TIME] "); - // The lockfile should have exclude-newer restored. + // The lockfile retains the span but the timestamp is not restored or updated. let lock = context.read("uv.lock"); assert_snapshot!(lock, @r#" version = 1 @@ -1286,7 +1285,6 @@ fn lock_exclude_newer_relative_no_timestamp_in_lockfile() -> Result<()> { requires-python = ">=3.12" [options] - exclude-newer = "2024-04-10T00:00:00Z" exclude-newer-span = "P3W" [[package]]