Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
e44b908
Integrate full package version history management into PackageRegistr…
chrisgitiota Jul 16, 2025
565a4b3
New package-history CLI tool to automatically create and update `Move…
chrisgitiota Jul 16, 2025
56c77e5
Merge branch 'main' into feat/full-package-history
chrisgitiota Jul 28, 2025
0692dbe
Use JSON format instead of TOML for the Move.package-history file
chrisgitiota Jul 28, 2025
3d39d3c
Fixed package_history_cli tests
chrisgitiota Aug 4, 2025
dfc88ce
Remove dead code
chrisgitiota Aug 4, 2025
69ca4cc
Rename `Move.package-history.json` to `Move.history.json`
chrisgitiota Aug 7, 2025
30abbcc
product_common now provides the functionality, previously implemented…
chrisgitiota Aug 8, 2025
29ca4a6
MoveHistoryManager holds move_lock_path and history_file_path now and…
chrisgitiota Aug 8, 2025
4c61d8d
Add function move_lock_file_exists()
chrisgitiota Aug 11, 2025
39aa898
Add function manage_history_file() providing main functionality neede…
chrisgitiota Aug 11, 2025
dc77438
fix fmt issues
chrisgitiota Aug 11, 2025
26a2f7d
fix warnings "hiding a lifetime that's elided elsewhere is confusing"
chrisgitiota Aug 11, 2025
01f1e11
fix bug in `console_out` messages of fn manage_history_file()
chrisgitiota Aug 11, 2025
d8a6b19
Updated MoveHistoryManager docs
chrisgitiota Aug 11, 2025
1bf1bd1
fix fmt issue
chrisgitiota Aug 11, 2025
ca6c8ca
Enhance MoveHistoryManager docs
chrisgitiota Aug 11, 2025
d9db850
fix fmt issue
chrisgitiota Aug 11, 2025
dae0cf5
ignore the build.rs example in rust doctest
chrisgitiota Aug 11, 2025
d854daf
Further enhance the docs for MoveHistoryManager and PackageRegistry
chrisgitiota Aug 12, 2025
ededffe
Merge branch 'main' into feat/full-package-history
chrisgitiota Aug 14, 2025
5363824
Remove doubled PackageRegistry struct definition
chrisgitiota Aug 14, 2025
e5e5e4a
Several enhancements
chrisgitiota Aug 14, 2025
6a08643
Hand over errors from `update()` or `init()` to users of `manage_hist…
chrisgitiota Aug 14, 2025
6c6295b
Replace `aliases_to_ignore` with `additional_aliases_to_watch` and a …
chrisgitiota Sep 23, 2025
12f1d50
Merge branch 'main' into feat/full-package-history
chrisgitiota Sep 23, 2025
23acc3a
Fix clippy anf fmt issues
chrisgitiota Sep 29, 2025
f7e189e
Merge branch 'main' into feat/full-package-history
chrisgitiota Sep 29, 2025
7dccb6f
Aline indexmap dependency with iota repo requirements
chrisgitiota Sep 29, 2025
c4d304e
Aligne indexmap dependency with iota repo requirements
chrisgitiota Sep 29, 2025
e5b8861
Reduce nesting with early returns
chrisgitiota Sep 29, 2025
1a388a6
Merge remote-tracking branch 'origin/feat/full-package-history' into …
chrisgitiota Sep 29, 2025
86c15a0
Fix fmt issue
chrisgitiota Sep 29, 2025
e2dfbd5
Merge branch 'main' into feat/full-package-history
chrisgitiota Nov 5, 2025
7db17e2
Fix typo
chrisgitiota Nov 5, 2025
86fd0a7
Fix bug in alignment of indexmap dependency version with IOTA repository
chrisgitiota Nov 5, 2025
4ed04e7
Add missing whitespace in Success message
chrisgitiota Nov 5, 2025
ec679a0
Implement alias management in PackageRegistry and MoveHistoryManager
itsyaasir Nov 5, 2025
afe7517
Add tests for alias synchronization in MoveHistoryManager
itsyaasir Nov 5, 2025
56aa4ce
Additional HstoryManager test update_syncs_only_aliases_included_in_a…
chrisgitiota Nov 5, 2025
e885380
Extended docs on fn PackageRegistry::insert_new_package_version()
chrisgitiota Nov 5, 2025
2c59b3c
chore:fmt
itsyaasir Nov 6, 2025
219bea6
Merge pull request #71 from iotaledger/fix/issue-4-aliases-are-incons…
itsyaasir Nov 6, 2025
aca4a79
Merge branch 'main' into feat/full-package-history
chrisgitiota Nov 10, 2025
9e17995
Documented edge cases in `Move.history.json` handling for network res…
chrisgitiota Nov 10, 2025
0dc97cd
Some MoveHistoryManager doc fixes
chrisgitiota Nov 10, 2025
6344b38
New test to explicitly test redeploymentents due to breaking changes
chrisgitiota Nov 10, 2025
93eaf35
Fix format issues
chrisgitiota Nov 10, 2025
16ba2eb
chore: ignore incase move lock doesn't have env (#72)
itsyaasir Nov 10, 2025
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
Prev Previous commit
Next Next commit
MoveHistoryManager holds move_lock_path and history_file_path now and…
… can ignore envs specified by their alias
  • Loading branch information
