fn run_subcommand()

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)
        }
    }
}