in focus/commands/src/cli/main.rs [754:1325]
fn run_subcommand(app: Arc<App>, tracker: &Tracker, options: FocusOpts) -> Result<ExitCode> {
let cloned_app = app.clone();
let ti_client = cloned_app.tool_insights_client();
let feature_name = feature_name_for(&options.cmd);
ti_client.get_context().set_tool_feature_name(&feature_name);
let span = debug_span!("Running subcommand", ?feature_name);
let _guard = span.enter();
// This is to accomadate the special case for some maintenance run scheduled with launchd,
// We will want to validate the passed in git binary, not the one in app.
let run_preflight_check = match options.cmd {
Subcommand::Maintenance { ref subcommand, .. } => {
!matches!(subcommand, MaintenanceSubcommand::Run { .. })
}
_ => true,
};
if run_preflight_check && !preflight_check(app.clone())? {
return Ok(ExitCode(1));
};
if let Subcommand::Clone(_) = &options.cmd {
eprintln!(
"{}{}The command `focus clone` is deprecated; use `focus new` instead!{}",
style::Bold,
color::Fg(color::Yellow),
style::Reset,
);
}
match options.cmd {
Subcommand::New(NewArgs {
dense_repo,
sparse_repo,
branch,
days_of_history,
copy_branches,
projects_and_targets,
template,
})
| Subcommand::Clone(NewArgs {
dense_repo,
sparse_repo,
branch,
days_of_history,
copy_branches,
projects_and_targets,
template,
}) => {
let origin = focus_operations::clone::Origin::try_from(dense_repo.as_str())?;
let sparse_repo = {
let current_dir =
std::env::current_dir().context("Failed to obtain current directory")?;
let expanded = paths::expand_tilde(sparse_repo)
.context("Failed to expand sparse repo path")?;
current_dir.join(expanded)
};
info!("Cloning {:?} into {}", dense_repo, sparse_repo.display());
// Add targets length to TI custom map.
ti_client.get_context().add_to_custom_map(
"projects_and_targets_count",
projects_and_targets.len().to_string(),
);
let clone_args = CloneArgs {
origin: Some(origin),
branch,
days_of_history,
copy_branches,
projects_and_targets,
..Default::default()
};
focus_operations::clone::run(
sparse_repo.clone(),
clone_args,
template,
tracker,
app.clone(),
)?;
perform_pending_migrations(&sparse_repo, app)
.context("Performing initial migrations after clone")?;
Ok(ExitCode(0))
}
Subcommand::Sync {
sparse_repo,
one_shot,
} => {
// TODO: Add total number of paths in repo to TI.
let sparse_repo =
paths::find_repo_root_from(app.clone(), paths::expand_tilde(sparse_repo)?)?;
ensure_repo_compatibility(&sparse_repo, app.clone())?;
let _lock_file = hold_lock_file(&sparse_repo)?;
let mode = if one_shot {
SyncMode::OneShot
} else {
SyncMode::Incremental
};
focus_operations::sync::run(&SyncRequest::new(&sparse_repo, mode), app)?;
Ok(ExitCode(0))
}
Subcommand::Refs {
repo: repo_path,
subcommand,
} => {
let sparse_repo = paths::find_repo_root_from(app.clone(), repo_path)?;
let repo = Repository::open(sparse_repo).context("opening the repo")?;
match subcommand {
RefsSubcommand::Delete {
cutoff_date,
use_transaction,
check_merge_base,
} => {
let cutoff = FocusTime::parse_date(cutoff_date)?;
focus_operations::refs::expire_old_refs(
&repo,
cutoff,
check_merge_base,
use_transaction,
app,
)?;
Ok(ExitCode(0))
}
RefsSubcommand::ListExpired {
cutoff_date,
check_merge_base,
} => {
let cutoff = FocusTime::parse_date(cutoff_date)?;
let focus_operations::refs::PartitionedRefNames {
current: _,
expired,
} = focus_operations::refs::PartitionedRefNames::for_repo(
&repo,
cutoff,
check_merge_base,
)?;
println!("{}", expired.join("\n"));
Ok(ExitCode(0))
}
RefsSubcommand::ListCurrent {
cutoff_date,
check_merge_base,
} => {
let cutoff = FocusTime::parse_date(cutoff_date)?;
let focus_operations::refs::PartitionedRefNames {
current,
expired: _,
} = focus_operations::refs::PartitionedRefNames::for_repo(
&repo,
cutoff,
check_merge_base,
)?;
println!("{}", current.join("\n"));
Ok(ExitCode(0))
}
}
}
Subcommand::Branch {
subcommand,
repo,
remote_name,
} => {
let repo = paths::find_repo_root_from(app.clone(), repo)?;
match subcommand {
BranchSubcommand::List {} => {
focus_operations::branch::list(app, repo, &remote_name)?;
Ok(ExitCode(0))
}
BranchSubcommand::Search { search_term } => {
focus_operations::branch::search(app, repo, &remote_name, &search_term)?;
Ok(ExitCode(0))
}
BranchSubcommand::Add { name } => {
focus_operations::branch::add(app, repo, &remote_name, &name)
}
}
}
Subcommand::Repo { subcommand } => match subcommand {
RepoSubcommand::List {} => {
focus_operations::repo::list(tracker)?;
Ok(ExitCode(0))
}
RepoSubcommand::Repair {} => {
focus_operations::repo::repair(tracker, app)?;
Ok(ExitCode(0))
}
RepoSubcommand::Register { sparse_repo } => {
focus_operations::repo::register(sparse_repo, tracker, app)?;
Ok(ExitCode(0))
}
},
Subcommand::DetectBuildGraphChanges {
repo,
advisory,
args,
} => {
let repo = paths::find_repo_root_from(app.clone(), paths::expand_tilde(repo)?)?;
let repo = git_helper::find_top_level(app.clone(), &repo)
.context("Failed to canonicalize repo path")?;
focus_operations::detect_build_graph_changes::run(&repo, advisory, args, app)
}
Subcommand::Add {
projects_and_targets,
interactive,
search_all_targets,
unroll,
} => {
let sparse_repo = paths::find_repo_root_from(app.clone(), std::env::current_dir()?)?;
paths::assert_focused_repo(&sparse_repo)?;
let _lock_file = hold_lock_file(&sparse_repo)?;
if interactive {
focus_operations::selection::add_interactive(
&sparse_repo,
app,
search_all_targets,
unroll,
)?;
} else {
focus_operations::selection::add(
&sparse_repo,
true,
projects_and_targets,
unroll,
app,
)?;
}
Ok(ExitCode(0))
}
Subcommand::Remove {
projects_and_targets,
all,
} => {
let sparse_repo = paths::find_repo_root_from(app.clone(), std::env::current_dir()?)?;
let _lock_file = hold_lock_file(&sparse_repo)?;
focus_operations::selection::remove(
&sparse_repo,
true,
projects_and_targets,
all,
app,
)?;
Ok(ExitCode(0))
}
Subcommand::Status {
targets,
target_types,
} => {
let sparse_repo = paths::find_repo_root_from(app.clone(), std::env::current_dir()?)?;
focus_operations::status::run(&sparse_repo, app, targets, target_types)
}
Subcommand::Projects {} => {
let repo = git_helper::find_top_level(app.clone(), std::env::current_dir()?)
.context("Finding the top level of the repo")?;
focus_operations::selection::list_projects(&repo, app)?;
Ok(ExitCode(0))
}
Subcommand::Maintenance {
subcommand,
git_config_key,
} => match subcommand {
MaintenanceSubcommand::Run {
git_binary_path,
tracked,
git_config_path,
time_period,
} => {
let git_binary = GitBinary::from_binary_path(git_binary_path)?;
if !check_compatible_git_version(&git_binary)? {
return Ok(ExitCode(1));
}
focus_operations::maintenance::run(
focus_operations::maintenance::RunOptions {
git_binary: Some(git_binary),
git_config_key,
git_config_path,
tracked,
},
time_period,
tracker,
app,
)?;
sandbox::cleanup::run_with_default()?;
Ok(ExitCode(0))
}
MaintenanceSubcommand::Register {
repo_path,
git_config_path,
} => {
let repo_path = match repo_path {
Some(path) => Some(paths::find_repo_root_from(app, path)?),
None => None,
};
focus_operations::maintenance::register(
focus_operations::maintenance::RegisterOpts {
repo_path,
git_config_key,
global_config_path: git_config_path,
},
)?;
Ok(ExitCode(0))
}
MaintenanceSubcommand::SetDefaultConfig { .. } => {
focus_operations::maintenance::set_default_git_maintenance_config(
&paths::find_repo_root_from(app, std::env::current_dir()?)?,
)?;
Ok(ExitCode(0))
}
MaintenanceSubcommand::Schedule { subcommand } => match subcommand {
MaintenanceScheduleSubcommand::Enable {
time_period,
all,
focus_path,
git_binary_path,
force_reload,
tracked,
} => {
maintenance::schedule_enable(maintenance::ScheduleOpts {
time_period: if all { None } else { Some(time_period) },
git_path: git_binary_path,
focus_path: match focus_path {
Some(fp) => fp,
None => std::env::current_exe()
.context("could not determine current executable path")?,
},
skip_if_already_scheduled: !force_reload,
tracked,
})?;
Ok(ExitCode(0))
}
MaintenanceScheduleSubcommand::Disable { delete } => {
maintenance::schedule_disable(delete)?;
Ok(ExitCode(0))
}
},
MaintenanceSubcommand::SandboxCleanup {
preserve_hours,
max_num_sandboxes,
} => {
let config = sandbox::cleanup::Config {
preserve_hours: preserve_hours
.unwrap_or(sandbox::cleanup::Config::DEFAULT_HOURS),
max_num_sandboxes: max_num_sandboxes
.unwrap_or(sandbox::cleanup::Config::DEFAULT_MAX_NUM_SANDBOXES),
..sandbox::cleanup::Config::try_from_git_default()?
};
sandbox::cleanup::run(&config)?;
Ok(ExitCode(0))
}
},
Subcommand::GitTrace { input, output } => {
focus_tracing::Trace::git_trace_from(input)?.write_trace_json_to(output)?;
Ok(ExitCode(0))
}
Subcommand::Upgrade { repo } => {
focus_migrations::production::perform_pending_migrations(
paths::find_repo_root_from(app.clone(), repo)?.as_path(),
app,
)
.context("Failed to upgrade repo")?;
Ok(ExitCode(0))
}
Subcommand::Index { subcommand } => match subcommand {
IndexSubcommand::Clear { sparse_repo } => {
let sparse_repo = paths::find_repo_root_from(app, sparse_repo)?;
focus_operations::index::clear(sparse_repo)?;
Ok(ExitCode(0))
}
IndexSubcommand::CalculateChurn {
sparse_repo,
num_commits,
} => {
let sparse_repo_path = paths::find_repo_root_from(app.clone(), sparse_repo)?;
focus_operations::index::print_churn_stats(app, sparse_repo_path, num_commits)?;
Ok(ExitCode(0))
}
IndexSubcommand::Fetch {
sparse_repo,
force,
remote,
} => {
let sparse_repo = paths::find_repo_root_from(app.clone(), sparse_repo)?;
let exit_code = focus_operations::index::fetch(app, sparse_repo, force, remote)?;
Ok(exit_code)
}
IndexSubcommand::Generate {
sparse_repo,
break_on_missing_keys,
} => {
let sparse_repo = paths::find_repo_root_from(app.clone(), sparse_repo)?;
let exit_code =
focus_operations::index::generate(app, sparse_repo, break_on_missing_keys)?;
Ok(exit_code)
}
IndexSubcommand::Get { target } => {
let sparse_repo = paths::find_repo_root_from(app.clone(), PathBuf::from("."))?;
let exit_code = focus_operations::index::get(app, &sparse_repo, &target)?;
Ok(exit_code)
}
IndexSubcommand::Hash { commit, targets } => {
let sparse_repo = paths::find_repo_root_from(app.clone(), PathBuf::from("."))?;
let exit_code = focus_operations::index::hash(app, &sparse_repo, commit, &targets)?;
Ok(exit_code)
}
IndexSubcommand::Push {
sparse_repo,
remote,
dry_run,
break_on_missing_keys,
} => {
let sparse_repo = paths::find_repo_root_from(app.clone(), sparse_repo)?;
let exit_code = focus_operations::index::push(
app,
sparse_repo,
remote,
dry_run,
break_on_missing_keys,
)?;
Ok(exit_code)
}
IndexSubcommand::Resolve {
targets,
break_on_missing_keys,
} => {
let sparse_repo = paths::find_repo_root_from(app.clone(), PathBuf::from("."))?;
let exit_code = focus_operations::index::resolve(
app,
&sparse_repo,
targets,
break_on_missing_keys,
)?;
Ok(exit_code)
}
},
Subcommand::ProjectCache { subcommand } => match subcommand {
ProjectCacheSubcommand::Push {
sparse_repo,
commit,
shard_index,
shard_count,
} => {
let sparse_repo = paths::find_repo_root_from(app.clone(), sparse_repo)?;
let exit_code = focus_operations::project_cache::push(
app,
sparse_repo,
commit,
shard_index,
shard_count,
)?;
Ok(exit_code)
}
},
Subcommand::Project { subcommand } => match subcommand {
ProjectSubcommand::Lint {} => {
let repo = git_helper::find_top_level(app.clone(), std::env::current_dir()?)
.context("Finding the top level of the repo")?;
lint(&repo, app)
}
},
Subcommand::Event { args: _ } => Ok(ExitCode(0)),
Subcommand::Version => {
println!("package-name: {}", env!("CARGO_PKG_NAME"));
println!("build-version: {}", env!("VERGEN_BUILD_SEMVER"));
println!("commit-timestamp: {}", env!("VERGEN_GIT_COMMIT_TIMESTAMP"));
println!("commit-sha: {}", env!("VERGEN_GIT_SHA"));
println!("cargo-features: {}", env!("VERGEN_CARGO_FEATURES"));
println!(
"twttr-enabled: {}",
env!("VERGEN_CARGO_FEATURES")
.split(',')
.any(|feature| feature == "twttr")
);
Ok(ExitCode(0))
}
Subcommand::Background { subcommand } => match subcommand {
BackgroundSubcommand::Enable {
sparse_repo,
idle_period_ms,
} => {
let sparse_repo = paths::find_repo_root_from(app.clone(), sparse_repo)?;
focus_operations::background::enable(app, sparse_repo, idle_period_ms)
}
BackgroundSubcommand::Disable { sparse_repo } => {
let sparse_repo = paths::find_repo_root_from(app.clone(), sparse_repo)?;
focus_operations::background::disable(app, sparse_repo)
}
BackgroundSubcommand::Sync { sparse_repo } => {
let sparse_repo = paths::find_repo_root_from(app.clone(), sparse_repo)?;
focus_operations::background::sync(app, sparse_repo)
}
},
Subcommand::Pull => {
let sparse_repo = paths::find_repo_root_from(app.clone(), std::env::current_dir()?)?;
focus_operations::pull::run(app, sparse_repo)
}
Subcommand::Selection { subcommand } => match subcommand {
SelectionSubcommand::Save {
project_name,
project_file,
project_description,
} => {
let sparse_repo =
paths::find_repo_root_from(app.clone(), std::env::current_dir()?)?;
save(
&sparse_repo,
project_name,
project_file,
project_description,
app,
)?;
Ok(ExitCode(0))
}
},
Subcommand::On { run_sync } => {
let sparse_repo = paths::find_repo_root_from(app.clone(), std::env::current_dir()?)?;
let _lock_file = hold_lock_file(&sparse_repo)?;
focus_operations::filter::run(sparse_repo, app, true, run_sync)
}
Subcommand::Off {} => {
let sparse_repo = paths::find_repo_root_from(app.clone(), std::env::current_dir()?)?;
let _lock_file = hold_lock_file(&sparse_repo)?;
focus_operations::filter::run(sparse_repo, app, false, false)
}
}
}