chrisgitiota committed Aug 8, 2025
commit 29ca4a62f44831029c4c49601e0d244d9fb27897
163 changes: 139 additions & 24 deletions product_common/src/move_history_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

use std::fs;
use std::path::Path;
use std::path::{Path, PathBuf};

use anyhow::Context;

Expand All @@ -23,7 +23,16 @@ fn to_prettified_string(registry: &PackageRegistry) -> anyhow::Result<String> {
}

impl PackageRegistry {
/// Creates a [PackageRegistry] from a Move.lock file.
/// Creates a [`PackageRegistry`] from the content of a `Move.lock` file.
///
/// # Arguments
/// * `move_lock` - A string containing the content of the `Move.lock` file.
///
/// # Returns
/// A `PackageRegistry` instance populated with data from the `Move.lock` file.
///
/// # Errors
/// Returns an error if the `Move.lock` file content is invalid or cannot be parsed.
pub fn from_move_lock_content(move_lock: &str) -> anyhow::Result<Self> {
let mut move_lock: toml::Table = move_lock.parse()?;

Expand Down Expand Up @@ -75,41 +84,98 @@ impl PackageRegistry {
}
}

pub struct MoveHistoryManager {}
/// Manages the history of Move packages, including initialization and updates.
pub struct MoveHistoryManager {
move_lock_path: PathBuf,
history_file_path: PathBuf,
aliases_to_ignore: Vec<String>,
}

impl MoveHistoryManager {
/// Creates a new `MoveHistoryManager` instance.
///
/// # Arguments
/// * `move_lock_path` - Path to the `Move.lock` file.
/// * `history_file_path` - Path to the `M̀ove.history.toml` file.
/// * `aliases_to_ignore` - Optional list of environment aliases to ignore.
/// If `aliases_to_ignore` is not provided, it defaults to `["localnet"]`.
///
/// # Returns
/// A new `MoveHistoryManager` instance.
///
/// Doesn't check if any of the provided paths are invalid or if the `Move.lock` file cannot be parsed.
/// Functions `init` and `update` will handle those checks.
pub fn new(move_lock_path: &Path, history_file_path: &Path, aliases_to_ignore: Option<Vec<String>>) -> Self {
let aliases_to_ignore = aliases_to_ignore
.unwrap_or(vec!["localnet".to_string()]);
Self {
move_lock_path: move_lock_path.to_owned(),
history_file_path: history_file_path.to_owned(),
aliases_to_ignore,
}
}

/// Checks if the Move.history.json file exists.
pub fn history_file_exists(&self) -> bool {
self.history_file_path.exists() && self.history_file_path.is_file()
}

/// Returns the list of environment aliases to ignore.
pub fn aliases_to_ignore(&self) -> &[String] {
&self.aliases_to_ignore
}

/// Returns the path to the Move.lock file.
pub fn move_lock_path(&self) -> &Path {
&self.move_lock_path
}

/// Returns the path to the Move.history.json file.
pub fn history_file_path(&self) -> &Path {
&self.history_file_path
}

/// Creates an initial Move.history.json file from a Move.lock file
pub fn init(move_lock_path: &Path, output_path: &Path) -> anyhow::Result<()> {
let move_lock_content = fs::read_to_string(move_lock_path)
.with_context(|| format!("Failed to read Move.lock file: {}", move_lock_path.display()))?;
/// Will ignore any environment aliases specified in `aliases_to_ignore`.
pub fn init(&self) -> anyhow::Result<()> {
let move_lock_content = fs::read_to_string(&self.move_lock_path)
.with_context(|| format!("Failed to read Move.lock file: {}", &self.move_lock_path.display()))?;

let registry =
let mut registry =
PackageRegistry::from_move_lock_content(&move_lock_content).context("Failed to parse Move.lock file")?;

for alias in self.aliases_to_ignore.iter() {
let _ = registry.remove_env_by_alias(alias);
}

let json_content = to_prettified_string(&registry)?;

fs::write(output_path, json_content)
.with_context(|| format!("Failed to write to output file: {}", output_path.display()))?;
fs::write(&self.history_file_path, json_content)
.with_context(|| format!("Failed to write to output file: {}", self.history_file_path.display()))?;

Ok(())
}

/// Updates an existing Move.history.json file with new package versions from a Move.lock file
pub fn update(history_file_path: &Path, move_lock_path: &Path) -> anyhow::Result<()> {
pub fn update(&self) -> anyhow::Result<()> {
// Read and deserialize existing package history
let history_content = fs::read_to_string(history_file_path)
.with_context(|| format!("Failed to read Move.history.json file: {}", history_file_path.display()))?;
let history_content = fs::read_to_string(&self.history_file_path)
.with_context(|| format!("Failed to read Move.history.json file: {}", self.history_file_path.display()))?;

let mut registry = PackageRegistry::from_package_history_json_str(&history_content)
.context("Failed to parse existing Move.history.json file")?;

// Read and parse Move.lock file
let move_lock_content = fs::read_to_string(move_lock_path)
.with_context(|| format!("Failed to read Move.lock file: {}", move_lock_path.display()))?;
let move_lock_content = fs::read_to_string(&self.move_lock_path)
.with_context(|| format!("Failed to read Move.lock file: {}", self.move_lock_path.display()))?;

let new_registry =
let mut new_registry =
PackageRegistry::from_move_lock_content(&move_lock_content).context("Failed to parse Move.lock file")?;

for alias in self.aliases_to_ignore.iter() {
let _ = new_registry.remove_env_by_alias(alias);
}

// Add new package versions from Move.lock to existing registry
for (chain_id, versions) in new_registry.envs().iter() {
if let Some(latest_version) = versions.last() {
Expand All @@ -120,8 +186,8 @@ impl MoveHistoryManager {
// Serialize and write updated registry
let updated_json_content = to_prettified_string(&registry)?;

fs::write(history_file_path, updated_json_content)
.with_context(|| format!("Failed to write updated content to: {}", history_file_path.display()))?;
fs::write(&self.history_file_path, updated_json_content)
.with_context(|| format!("Failed to write updated content to: {}", self.history_file_path.display()))?;

Ok(())
}
Expand All @@ -146,6 +212,11 @@ latest-published-id = "0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054
chain-id = "2304aa97"
original-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555"
latest-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555"

[env.localnet]
chain-id = "ecc0606a"
original-published-id = "0xfbddb4631d027b2c4f0b4b90c020713d258ed32bdb342b5397f4da71edb7478b"
latest-published-id = "0xfbddb4631d027b2c4f0b4b90c020713d258ed32bdb342b5397f4da71edb7478b"
"#
.to_string()
}
Expand Down Expand Up @@ -174,19 +245,22 @@ latest-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864a

fs::write(&move_lock_path, create_test_move_lock()).unwrap();

MoveHistoryManager::init(&move_lock_path, &output_path).unwrap();
MoveHistoryManager::new(&move_lock_path, &output_path, None).init().unwrap();

assert!(output_path.exists());
let content = fs::read_to_string(&output_path).unwrap();
assert!(content.contains("\"aliases\": {"));
assert!(content.contains("\"mainnet\": \"6364aad5\""));
assert!(content.contains("\"testnet\": \"2304aa97\""));
assert!(!content.contains("\"localnet\": \"ecc0606a\""));

assert!(content.contains("\"envs\": {"));
assert!(content.contains("\"2304aa97\": ["));
assert!(content.contains("\"0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555\""));
assert!(content.contains("\"6364aad5\": ["));
assert!(content.contains("\"0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed2054ad931de08\""));
assert!(!content.contains("\"ecc0606a\": ["));
assert!(!content.contains("\"0xfbddb4631d027b2c4f0b4b90c020713d258ed32bdb342b5397f4da71edb7478b\""));
}

#[test]
Expand All @@ -195,7 +269,7 @@ latest-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864a
let move_lock_path = temp_dir.path().join("nonexistent.lock");
let output_path = temp_dir.path().join("output.json");

let result = MoveHistoryManager::init(&move_lock_path, &output_path);
let result = MoveHistoryManager::new(&move_lock_path, &output_path, None).init();
assert!(result.is_err());
}

Expand All @@ -217,16 +291,22 @@ original-published-id = "0x84cf5d12de2f9731a89bb519bc0c982a941b319a33abefdd5ed20
chain-id = "2304aa97"
latest-published-id = "0x332741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc666"
original-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac864ab4e4cc555"

[env.localnet]
chain-id = "ecc0606a"
original-published-id = "0xfbddb4631d027b2c4f0b4b90c020713d258ed32bdb342b5397f4da71edb7478b"
latest-published-id = "0x0d88bcecde97585d50207a029a85d7ea0bacf73ab741cbaa975a6e279251033a"
"#;
fs::write(&move_lock_path, updated_move_lock).unwrap();

MoveHistoryManager::update(&history_path, &move_lock_path).unwrap();
MoveHistoryManager::new(&move_lock_path, &history_path, None).update().unwrap();

let updated_content = fs::read_to_string(&history_path).unwrap();
let registry = PackageRegistry::from_package_history_json_str(&updated_content).unwrap();

assert_eq!(registry.history("6364aad5").unwrap().len(), 2);
assert_eq!(registry.history("2304aa97").unwrap().len(), 2);
assert_eq!(registry.history("ecc0606a"), None);
}

#[test]
Expand All @@ -237,7 +317,7 @@ original-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac86

fs::write(&move_lock_path, create_test_move_lock()).unwrap();

let result = MoveHistoryManager::update(&history_path, &move_lock_path);
let result = MoveHistoryManager::new(&move_lock_path, &history_path, None).update();
assert!(result.is_err());
}

Expand All @@ -249,8 +329,7 @@ original-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac86

fs::write(&history_path, create_test_package_history()).unwrap();
fs::write(&move_lock_path, create_test_move_lock()).unwrap();

MoveHistoryManager::update(&history_path, &move_lock_path).unwrap();
MoveHistoryManager::new(&move_lock_path, &history_path, None).update().unwrap();

let updated_content = fs::read_to_string(&history_path).unwrap();
let registry = PackageRegistry::from_package_history_json_str(&updated_content).unwrap();
Expand All @@ -259,4 +338,40 @@ original-published-id = "0x222741bbdff74b42df48a7b4733185e9b24becb8ccfbafe8eac86
assert_eq!(registry.history("6364aad5").unwrap().len(), 1);
assert_eq!(registry.history("2304aa97").unwrap().len(), 1);
}
}

