Skip to content

Commit acbdf2e

Browse files
committed
Minimal implementation of checkout subcommand
1 parent 1c88f5f commit acbdf2e

17 files changed

+358
-11
lines changed

CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ set(GIT2CPP_SRC
4343
${GIT2CPP_SOURCE_DIR}/subcommand/add_subcommand.hpp
4444
${GIT2CPP_SOURCE_DIR}/subcommand/branch_subcommand.cpp
4545
${GIT2CPP_SOURCE_DIR}/subcommand/branch_subcommand.hpp
46+
${GIT2CPP_SOURCE_DIR}/subcommand/checkout_subcommand.cpp
47+
${GIT2CPP_SOURCE_DIR}/subcommand/checkout_subcommand.hpp
4648
${GIT2CPP_SOURCE_DIR}/subcommand/init_subcommand.cpp
4749
${GIT2CPP_SOURCE_DIR}/subcommand/init_subcommand.hpp
4850
${GIT2CPP_SOURCE_DIR}/subcommand/status_subcommand.cpp
@@ -59,6 +61,8 @@ set(GIT2CPP_SRC
5961
${GIT2CPP_SOURCE_DIR}/wrapper/commit_wrapper.hpp
6062
${GIT2CPP_SOURCE_DIR}/wrapper/index_wrapper.cpp
6163
${GIT2CPP_SOURCE_DIR}/wrapper/index_wrapper.hpp
64+
${GIT2CPP_SOURCE_DIR}/wrapper/object_wrapper.cpp
65+
${GIT2CPP_SOURCE_DIR}/wrapper/object_wrapper.hpp
6266
${GIT2CPP_SOURCE_DIR}/wrapper/refs_wrapper.cpp
6367
${GIT2CPP_SOURCE_DIR}/wrapper/refs_wrapper.hpp
6468
${GIT2CPP_SOURCE_DIR}/wrapper/repository_wrapper.cpp

src/main.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "version.hpp"
77
#include "subcommand/add_subcommand.hpp"
88
#include "subcommand/branch_subcommand.hpp"
9+
#include "subcommand/checkout_subcommand.hpp"
910
#include "subcommand/init_subcommand.hpp"
1011
#include "subcommand/status_subcommand.hpp"
1112

@@ -25,6 +26,7 @@ int main(int argc, char** argv)
2526
status_subcommand status(lg2_obj, app);
2627
add_subcommand add(lg2_obj, app);
2728
branch_subcommand(lg2_obj, app);
29+
checkout_subcommand(lg2_obj, app);
2830

2931
app.parse(argc, argv);
3032

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
#include <iostream>
2+
#include <sstream>
3+
4+
#include "../subcommand/checkout_subcommand.hpp"
5+
#include "../utils/git_exception.hpp"
6+
#include "../wrapper/repository_wrapper.hpp"
7+
8+
checkout_subcommand::checkout_subcommand(const libgit2_object&, CLI::App& app)
9+
{
10+
auto* sub = app.add_subcommand("checkout", "Switch branches or restore working tree files");
11+
12+
sub->add_option("<branch>", m_branch_name, "Branch to checkout");
13+
sub->add_flag("-b", m_create_flag, "Create a new branch before checking it out");
14+
sub->add_flag("-B", m_force_create_flag, "Create a new branch or reset it if it exists before checking it out");
15+
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");
16+
17+
sub->callback([this]() { this->run(); });
18+
}
19+
20+
void checkout_subcommand::run()
21+
{
22+
auto directory = get_current_git_path();
23+
auto repo = repository_wrapper::open(directory);
24+
25+
if (repo.state() != GIT_REPOSITORY_STATE_NONE)
26+
{
27+
throw std::runtime_error("Cannot checkout, repository is in unexpected state");
28+
}
29+
30+
git_checkout_options options;
31+
git_checkout_options_init(&options, GIT_CHECKOUT_OPTIONS_VERSION);
32+
33+
if(m_force_checkout_flag)
34+
{
35+
options.checkout_strategy = GIT_CHECKOUT_FORCE;
36+
}
37+
38+
if (m_create_flag || m_force_create_flag)
39+
{
40+
auto annotated_commit = create_local_branch(repo, m_branch_name, m_force_create_flag);
41+
checkout_tree(repo, annotated_commit, m_branch_name, options);
42+
update_head(repo, annotated_commit, m_branch_name);
43+
}
44+
else
45+
{
46+
auto optional_commit = resolve_local_ref(repo, m_branch_name);
47+
if (!optional_commit)
48+
{
49+
// TODO: handle remote refs
50+
std::ostringstream buffer;
51+
buffer << "error: could not resolve pathspec '" << m_branch_name << "'" << std::endl;
52+
throw std::runtime_error(buffer.str());
53+
}
54+
checkout_tree(repo, *optional_commit, m_branch_name, options);
55+
update_head(repo, *optional_commit, m_branch_name);
56+
}
57+
}
58+
59+
std::optional<annotated_commit_wrapper> checkout_subcommand::resolve_local_ref
60+
(
61+
const repository_wrapper& repo,
62+
const std::string& target_name
63+
)
64+
{
65+
if (auto ref = repo.find_reference_dwim(target_name))
66+
{
67+
return repo.find_annotated_commit(*ref);
68+
}
69+
else if (auto obj = repo.revparse_single(target_name))
70+
{
71+
return repo.find_annotated_commit(obj->oid());
72+
}
73+
else
74+
{
75+
return std::nullopt;
76+
}
77+
}
78+
79+
annotated_commit_wrapper checkout_subcommand::create_local_branch
80+
(
81+
repository_wrapper& repo,
82+
const std::string& target_name,
83+
bool force
84+
)
85+
{
86+
auto branch = repo.create_branch(target_name, force);
87+
return repo.find_annotated_commit(branch);
88+
}
89+
90+
void checkout_subcommand::checkout_tree
91+
(
92+
const repository_wrapper& repo,
93+
const annotated_commit_wrapper& target_annotated_commit,
94+
const std::string& target_name,
95+
const git_checkout_options& options
96+
)
97+
{
98+
auto target_commit = repo.find_commit(target_annotated_commit.oid());
99+
throwIfError(git_checkout_tree(repo, target_commit, &options));
100+
}
101+
102+
void checkout_subcommand::update_head
103+
(
104+
repository_wrapper& repo,
105+
const annotated_commit_wrapper& target_annotated_commit,
106+
const std::string& target_name
107+
)
108+
{
109+
std::string_view annotated_ref = target_annotated_commit.reference_name();
110+
if (!annotated_ref.empty())
111+
{
112+
auto ref = repo.find_reference(annotated_ref);
113+
if (ref.is_remote())
114+
{
115+
auto branch = repo.create_branch(target_name, target_annotated_commit);
116+
repo.set_head(branch.reference_name());
117+
}
118+
else
119+
{
120+
repo.set_head(annotated_ref);
121+
}
122+
}
123+
else
124+
{
125+
repo.set_head_detached(target_annotated_commit);
126+
}
127+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#pragma once
2+
3+
#include <optional>
4+
#include <string>
5+
6+
#include <CLI/CLI.hpp>
7+
8+
#include "../utils/common.hpp"
9+
#include "../wrapper/repository_wrapper.hpp"
10+
11+
class checkout_subcommand
12+
{
13+
public:
14+
15+
explicit checkout_subcommand(const libgit2_object&, CLI::App& app);
16+
void run();
17+
18+
private:
19+
20+
std::optional<annotated_commit_wrapper> resolve_local_ref
21+
(
22+
const repository_wrapper& repo,
23+
const std::string& target_name
24+
);
25+
26+
annotated_commit_wrapper create_local_branch
27+
(
28+
repository_wrapper& repo,
29+
const std::string& target_name,
30+
bool force
31+
);
32+
33+
void checkout_tree
34+
(
35+
const repository_wrapper& repo,
36+
const annotated_commit_wrapper& target_annotated_commit,
37+
const std::string& target_name,
38+
const git_checkout_options& options
39+
);
40+
41+
void update_head
42+
(
43+
repository_wrapper& repo,
44+
const annotated_commit_wrapper& target_annotated_commit,
45+
const std::string& target_name
46+
);
47+
48+
std::string m_branch_name = {};
49+
bool m_create_flag = false;
50+
bool m_force_create_flag = false;
51+
bool m_force_checkout_flag = false;
52+
};

src/wrapper/branch_wrapper.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,16 @@ branch_wrapper::~branch_wrapper()
1919
std::string_view branch_wrapper::name() const
2020
{
2121
const char* out = nullptr;
22-
throwIfError(git_branch_name(&out, p_resource));
22+
throwIfError(git_branch_name(&out, *this));
2323
return std::string_view(out);
2424
}
2525

26+
std::string_view branch_wrapper::reference_name() const
27+
{
28+
const char* out = git_reference_name(*this);
29+
return out ? out : std::string_view();
30+
}
31+
2632
void delete_branch(branch_wrapper&& branch)
2733
{
2834
throwIfError(git_branch_delete(branch));

src/wrapper/branch_wrapper.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class branch_wrapper : public wrapper_base<git_reference>
2121
branch_wrapper& operator=(branch_wrapper&&) = default;
2222

2323
std::string_view name() const;
24+
std::string_view reference_name() const;
2425

2526
private:
2627

src/wrapper/commit_wrapper.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ commit_wrapper::~commit_wrapper()
1111
p_resource = nullptr;
1212
}
1313

14+
commit_wrapper::operator git_object*() const noexcept
15+
{
16+
return reinterpret_cast<git_object*>(p_resource);
17+
}
18+
1419
const git_oid& commit_wrapper::oid() const
1520
{
1621
return *git_commit_id(p_resource);

src/wrapper/commit_wrapper.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ class commit_wrapper : public wrapper_base<git_commit>
1515
commit_wrapper(commit_wrapper&&) noexcept = default;
1616
commit_wrapper& operator=(commit_wrapper&&) noexcept = default;
1717

18+
operator git_object*() const noexcept;
19+
1820
const git_oid& oid() const;
1921

2022
private:

src/wrapper/object_wrapper.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#include "../wrapper/object_wrapper.hpp"
2+
3+
object_wrapper::object_wrapper(git_object* obj)
4+
: base_type(obj)
5+
{
6+
}
7+
8+
object_wrapper::~object_wrapper()
9+
{
10+
git_object_free(p_resource);
11+
p_resource = nullptr;
12+
}
13+
14+
const git_oid& object_wrapper::oid() const
15+
{
16+
return *git_object_id(*this);
17+
}

src/wrapper/object_wrapper.hpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#pragma once
2+
3+
#include <git2.h>
4+
5+
#include "../wrapper/wrapper_base.hpp"
6+
7+
class object_wrapper : public wrapper_base<git_object>
8+
{
9+
public:
10+
11+
using base_type = wrapper_base<git_object>;
12+
13+
~object_wrapper();
14+
15+
object_wrapper(object_wrapper&&) noexcept = default;
16+
object_wrapper& operator=(object_wrapper&&) noexcept = default;
17+
18+
const git_oid& oid() const;
19+
20+
private:
21+
22+
object_wrapper(git_object* obj);
23+
24+
friend class repository_wrapper;
25+
};

0 commit comments

Comments
 (0)