Skip to content

Commit b811507

Browse files
authored
Checkout subcommand (#21)
* commit_wrapper now built from reposiroty_wrapper * Replaced std::string with std::string_view * Annotated commit wrapper * Minimal implementation of checkout subcommand
1 parent 171dc28 commit b811507

20 files changed

+480
-42
lines changed

CMakeLists.txt

Lines changed: 6 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
@@ -51,12 +53,16 @@ set(GIT2CPP_SRC
5153
${GIT2CPP_SOURCE_DIR}/utils/common.hpp
5254
${GIT2CPP_SOURCE_DIR}/utils/git_exception.cpp
5355
${GIT2CPP_SOURCE_DIR}/utils/git_exception.hpp
56+
${GIT2CPP_SOURCE_DIR}/wrapper/annotated_commit_wrapper.cpp
57+
${GIT2CPP_SOURCE_DIR}/wrapper/annotated_commit_wrapper.hpp
5458
${GIT2CPP_SOURCE_DIR}/wrapper/branch_wrapper.cpp
5559
${GIT2CPP_SOURCE_DIR}/wrapper/branch_wrapper.hpp
5660
${GIT2CPP_SOURCE_DIR}/wrapper/commit_wrapper.cpp
5761
${GIT2CPP_SOURCE_DIR}/wrapper/commit_wrapper.hpp
5862
${GIT2CPP_SOURCE_DIR}/wrapper/index_wrapper.cpp
5963
${GIT2CPP_SOURCE_DIR}/wrapper/index_wrapper.hpp
64+
${GIT2CPP_SOURCE_DIR}/wrapper/object_wrapper.cpp
65+
${GIT2CPP_SOURCE_DIR}/wrapper/object_wrapper.hpp
6066
${GIT2CPP_SOURCE_DIR}/wrapper/refs_wrapper.cpp
6167
${GIT2CPP_SOURCE_DIR}/wrapper/refs_wrapper.hpp
6268
${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+
};
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#include "../wrapper/annotated_commit_wrapper.hpp"
2+
3+
annotated_commit_wrapper::annotated_commit_wrapper(git_annotated_commit* commit)
4+
: base_type(commit)
5+
{
6+
}
7+
8+
annotated_commit_wrapper::~annotated_commit_wrapper()
9+
{
10+
git_annotated_commit_free(p_resource);
11+
p_resource = nullptr;
12+
}
13+
14+
const git_oid& annotated_commit_wrapper::oid() const
15+
{
16+
return *git_annotated_commit_id(p_resource);
17+
}
18+
19+
std::string_view annotated_commit_wrapper::reference_name() const
20+
{
21+
const char* res = git_annotated_commit_ref(*this);
22+
return res ? res : std::string_view{};
23+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#pragma once
2+
3+
#include <string_view>
4+
5+
#include <git2.h>
6+
7+
#include "../wrapper/wrapper_base.hpp"
8+
9+
class annotated_commit_wrapper : public wrapper_base<git_annotated_commit>
10+
{
11+
public:
12+
13+
using base_type = wrapper_base<git_annotated_commit>;
14+
15+
~annotated_commit_wrapper();
16+
17+
annotated_commit_wrapper(annotated_commit_wrapper&&) noexcept = default;
18+
annotated_commit_wrapper& operator=(annotated_commit_wrapper&&) noexcept = default;
19+
20+
const git_oid& oid() const;
21+
std::string_view reference_name() const;
22+
23+
private:
24+
25+
annotated_commit_wrapper(git_annotated_commit* commit);
26+
27+
friend class repository_wrapper;
28+
};
29+

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: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
1-
#include "../utils/git_exception.hpp"
21
#include "../wrapper/commit_wrapper.hpp"
3-
#include "../wrapper/repository_wrapper.hpp"
2+
3+
commit_wrapper::commit_wrapper(git_commit* commit)
4+
: base_type(commit)
5+
{
6+
}
47

58
commit_wrapper::~commit_wrapper()
69
{
710
git_commit_free(p_resource);
811
p_resource = nullptr;
912
}
1013

11-
12-
commit_wrapper commit_wrapper::from_reference_name(const repository_wrapper& repo, const std::string& ref_name)
14+
commit_wrapper::operator git_object*() const noexcept
1315
{
14-
git_oid oid_parent_commit;
15-
throwIfError(git_reference_name_to_id(&oid_parent_commit, repo, ref_name.c_str()));
16+
return reinterpret_cast<git_object*>(p_resource);
17+
}
1618

17-
commit_wrapper cw;
18-
throwIfError(git_commit_lookup(&(cw.p_resource), repo, &oid_parent_commit));
19-
return cw;
19+
const git_oid& commit_wrapper::oid() const
20+
{
21+
return *git_commit_id(p_resource);
2022
}
23+

src/wrapper/commit_wrapper.hpp

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
11
#pragma once
22

3-
#include <string>
4-
53
#include <git2.h>
64

75
#include "../wrapper/wrapper_base.hpp"
86

9-
class repository_wrapper;
10-
117
class commit_wrapper : public wrapper_base<git_commit>
128
{
139
public:
1410

11+
using base_type = wrapper_base<git_commit>;
12+
1513
~commit_wrapper();
1614

1715
commit_wrapper(commit_wrapper&&) noexcept = default;
1816
commit_wrapper& operator=(commit_wrapper&&) noexcept = default;
1917

20-
static commit_wrapper
21-
from_reference_name(const repository_wrapper& repo, const std::string& ref_name = "HEAD");
18+
operator git_object*() const noexcept;
19+
20+
const git_oid& oid() const;
2221

2322
private:
2423

25-
commit_wrapper() = default;
24+
commit_wrapper(git_commit* commit);
25+
26+
friend class repository_wrapper;
2627
};

0 commit comments

Comments
 (0)