| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,10 @@ | ||
| #include "../subcommand/checkout_subcommand.hpp" | ||
|
|
||
| #include <filesystem> | ||
| #include <iostream> | ||
| #include <set> | ||
| #include <sstream> | ||
|
|
||
| #include <git2/oid.h> | ||
|
|
||
| #include "../subcommand/status_subcommand.hpp" | ||
| #include "../utils/git_exception.hpp" | ||
| Expand All | @@ -13,7 +15,10 @@ 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("<branch>", m_branch_name, "Branch to checkout"); | ||
| // "-- file" lands in m_positional_args because CLI11 consumes "--" silently. | ||
| sub->add_option("<tree-ish|pathspec>", m_positional_args, "Tree-ish to checkout, and/or one/many pathspec(s)"); | ||
| // checkout <branch>, checkout <tag>, checkout <file> ..., checkout <branch> <file> ... | ||
| // Use without "--" | ||
| 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( | ||
| Expand Down Expand Up | @@ -51,6 +56,56 @@ namespace | |
| } | ||
| } | ||
|
|
||
| void checkout_subcommand::checkout_files( | ||
| const repository_wrapper& repo, | ||
| const std::vector<std::string>& files, | ||
| const git_checkout_options& base_options | ||
| ) | ||
| { | ||
| std::vector<const char*> pathspec_strings; | ||
| pathspec_strings.reserve(files.size()); | ||
| for (const auto& f : files) | ||
| { | ||
| pathspec_strings.push_back(f.c_str()); | ||
| } | ||
|
Comment thread
JohanMabille marked this conversation as resolved.
|
||
|
|
||
| git_checkout_options options = base_options; | ||
| options.paths.strings = const_cast<char**>(pathspec_strings.data()); | ||
| options.paths.count = pathspec_strings.size(); | ||
|
|
||
| throw_if_error(git_checkout_head(repo, &options)); | ||
| } | ||
|
|
||
| void checkout_subcommand::checkout_paths( | ||
|
Comment thread
Outdated
Copy link
Copy Markdown
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityCould be renamed checkout_tree_files or checkout_ref_files
Sorry, something went wrong.
All reactions
|
||
| const repository_wrapper& repo, | ||
| const std::string_view tree_ish, | ||
| const std::vector<std::string>& pathspecs, | ||
| const git_checkout_options& base_options | ||
| ) | ||
| { | ||
| auto obj = repo.revparse_single(tree_ish); | ||
| if (!obj) | ||
| { | ||
| throw git_exception( | ||
| "error: could not resolve tree-ish '" + std::string(tree_ish) + "'", | ||
| git2cpp_error_code::BAD_ARGUMENT | ||
| ); | ||
| } | ||
|
|
||
| std::vector<const char*> pathspec_strings; | ||
| pathspec_strings.reserve(pathspecs.size()); | ||
| for (const auto& p : pathspecs) | ||
| { | ||
| pathspec_strings.push_back(p.c_str()); | ||
| } | ||
|
|
||
| git_checkout_options options = base_options; | ||
| options.paths.strings = const_cast<char**>(pathspec_strings.data()); | ||
| options.paths.count = pathspec_strings.size(); | ||
|
|
||
| throw_if_error(git_checkout_tree(repo, *obj, &options)); | ||
| } | ||
|
|
||
| void checkout_subcommand::run() | ||
| { | ||
| auto directory = get_current_git_path(); | ||
| Expand All | @@ -73,57 +128,134 @@ void checkout_subcommand::run() | |
| options.checkout_strategy = GIT_CHECKOUT_SAFE; | ||
| } | ||
|
|
||
| if (m_create_flag || m_force_create_flag) | ||
| if (m_positional_args.empty()) | ||
| { | ||
| 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); | ||
|
|
||
| std::cout << "Switched to a new branch '" << m_branch_name << "'" << std::endl; | ||
| throw std::runtime_error("error: no branch or file specified"); | ||
| } | ||
| else | ||
|
|
||
| const std::string& target_name = m_positional_args[0]; // can be a branch or a tag | ||
| const std::vector<std::string> pathspecs(m_positional_args.begin() + 1, m_positional_args.end()); | ||
|
|
||
| if (m_create_flag || m_force_create_flag) | ||
| { | ||
| auto optional_commit = repo.resolve_local_ref(m_branch_name); | ||
| if (!optional_commit) | ||
| if (!pathspecs.empty()) | ||
| { | ||
| // TODO: handle remote refs | ||
| std::ostringstream buffer; | ||
| buffer << "error: could not resolve pathspec '" << m_branch_name << "'" << std::endl; | ||
| throw std::runtime_error(buffer.str()); | ||
| throw git_exception("error: '-b' or '-B' does not accept pathspecs.", git2cpp_error_code::BAD_ARGUMENT); | ||
| } | ||
|
|
||
| auto sl = status_list_wrapper::status_list(repo); | ||
| try | ||
| auto annotated_commit = create_local_branch(repo, target_name, m_force_create_flag); | ||
| checkout_tree(repo, annotated_commit, target_name, options); | ||
| update_head(repo, annotated_commit, target_name); | ||
|
|
||
| std::cout << "Switched to a new branch '" << target_name << "'" << std::endl; | ||
| return; | ||
| } | ||
|
|
||
| if (!pathspecs.empty()) | ||
|
Comment thread
Outdated
Copy link
Copy Markdown
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityPrefer else if instead of multi return statements. (Early return to handle error / specific case is usually ok, but it makes it harder to nuderstand the code when used to encode the logic).
Sorry, something went wrong.
All reactions
|
||
| { | ||
| // Try tree-ish + pathspec(s) | ||
| if (auto obj = repo.revparse_single(target_name)) | ||
| { | ||
| checkout_tree(repo, *optional_commit, m_branch_name, options); | ||
| update_head(repo, *optional_commit, m_branch_name); | ||
| // Validate all pathspecs before checkout so we can mimic git-like errors | ||
| for (const auto& p : pathspecs) | ||
| { | ||
| if (!std::filesystem::exists(std::filesystem::path(directory) / p) && !repo.does_track(p)) | ||
| { | ||
| throw git_exception( | ||
| "error: pathspec '" + p + "' did not match any file(s) known to git", | ||
| git2cpp_error_code::BAD_ARGUMENT | ||
| ); | ||
| } | ||
| } | ||
|
Comment thread
JohanMabille marked this conversation as resolved.
|
||
|
|
||
| options.checkout_strategy = GIT_CHECKOUT_FORCE; | ||
| checkout_paths(repo, target_name, pathspecs, options); | ||
| return; | ||
| } | ||
| catch (const git_exception& e) | ||
|
|
||
| // Else treat as files | ||
| for (const auto& p : pathspecs) | ||
|
Comment thread
Outdated
Copy link
Copy Markdown
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityHere too prefer an else branch rather than a return statement.
Sorry, something went wrong.
All reactions
|
||
| { | ||
| if (sl.has_notstagged_header()) | ||
| if (!std::filesystem::exists(std::filesystem::path(directory) / p) && !repo.does_track(p)) | ||
| { | ||
| print_no_switch(sl); | ||
| throw git_exception( | ||
| "error: pathspec '" + p + "' did not match any file(s) known to git", | ||
| git2cpp_error_code::BAD_ARGUMENT | ||
| ); | ||
| } | ||
| throw e; | ||
| } | ||
|
|
||
| if (sl.has_notstagged_header()) | ||
| std::vector<std::string> files = m_positional_args; | ||
| options.checkout_strategy = GIT_CHECKOUT_FORCE; | ||
| checkout_files(repo, files, options); | ||
| return; | ||
| } | ||
|
|
||
| auto optional_commit = repo.resolve_local_ref(target_name); | ||
| if (!optional_commit) | ||
| { | ||
| // TODO: handle remote refs | ||
|
|
||
| // Fall back to checking out a unique file | ||
| const std::vector<std::string> file = {target_name}; | ||
|
|
||
| if (!std::filesystem::exists(std::filesystem::path(directory) / target_name)) | ||
| { | ||
| bool is_long = false; | ||
| bool is_coloured = false; | ||
| std::set<std::string> tracked_dir_set{}; | ||
| print_notstagged(sl, tracked_dir_set, is_long, is_coloured); | ||
| // Neither a branch/tag nor a file | ||
| throw git_exception( | ||
| "error: pathspec '" + target_name + "' did not match any file(s) known to git", | ||
| git2cpp_error_code::BAD_ARGUMENT | ||
| ); | ||
| } | ||
| if (sl.has_tobecommited_header()) | ||
|
|
||
| options.checkout_strategy = GIT_CHECKOUT_FORCE; | ||
| checkout_files(repo, file, options); | ||
| return; | ||
| } | ||
|
|
||
| auto sl = status_list_wrapper::status_list(repo); | ||
| try | ||
| { | ||
| checkout_tree(repo, *optional_commit, target_name, options); | ||
| update_head(repo, *optional_commit, target_name); | ||
| } | ||
| catch (const git_exception& e) | ||
| { | ||
| if (sl.has_notstagged_header()) | ||
| { | ||
| bool is_long = false; | ||
| bool is_coloured = false; | ||
| std::set<std::string> tracked_dir_set{}; | ||
| print_tobecommited(sl, tracked_dir_set, is_long, is_coloured); | ||
| print_no_switch(sl); | ||
| } | ||
| std::cout << "Switched to branch '" << m_branch_name << "'" << std::endl; | ||
| throw e; | ||
| } | ||
|
|
||
| if (sl.has_notstagged_header()) | ||
| { | ||
| bool is_long = false; | ||
| bool is_coloured = false; | ||
| std::set<std::string> tracked_dir_set{}; | ||
| print_notstagged(sl, tracked_dir_set, is_long, is_coloured); | ||
| } | ||
| if (sl.has_tobecommited_header()) | ||
| { | ||
| bool is_long = false; | ||
| bool is_coloured = false; | ||
| std::set<std::string> tracked_dir_set{}; | ||
| print_tobecommited(sl, tracked_dir_set, is_long, is_coloured); | ||
| } | ||
|
|
||
| std::string_view annotated_ref = optional_commit->reference_name(); | ||
| if (!annotated_ref.empty() && repo.find_reference(annotated_ref).is_branch()) | ||
| { | ||
| std::cout << "Switched to branch '" << target_name << "'" << std::endl; | ||
| print_tracking_info(repo, sl, true, false); | ||
| } | ||
| else | ||
| { | ||
| std::string sha = optional_commit->commit_oid_tostr().substr(0, 7); | ||
| auto commit = repo.find_commit(optional_commit->oid()); | ||
| std::string summary = commit.summary(); | ||
| std::cout << "HEAD is now at " << sha << " " << summary << std::endl; | ||
| } | ||
| } | ||
|
|
||
| annotated_commit_wrapper | ||
| Expand All | @@ -150,22 +282,71 @@ void checkout_subcommand::update_head( | |
| const std::string_view target_name | ||
| ) | ||
| { | ||
| // Check if HEAD is already detached or not | ||
| const bool head_was_detached = [&]() | ||
| { | ||
| auto head_ref = repo.head(); | ||
| return !head_ref.is_branch(); | ||
| }(); | ||
|
|
||
| // Save previous HEAD info (if it was detached) before changing it (for output message) | ||
| std::optional<commit_wrapper> previous_head_commit; | ||
| std::string previous_head_message; | ||
| if (head_was_detached) | ||
| { | ||
| previous_head_commit = repo.find_commit("HEAD"); | ||
| previous_head_message = "Previous HEAD position was " | ||
| + std::string(previous_head_commit.value().commit_oid_tostr().substr(0, 7)) | ||
| + " " + previous_head_commit.value().summary(); | ||
| } | ||
|
|
||
| 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()) | ||
| if (ref.is_branch()) | ||
| { | ||
| auto branch = repo.create_branch(target_name, target_annotated_commit); | ||
| repo.set_head(branch.reference_name()); | ||
| if (head_was_detached) | ||
| { | ||
| std::cout << previous_head_message << std::endl; | ||
| } | ||
| repo.set_head(annotated_ref); | ||
| return; | ||
| } | ||
| else | ||
| } | ||
|
|
||
| repo.set_head_detached(target_annotated_commit); | ||
|
|
||
| if (head_was_detached) | ||
| { | ||
| // Only print "Previous HEAD position..." if HEAD was already detached before and if there is an | ||
| // actual checkout | ||
| auto new_head_commit = repo.find_commit("HEAD"); | ||
| if (!git_oid_equal(&previous_head_commit.value().oid(), &new_head_commit.oid())) | ||
| { | ||
| repo.set_head(annotated_ref); | ||
| std::cout << previous_head_message << std::endl; | ||
| } | ||
| } | ||
| else | ||
| { | ||
| repo.set_head_detached(target_annotated_commit); | ||
| // Only print the detached-HEAD advice if HEAD was not already detached. | ||
| std::cout << "Note: switching to '" << target_name << "'." << std::endl; | ||
| std::cout << std::endl; | ||
| std::cout << "You are in 'detached HEAD' state. You can look around, make experimental" << std::endl; | ||
| std::cout << "changes and commit them, and you can discard any commits you make in this" << std::endl; | ||
| std::cout << "state without impacting any branches by switching back to a branch." << std::endl; | ||
| std::cout << std::endl; | ||
|
|
||
| // TODO: add to the following when the switch subcommand is implemented: | ||
| // std::cout << "If you want to create a new branch to retain commits you create, you may" << | ||
| // std::endl; std::cout << "do so (now or later) by using -c with the switch command. Example:" << | ||
| // std::endl; std::cout << " git switch -c <new-branch-name>" << std::endl; std::cout << std::endl; | ||
| // std::cout << "Or undo this operation with:" << std::endl; | ||
| // std::cout << std::endl; | ||
| // std::cout << " git switch -" << std::endl; | ||
| // std::cout << std::endl; | ||
| // TODO: add the following later | ||
| // std::cout << "Turn off this advice by setting config variable advice.detachedHead to false" | ||
| // << std::endl; | ||
| } | ||
| } | ||
| Back | FazBrowse Home | New Git URL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityCould be renamed checkout_head_files
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.