#[test]
fn history_file_exists_returns_true_when_file_exists() {
let temp_dir = TempDir::new().unwrap();
let move_lock_path = temp_dir.path().join("Move.lock");
let history_path = temp_dir.path().join("Move.history.json");

// Create the history file
fs::write(&history_path, "{}").unwrap();

let manager = MoveHistoryManager::new(&move_lock_path, &history_path, None);
assert!(manager.history_file_exists());
}

#[test]
fn history_file_exists_returns_false_when_file_does_not_exist() {
let temp_dir = TempDir::new().unwrap();
let move_lock_path = temp_dir.path().join("Move.lock");
let history_path = temp_dir.path().join("nonexistent.json");

let manager = MoveHistoryManager::new(&move_lock_path, &history_path, None);
assert!(!manager.history_file_exists());
}

#[test]
fn history_file_exists_returns_false_when_path_is_directory() {
let temp_dir = TempDir::new().unwrap();
let move_lock_path = temp_dir.path().join("Move.lock");
let history_path = temp_dir.path().join("directory");

// Create a directory instead of a file
fs::create_dir(&history_path).unwrap();

let manager = MoveHistoryManager::new(&move_lock_path, &history_path, None);
assert!(!manager.history_file_exists());
}
}
10 changes: 10 additions & 0 deletions product_common/src/package_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,16 @@ impl PackageRegistry {
.iter()
.find_map(|(alias, chain)| (chain == chain_id).then_some(alias.as_str()))
}

/// Removes the environment specified by the alias from the registry.
/// Returns the removed environment's versions if it existed, or `None` if the alias was not found.
pub fn remove_env_by_alias(&mut self, alias: &str) -> Option<Vec<ObjectID>> {
if let Some(chain_id) = self.aliases.remove(alias) {
self.envs.remove(&chain_id)
} else {
None
}
}

/// Returns the envs of this package registry.
pub fn envs(&self) -> &HashMap<String, Vec<ObjectID>> {
Expand Down
Loading