Skip to content
Closed
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
66 changes: 66 additions & 0 deletions crates/surge-cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ mod tests {
};
use surge_core::config::manifest::{ShortcutLocation, SurgeManifest};
use surge_core::context::{StorageConfig, StorageProvider};
use surge_core::crypto::sha256::sha256_hex;
use surge_core::diff::chunked::{ChunkedDiffOptions, chunked_bsdiff};
use surge_core::diff::wrapper::bsdiff_buffers;
use surge_core::installer_bundle::read_embedded_payload;
Expand Down Expand Up @@ -776,6 +777,71 @@ apps:
assert_eq!(scoped_index.releases[0].name, "App A");
}

#[tokio::test]
async fn test_push_preserves_pack_recorded_full_encoding() {
let tmp = tempfile::tempdir().unwrap();
let store_dir = tmp.path().join("store");
let packages_dir = tmp.path().join("packages");
let manifest_path = tmp.path().join("surge.yml");
let rid = current_rid();
let app_id = "full-encoding-app";
let version = "1.0.0";
let full_package_bytes = b"full-package";
let full_package_key = format!("{app_id}-{version}-{rid}-full.tar.zst");

std::fs::create_dir_all(&store_dir).unwrap();
std::fs::create_dir_all(&packages_dir).unwrap();
write_manifest(&manifest_path, &store_dir, app_id, &rid);
std::fs::write(packages_dir.join(&full_package_key), full_package_bytes).unwrap();

let packed_index = ReleaseIndex {
app_id: app_id.to_string(),
releases: vec![ReleaseEntry {
version: version.to_string(),
channels: vec!["test".to_string()],
os: "linux".to_string(),
rid: rid.clone(),
is_genesis: false,
full_filename: full_package_key.clone(),
full_size: i64::try_from(full_package_bytes.len()).expect("fixture size fits in i64"),
full_sha256: sha256_hex(full_package_bytes),
full_compression_level: 3,
full_zstd_workers: 8,
deltas: Vec::new(),
preferred_delta_id: String::new(),
created_utc: String::new(),
release_notes: String::new(),
name: String::new(),
main_exe: "demoapp".to_string(),
install_directory: "demoapp".to_string(),
supervisor_id: String::new(),
icon: String::new(),
shortcuts: Vec::new(),
persistent_assets: Vec::new(),
installers: Vec::new(),
environment: BTreeMap::new(),
}],
..ReleaseIndex::default()
};
let packed_index_bytes = compress_release_index(&packed_index, DEFAULT_ZSTD_LEVEL).unwrap();
std::fs::write(store_dir.join(RELEASES_FILE_COMPRESSED), packed_index_bytes).unwrap();

super::push::execute(&manifest_path, Some(app_id), version, Some(&rid), "test", &packages_dir)
.await
.unwrap();

let index = read_index(&store_dir);
let release = index
.releases
.iter()
.find(|release| release.version == version && release.rid == rid)
.expect("pushed release should exist");
assert_eq!(release.full_compression_level, 3);
assert_eq!(release.full_zstd_workers, 8);
assert_eq!(release.full_filename, full_package_key);
assert_eq!(release.full_sha256, sha256_hex(full_package_bytes));
}

#[tokio::test]
async fn test_push_uploads_delta_only_after_first_full_for_rid() {
let tmp = tempfile::tempdir().unwrap();
Expand Down
38 changes: 32 additions & 6 deletions crates/surge-cli/src/commands/push.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,9 @@ async fn update_release_index(
let mut channels = BTreeSet::new();
channels.insert(channel.to_string());

let existing_full_encoding =
recorded_full_encoding_for_existing_release(&index, version, rid, &full_filename, full_size, &full_sha256);

for existing in &index.releases {
if existing.version == version && existing.rid == rid {
for existing_channel in &existing.channels {
Expand All @@ -305,12 +308,8 @@ async fn update_release_index(
full_filename,
full_size,
full_sha256,
// `surge push` uploads a pre-built archive without knowing how it was
// compressed, so we mark the encoding as unrecorded. Promotes of this
// release will have to fall back to `selected_delta` to recover the
// encoding (and error out if they can't), which is safer than guessing.
full_compression_level: UNRECORDED_COMPRESSION_LEVEL,
full_zstd_workers: UNRECORDED_ZSTD_WORKERS,
full_compression_level: existing_full_encoding.map_or(UNRECORDED_COMPRESSION_LEVEL, |encoding| encoding.0),
full_zstd_workers: existing_full_encoding.map_or(UNRECORDED_ZSTD_WORKERS, |encoding| encoding.1),
deltas: Vec::new(),
preferred_delta_id: String::new(),
created_utc: chrono::Utc::now().to_rfc3339(),
Expand Down Expand Up @@ -375,6 +374,33 @@ async fn update_release_index(
Ok(pruned)
}

fn recorded_full_encoding_for_existing_release(
index: &ReleaseIndex,
version: &str,
rid: &str,
full_filename: &str,
full_size: i64,
full_sha256: &str,
) -> Option<(i32, i32)> {
let existing = index
.releases
.iter()
.find(|release| release.version == version && release.rid == rid)?;

if existing.full_filename != full_filename || existing.full_size != full_size || existing.full_sha256 != full_sha256
{
return None;
}

if existing.full_compression_level == UNRECORDED_COMPRESSION_LEVEL
|| existing.full_zstd_workers == UNRECORDED_ZSTD_WORKERS
{
return None;
}

Some((existing.full_compression_level, existing.full_zstd_workers))
}

async fn prune_redundant_artifacts(backend: &dyn StorageBackend, index: &ReleaseIndex) -> Result<usize> {
let required = required_artifacts_for_index(index);

Expand Down