Skip to content

Commit

Permalink
feat(up): ✨ synchronize 'omni up' operations (#687)
Browse files Browse the repository at this point in the history
Until now, `omni up` could be run multiple times in parallel, this
also meant that if a repository is already being updated in the
background, it would be updated a second time synchronously by calling
`omni up`. This changes that by allowing any new `omni up` to attach
to a currently-running `omni up` operation.

This effectively allows to move all updates to be background-only
updates, as it is now possible for an up operation to run in the
background, but to be attached to the current process if necessary.

Closes #686
  • Loading branch information
XaF authored Sep 9, 2024
1 parent ed84298 commit 4666c72
Show file tree
Hide file tree
Showing 19 changed files with 914 additions and 62 deletions.
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,12 @@ tempfile = "3.12.0"
tera = "1.20.0"
term_cursor = "0.2.1"
term_size = "0.3.2"
thiserror = "1.0.63"
time = { version = "0.3.36", features = ["serde-well-known"] }
tokio = { version = "1.39.1", features = ["full"] }
url = "2.5.2"
uuid = { version = "1.10.0", features = ["v4", "fast-rng"] }
walkdir = "2.4.0"
which = "6.0.2"
whoami = "1.5.2"
zip-extract = "0.2.1"
7 changes: 7 additions & 0 deletions src/internal/commands/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -479,4 +479,11 @@ impl Command {

std::cmp::Ordering::Equal
}

pub fn requires_sync_update(&self) -> bool {
match self {
Command::FromPath(command) => command.requires_sync_update(),
_ => false,
}
}
}
210 changes: 177 additions & 33 deletions src/internal/commands/builtin/up.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ use crate::internal::config::up::utils::PrintProgressHandler;
use crate::internal::config::up::utils::ProgressHandler;
use crate::internal::config::up::utils::RunConfig;
use crate::internal::config::up::utils::SpinnerProgressHandler;
use crate::internal::config::up::utils::SyncUpdateInit;
use crate::internal::config::up::utils::SyncUpdateInitOption;
use crate::internal::config::up::utils::SyncUpdateListener;
use crate::internal::config::up::utils::SyncUpdateOperation;
use crate::internal::config::up::UpConfig;
use crate::internal::config::up::UpOptions;
use crate::internal::config::CommandSyntax;
Expand All @@ -38,12 +42,14 @@ use crate::internal::config::ConfigLoader;
use crate::internal::config::ConfigValue;
use crate::internal::config::SyntaxOptArg;
use crate::internal::env::shell_is_interactive;
use crate::internal::errors::SyncUpdateError;
use crate::internal::git::format_path_with_template;
use crate::internal::git::package_path_from_git_url;
use crate::internal::git::path_entry_config;
use crate::internal::git::safe_git_url_parse;
use crate::internal::git::ORG_LOADER;
use crate::internal::git_env;
use crate::internal::git_env_fresh;
use crate::internal::user_interface::StringColor;
use crate::internal::workdir;
use crate::internal::workdir::add_trust;
Expand Down Expand Up @@ -1168,6 +1174,71 @@ impl UpCommand {

updated
}

fn handle_suggestions(
&self,
suggest_config: Option<ConfigValue>,
suggest_clone: bool,
suggest_config_updated: bool,
suggest_clone_updated: bool,
options: &UpOptions,
) {
if let Some(suggested) = suggest_config {
self.suggest_config(suggested);
}

if suggest_clone {
self.suggest_clone();
}

if let Some(wd_id) = workdir(".").id() {
if suggest_config_updated || suggest_clone_updated {
self.handle_sync_operation(
SyncUpdateOperation::OmniInfo(format!(
"configuration suggestions for {} have an update",
wd_id.light_blue(),
)),
options,
);
self.handle_sync_operation(
SyncUpdateOperation::OmniInfo(format!(
"run {} to get the latest suggestions",
"omni up --bootstrap".light_yellow(),
)),
options,
);
}
}

self.handle_sync_operation(SyncUpdateOperation::Exit(0), options);
}

fn handle_sync_operation(&self, operation: SyncUpdateOperation, options: &UpOptions) {
if let Some(sync_file) = options.lock_file {
if let Err(err) = operation.dump_to_file(sync_file) {
omni_error!(format!("failed to write sync file: {}", err));
}
}

match operation {
SyncUpdateOperation::Init(init) => {
panic!("unexpected init message: {:?}", init);
}
SyncUpdateOperation::Exit(exit_code) => exit(exit_code),
SyncUpdateOperation::OmniWarning(message) => {
omni_warning!(message);
}
SyncUpdateOperation::OmniError(message) => {
omni_error!(message);
}
SyncUpdateOperation::OmniInfo(message) => {
omni_info!(message);
}
SyncUpdateOperation::Progress(progress) => {
panic!("unexpected progress message: {:?}", progress)
}
}
}
}

