Skip to content

Commit e166459

Browse files
committed
Add highlevel git_checkout_or_clone test
1 parent 1728577 commit e166459

File tree

3 files changed

+181
-70
lines changed

3 files changed

+181
-70
lines changed

src/utility/git.cpp

Lines changed: 65 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,6 @@ repository_fetch(::git_repository* repository, std::string const& remote_name =
366366

367367
status_opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY;
368368
status_opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
369-
status_opts.flags |= GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
370369
status_opts.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED;
371370

372371
::git_status_list* status_list = nullptr;
@@ -380,26 +379,30 @@ repository_fetch(::git_repository* repository, std::string const& remote_name =
380379
auto const count = ::git_status_list_entrycount(status_list);
381380
for (auto i = 0uz; i != count; ++i) {
382381
auto const* entry = ::git_status_byindex(status_list, i);
383-
if (entry->head_to_index == nullptr) {
384-
continue;
385-
386-
} else if (entry->status == GIT_STATUS_WT_NEW or entry->status == GIT_STATUS_IGNORED) {
387-
auto path = repository_dir / entry->head_to_index->new_file.path;
388-
path = std::filesystem::canonical(path);
389-
390-
if (not is_subpath(path, repository_dir)) {
391-
std::print(
392-
stderr,
393-
"security: file '{}' is outside of directory of repository at '{}'",
394-
path.string(),
395-
repository_dir.string());
396-
std::terminate();
397-
}
398-
399-
std::print(stderr, "info: removing file '{}'", path.string());
400-
auto ec = std::error_code{};
401-
if (not std::filesystem::remove(path, ec)) {
402-
std::print(stderr, "error: failed removing file '{}': {}", path.string(), ec.message());
382+
assert(entry != nullptr);
383+
384+
if (entry->index_to_workdir != nullptr) {
385+
if (entry->status == GIT_STATUS_WT_NEW or entry->status == GIT_STATUS_IGNORED) {
386+
auto path = repository_dir / entry->index_to_workdir->new_file.path;
387+
path = std::filesystem::canonical(path);
388+
389+
if (not is_subpath(path, repository_dir)) {
390+
std::print(
391+
stderr,
392+
"security: file '{}' is outside of directory of repository at '{}'",
393+
path.string(),
394+
repository_dir.string());
395+
std::terminate();
396+
}
397+
398+
std::print(stderr, "info: removing file '{}'", path.string());
399+
400+
// The untracked files in untracked directories are not listed,
401+
// only the directories themselves.
402+
auto ec = std::error_code{};
403+
if (not std::filesystem::remove_all(path, ec)) {
404+
std::print(stderr, "error: failed removing file '{}': {}", path.string(), ec.message());
405+
}
403406
}
404407
}
405408
}
@@ -468,7 +471,7 @@ git_fetch_and_update(std::string const& url, std::string const& rev, std::filesy
468471
::git_repository_free(repository);
469472
}};
470473

471-
if (auto remote_url_o = repository_remote_url(repository, url)) {
474+
if (auto remote_url_o = repository_remote_url(repository)) {
472475
if (*remote_url_o != url) {
473476
std::print("The repository at {}, does not have a remote with the url {}", path.string(), url);
474477
return git_error::remote_url_mismatch;
@@ -477,15 +480,20 @@ git_fetch_and_update(std::string const& url, std::string const& rev, std::filesy
477480
return remote_url_o.error();
478481
}
479482

480-
auto fetch = to_bool(flags & git_checkout_flags::force_checkout);
483+
auto fetch = to_bool(flags & git_checkout_flags::force_fetch);
481484
if (auto result = repository_matches_rev(repository, rev)) {
482485
switch (*result) {
483486
case rev_match::rev_not_found:
487+
if (to_bool(flags & git_checkout_flags::fresh_clone)) {
488+
return git_error::rev_not_found;
489+
}
490+
[[fallthrough]];
484491
case rev_match::not_checked_out:
485492
case rev_match::checked_out_branch:
486-
fetch = true;
493+
fetch |= not to_bool(flags & git_checkout_flags::fresh_clone);
487494
break;
488495
case rev_match::checked_out:
496+
// Revisions that are tags or commits will not cause a fetch.
489497
break;
490498
}
491499

@@ -503,7 +511,7 @@ git_fetch_and_update(std::string const& url, std::string const& rev, std::filesy
503511
if (auto result = repository_matches_rev(repository, rev)) {
504512
switch (*result) {
505513
case rev_match::rev_not_found:
506-
return git_error::not_found;
514+
return git_error::rev_not_found;
507515
case rev_match::not_checked_out:
508516
checkout = true;
509517
break;
@@ -515,8 +523,10 @@ git_fetch_and_update(std::string const& url, std::string const& rev, std::filesy
515523
return result.error();
516524
}
517525

518-
auto clean = to_bool(flags & git_checkout_flags::clean);
519-
clean |= checkout;
526+
auto clean = checkout;
527+
clean |= to_bool(flags & git_checkout_flags::force_clean);
528+
// A fresh clone does not need to be cleaned.
529+
clean &= not to_bool(flags & git_checkout_flags::fresh_clone);
520530

521531
if (clean) {
522532
if (auto result = repository_clean(repository); result != git_error::ok) {
@@ -578,28 +588,33 @@ git_fetch_and_update(std::string const& url, std::string const& rev, std::filesy
578588
return git_error::ok;
579589
}
580590

581-
//[[nodiscard]] git_error git_checkout_or_clone(
582-
// std::string const& url, std::string const& branch, std::filesystem::path path, git_checkout_flags flags)
583-
//{
584-
// if (git_is_checked_out(path)) {
585-
// // Check if the repository should be checked out again.
586-
//
587-
// return git_error::ok;
588-
// }
589-
//
590-
// if (git_is_branch(url, branch)) {
591-
// git_clone_shallow(url, branch, path);
592-
//
593-
// } else if (git_is_tag(url, branch)) {
594-
// git_clone(url, path);
595-
// git_checkout(path, branch);
596-
//
597-
// } else {
598-
// git_clone(url, path);
599-
// git_checkout_oid(path, branch);
600-
//
601-
// }
602-
//
603-
//}
591+
[[nodiscard]] git_error git_checkout_or_clone(
592+
std::string const& url, std::string const& rev, std::filesystem::path path, git_checkout_flags flags)
593+
{
594+
// First try and just update the repository.
595+
switch(git_fetch_and_update(url, rev, path, flags)) {
596+
case git_error::ok:
597+
return git_error::ok;
598+
599+
case git_error::rev_not_found:
600+
// Repository was found but not the ref.
601+
return git_error::rev_not_found;
602+
603+
case git_error::not_found:
604+
// The repository was not found, try and clone instead.
605+
break;
606+
607+
default:
608+
return git_error::error;
609+
}
610+
611+
if (auto r = git_clone(url, rev, path); r != git_error::ok) {
612+
return r;
613+
}
614+
615+
// In case rev is a tag or commit, checkout/update the repository.
616+
flags |= git_checkout_flags::fresh_clone;
617+
return git_fetch_and_update(url, rev, path, flags);
618+
}
604619

605620
} // namespace hk

src/utility/git.hpp

Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@ enum class git_error {
5151
not_supported,
5252
read_only,
5353

54-
/** The repository does have a remote that matches the expected URL.
54+
/** The revision was not found in this repository.
55+
*/
56+
rev_not_found,
57+
58+
/** The repository does have a remote that matches the expected URL.
5559
*/
5660
remote_url_mismatch,
5761

@@ -60,15 +64,24 @@ enum class git_error {
6064
relative_workdir,
6165

6266
/** Found a file outside of the work dir.
63-
*
67+
*
6468
* This is a security issue.
6569
*/
6670
file_outside_workdir
6771
};
6872

6973
enum class git_checkout_flags {
70-
force_checkout = 0x1,
71-
clean = 0x2
74+
/** The repository was freshly cloned, not need for a fetch.
75+
*/
76+
fresh_clone = 0x1,
77+
78+
/** Fetch, even when the rev points to a tag or commit.
79+
*/
80+
force_fetch = 0x2,
81+
82+
/** Force the repository to be cleaned, even if no checkout is performed.
83+
*/
84+
force_clean = 0x4
7285
};
7386

7487
[[nodiscard]] constexpr git_checkout_flags operator|(git_checkout_flags lhs, git_checkout_flags rhs) noexcept
@@ -81,6 +94,21 @@ enum class git_checkout_flags {
8194
return static_cast<git_checkout_flags>(std::to_underlying(lhs) & std::to_underlying(rhs));
8295
}
8396

97+
[[nodiscard]] constexpr git_checkout_flags operator~(git_checkout_flags rhs) noexcept
98+
{
99+
return static_cast<git_checkout_flags>(~std::to_underlying(rhs));
100+
}
101+
102+
constexpr git_checkout_flags& operator|=(git_checkout_flags &lhs, git_checkout_flags rhs) noexcept
103+
{
104+
return lhs = lhs | rhs;
105+
}
106+
107+
constexpr git_checkout_flags& operator&=(git_checkout_flags &lhs, git_checkout_flags rhs) noexcept
108+
{
109+
return lhs = lhs & rhs;
110+
}
111+
84112
[[nodiscard]] constexpr bool to_bool(git_checkout_flags rhs) noexcept
85113
{
86114
return static_cast<bool>(std::to_underlying(rhs));
@@ -107,7 +135,7 @@ struct git_reference {
107135

108136
[[nodiscard]] constexpr bool is_tag() const noexcept
109137
{
110-
return name.starts_with("ref/tags/");
138+
return name.starts_with("refs/tags/");
111139
}
112140
};
113141

@@ -116,15 +144,15 @@ class git_references : public std::vector<git_reference> {
116144
using std::vector<git_reference>::vector;
117145

118146
/** Find a specific ref.
119-
*
147+
*
120148
* @pre sort() must be called first.
121149
* @param ref_name The full ref-name.
122150
* @return An iterator to the reference, or end() if not found.
123151
*/
124152
[[nodiscard]] const_iterator find_ref(std::string const& ref_name) const;
125153

126154
/** Find a name.
127-
*
155+
*
128156
* @pre sort() must be called first.
129157
* @param name A branch, tag or hex-oid.
130158
* @return An iterator to the reference, or end() if not found.
@@ -137,14 +165,14 @@ class git_references : public std::vector<git_reference> {
137165
};
138166

139167
/** Get a list of references.
140-
*
168+
*
141169
* @param url URL to remote repository
142170
* @return List of references, or error.
143171
*/
144172
[[nodiscard]] std::expected<git_references, git_error> git_list(std::string const& url);
145173

146174
/** Clone a repository.
147-
*
175+
*
148176
* @param url The (remote) location of the repository
149177
* @param git_rev The branch to checkout.
150178
* If git_rev is a branch the checkout is done with depth 1.
@@ -154,25 +182,33 @@ class git_references : public std::vector<git_reference> {
154182
[[nodiscard]] git_error git_clone(std::string const& url, std::string const& git_rev, std::filesystem::path path);
155183

156184
/** This function will open the repository and update to the latest version.
157-
*
185+
*
158186
* @param url The remote url, used to check if the repository at the path
159187
* has the same remote url.
160188
* @param rev The rev (branch/tag/sha) to checkout. If the repository is of a different
161189
* rev this branch is checked out, and the repository is cleaned.
162190
* @param path The path where the repository is located.
163191
* @param flags Flags for the way the repository should be checked out.
164192
*/
165-
[[nodiscard]] git_error git_fetch_and_update(std::string const& url, std::string const& rev, std::filesystem::path path, git_checkout_flags flags);
193+
[[nodiscard]] git_error git_fetch_and_update(
194+
std::string const& url,
195+
std::string const& rev,
196+
std::filesystem::path path,
197+
git_checkout_flags flags = git_checkout_flags{});
166198

167199
/** Checkout or clone the repository.
168-
*
200+
*
169201
* @param url The location of the remote repository.
170-
* @param branch The branch, tag, or hex-oid
202+
* @param rev The rev (branch/tag/sha) to checkout. If the repository is of a different
203+
* rev this branch is checked out, and the repository is cleaned.
171204
* @param path The location where to clone/checkout the repository to.
172205
* @param flags Flags for what to do with an already cloned repository.
173206
* @return An error, or git_error::ok.
174207
*/
175208
[[nodiscard]] git_error git_checkout_or_clone(
176-
std::string const& url, std::string const& branch, std::filesystem::path path, git_checkout_flags flags);
209+
std::string const& url,
210+
std::string const& rev,
211+
std::filesystem::path path,
212+
git_checkout_flags flags = git_checkout_flags{});
177213

178-
}
214+
} // namespace hk

0 commit comments

Comments
 (0)