#include #include #include "../subcommand/checkout_subcommand.hpp" #include "../utils/git_exception.hpp" #include "../wrapper/repository_wrapper.hpp" checkout_subcommand::checkout_subcommand(const libgit2_object&, CLI::App& app) { auto* sub = app.add_subcommand("checkout", "Switch branches or restore working tree files"); sub->add_option("", m_branch_name, "Branch to checkout"); sub->add_flag("-b", m_create_flag, "Create a new branch before checking it out"); sub->add_flag("-B", m_force_create_flag, "Create a new branch or reset it if it exists before checking it out"); sub->add_flag("-f, --force", m_force_checkout_flag, "When switching branches, proceed even if the index or the working tree differs from HEAD, and even if there are untracked files in the way"); sub->callback([this]() { this->run(); }); } void checkout_subcommand::run() { auto directory = get_current_git_path(); auto repo = repository_wrapper::open(directory); if (repo.state() != GIT_REPOSITORY_STATE_NONE) { throw std::runtime_error("Cannot checkout, repository is in unexpected state"); } git_checkout_options options; git_checkout_options_init(&options, GIT_CHECKOUT_OPTIONS_VERSION); if(m_force_checkout_flag) { options.checkout_strategy = GIT_CHECKOUT_FORCE; } if (m_create_flag || m_force_create_flag) { auto annotated_commit = create_local_branch(repo, m_branch_name, m_force_create_flag); checkout_tree(repo, annotated_commit, m_branch_name, options); update_head(repo, annotated_commit, m_branch_name); } else { auto optional_commit = resolve_local_ref(repo, m_branch_name); if (!optional_commit) { // TODO: handle remote refs std::ostringstream buffer; buffer << "error: could not resolve pathspec '" << m_branch_name << "'" << std::endl; throw std::runtime_error(buffer.str()); } checkout_tree(repo, *optional_commit, m_branch_name, options); update_head(repo, *optional_commit, m_branch_name); } } std::optional checkout_subcommand::resolve_local_ref ( const repository_wrapper& repo, const std::string& target_name ) { if (auto ref = repo.find_reference_dwim(target_name)) { return repo.find_annotated_commit(*ref); } else if (auto obj = repo.revparse_single(target_name)) { return repo.find_annotated_commit(obj->oid()); } else { return std::nullopt; } } annotated_commit_wrapper checkout_subcommand::create_local_branch ( repository_wrapper& repo, const std::string& target_name, bool force ) { auto branch = repo.create_branch(target_name, force); return repo.find_annotated_commit(branch); } void checkout_subcommand::checkout_tree ( const repository_wrapper& repo, const annotated_commit_wrapper& target_annotated_commit, const std::string& target_name, const git_checkout_options& options ) { auto target_commit = repo.find_commit(target_annotated_commit.oid()); throw_if_error(git_checkout_tree(repo, target_commit, &options)); } void checkout_subcommand::update_head ( repository_wrapper& repo, const annotated_commit_wrapper& target_annotated_commit, const std::string& target_name ) { std::string_view annotated_ref = target_annotated_commit.reference_name(); if (!annotated_ref.empty()) { auto ref = repo.find_reference(annotated_ref); if (ref.is_remote()) { auto branch = repo.create_branch(target_name, target_annotated_commit); repo.set_head(branch.reference_name()); } else { repo.set_head(annotated_ref); } } else { repo.set_head_detached(target_annotated_commit); } }