impl BuiltinCommand for UpCommand {
Expand Down Expand Up @@ -1333,7 +1404,7 @@ impl BuiltinCommand for UpCommand {
}

if !self.update_repository() {
if let (Some(wd_id), Some(git_commit)) = (wd.id(), git_env(".").commit()) {
if let (Some(wd_id), Some(git_commit)) = (wd.id(), git_env_fresh(".").commit()) {
if RepositoriesCache::get().check_fingerprint(
&wd_id,
"head_commit",
Expand Down Expand Up @@ -1454,6 +1525,65 @@ impl BuiltinCommand for UpCommand {
exit(1);
}

// Read the head commit of the repository
let head_commit = git_env_fresh(".").commit().map(|commit| commit.to_string());

// Prepare the sync command, so we can make sure we are listening to the correct operation
let sync_command = if self.is_up() {
let mut init_options = HashSet::new();
if suggest_config.is_some() {
init_options.insert(SyncUpdateInitOption::SuggestConfig);
}
if suggest_clone {
init_options.insert(SyncUpdateInitOption::SuggestClone);
}
SyncUpdateInit::Up(
head_commit.clone(),
init_options,
self.cli_args().cache_enabled,
)
} else {
SyncUpdateInit::Down(self.cli_args().cache_enabled)
};

// Prepare a listener in case the operation is already running
let mut listener = SyncUpdateListener::new();
listener.expect_init(&sync_command);

// Lock the update process to avoid running it multiple times in parallel
let lock_file = match workdir(".").lock_update(&mut listener) {
Ok(Some(lock_file)) => lock_file,
Ok(None) => {
// Nothing to do here, the update was done in an attached operation
exit(0);
}
Err(SyncUpdateError::MissingInitOptions) => {
// If we get here, it means we were attached to an `up` operation that successfully
// went through, but we have options (e.g. suggest config, suggest clone) that
// weren't present for the attached operation. We thus still need to handle those
self.handle_suggestions(
suggest_config,
suggest_clone,
suggest_config_updated,
suggest_clone_updated,
&UpOptions::new(),
);
exit(0);
}
Err(err) => {
omni_error!(format!("{}", err));
exit(1);
}
};

// If we're here, we've got the lock file, so let's dump the init information
if let Err(err) = SyncUpdateOperation::Init(sync_command).dump_to_file(&lock_file) {
omni_warning!(format!("failed to write sync file: {}", err));
}

// Prepare the options for the up command
let options = UpOptions::new().lock_file(&lock_file);

// No matter what's happening after, we want a clean cache for that
// repository, as we're rebuilding the up environment from scratch
UpConfig::clear_cache();
Expand All @@ -1473,59 +1603,73 @@ impl BuiltinCommand for UpCommand {
false
}
}) {
omni_warning!(format!("failed to update cache: {}", err));
self.handle_sync_operation(
SyncUpdateOperation::OmniWarning(format!(
"failed to update environment cache: {}",
err
)),
&options,
);
} else if env_vars.is_some() {
omni_info!(format!("Repository environment configured"));
self.handle_sync_operation(
SyncUpdateOperation::OmniInfo("Repository environment configured".to_string()),
&options,
);
}
}

// If it has an up configuration, handle it
if has_up_config {
let up_config = up_config.unwrap();

if self.is_up() {
let options = UpOptions::new()
let options = options
.clone()
.cache(self.cli_args().cache_enabled)
.fail_on_upgrade(self.cli_args().fail_on_upgrade);
if let Err(err) = up_config.up(&options) {
omni_error!(format!("issue while setting repo up: {}", err));
exit(1);
self.handle_sync_operation(
SyncUpdateOperation::OmniError(format!(
"issue while setting repo up: {}",
err
)),
&options,
);
self.handle_sync_operation(SyncUpdateOperation::Exit(1), &options);
}

if let (Some(wd_id), Some(git_commit)) = (wd.id(), git_env(".").commit()) {
if let (Some(wd_id), Some(git_commit)) = (wd.id(), head_commit) {
if let Err(err) = RepositoriesCache::exclusive(|repos| {
repos.update_fingerprint(&wd_id, "head_commit", fingerprint(&git_commit))
}) {
omni_warning!(format!("failed to update cache: {}", err));
self.handle_sync_operation(
SyncUpdateOperation::OmniWarning(format!(
"failed to update cache: {}",
err
)),
&options,
);
}
}
} else if let Err(err) = up_config.down() {
omni_error!(format!("issue while tearing repo down: {}", err));
exit(1);
}
}

if let Some(suggested) = suggest_config {
self.suggest_config(suggested);
}

if suggest_clone {
self.suggest_clone();
}

if let Some(wd_id) = wd.id() {
if suggest_config_updated || suggest_clone_updated {
omni_info!(format!(
"configuration suggestions for {} have an update",
wd_id.light_blue(),
));
omni_info!(format!(
"run {} to get the latest suggestions",
"omni up --bootstrap".light_yellow(),
));
} else if let Err(err) = up_config.down(&options) {
self.handle_sync_operation(
SyncUpdateOperation::OmniError(format!(
"issue while tearing repo down: {}",
err
)),
&options,
);
self.handle_sync_operation(SyncUpdateOperation::Exit(1), &options);
}
}

exit(0);
self.handle_suggestions(
suggest_config,
suggest_clone,
suggest_config_updated,
suggest_clone_updated,
&options,
);
}

fn autocompletion(&self) -> bool {
Expand Down
Loading

0 comments on commit 4666c72

Please sign in to comment.