@@ -160,19 +160,80 @@ class git_lib {
160160 return r;
161161}
162162
163+ enum class rev_match {
164+ /* * Rev was not found in the repository.
165+ */
166+ rev_not_found,
167+
168+ /* * Rev was not checked out.
169+ */
170+ not_checked_out,
171+
172+ /* * Rev was checked out and is a tag or sha.
173+ */
174+ checked_out,
175+
176+ /* * Rev was checked out and is a branch.
177+ */
178+ checked_out_branch,
179+ };
180+
163181/* * Check if one of the remote has the correct branch checked out.
164182 *
165183 * @param repository The repository to check all the remotes
166- * @param branch The branch to compare.
167- * @retval true The correct branch was checked out.
168- * @retval false Another branch was checked out.
184+ * @param rev The rev to compare.
169185 * @retval error An error during query.
170186 */
171- [[nodiscard]] std::expected<bool , git_error> repository_matches_branch (::git_repository *repository, std::string const & branch )
187+ [[nodiscard]] std::expected<rev_match , git_error> repository_matches_rev (::git_repository *repository, std::string const & rev )
172188{
189+ ::git_object *rev_obj = nullptr ;
190+ ::git_reference *rev_ref = nullptr ;
191+ if (auto const result = ::git_revparse_ext (&rev_obj, &rev_ref, repository, rev.c_str ()); result != GIT_OK) {
192+ if (result == GIT_ENOTFOUND) {
193+ // The rev was not found in the repository.
194+ return rev_match::rev_not_found;
195+ }
196+ return std::unexpected{make_git_error (result)};
197+ }
198+ auto const d1 = defer{[&] {
199+ ::git_object_free (rev_obj);
200+ ::git_reference_free (rev_ref);
201+ }};
202+
203+ ::git_object *peeled_rev_obj = nullptr ;
204+ if (auto const result = ::git_object_peel (&peeled_rev_obj, rev_obj, GIT_OBJECT_COMMIT); result != GIT_OK) {
205+ return std::unexpected{make_git_error (result)};
206+ }
207+ auto const d2 = defer{[&] { ::git_object_free (peeled_rev_obj); }};
208+
209+ ::git_reference *head_ref = nullptr ;
210+ if (auto const result = ::git_repository_head (&head_ref, repository); result != GIT_OK) {
211+ return std::unexpected{make_git_error (result)};
212+ }
213+ auto const d3 = defer{[&] { ::git_reference_free (head_ref); }};
214+
215+ ::git_object *peeled_head_obj = nullptr ;
216+ if (auto const result = ::git_reference_peel (&peeled_head_obj, head_ref, GIT_OBJECT_COMMIT); result != GIT_OK) {
217+ return std::unexpected{make_git_error (result)};
218+ }
219+ auto const d4 = defer{[&] { ::git_object_free (peeled_head_obj); }};
220+
221+ auto const *rev_oid = git_object_id (peeled_rev_obj);
222+ assert (rev_oid != nullptr );
173223
224+ auto const *head_oid = git_object_id (peeled_head_obj);
225+ assert (head_oid != nullptr );
226+
227+ if (git_oid_cmp (rev_oid, head_oid) != 0 ) {
228+ return rev_match::not_checked_out;
229+ } else if (git_reference_is_branch (rev_ref)) {
230+ return rev_match::checked_out_branch;
231+ } else {
232+ return rev_match::checked_out;
233+ }
174234}
175235
236+
176237/* * Check if one of the remote has a url that matches the given url.
177238 *
178239 * @param repository The repository to check all the remotes
@@ -207,7 +268,30 @@ class git_lib {
207268 return false ;
208269}
209270
210- [[nodiscard]] git_error git_fetch_and_update (std::string const & url, std::string const & branch, std::filesystem::path path, git_checkout_flags flags)
271+ [[nodiscard]] static git_error repository_fetch (::git_repository *repository, std::string const & remote_name = std::string{" origin" })
272+ {
273+ assert (repository != nullptr );
274+
275+ ::git_remote *remote = nullptr ;
276+ auto fetch_opts = ::git_fetch_options{};
277+
278+ if (auto result = ::git_fetch_init_options (&fetch_opts, GIT_FETCH_OPTIONS_VERSION); result != 0 ) {
279+ return git_error::error;
280+ }
281+
282+ if (auto result = ::git_remote_lookup (&remote, repository, remote_name.c_str ()); result != GIT_OK) {
283+ return make_git_error (result);
284+ }
285+ defer{[&] { ::git_remote_free (remote); }};
286+
287+ if (auto result = ::git_remote_fetch (remote, nullptr , &fetch_opts, nullptr )) {
288+ return make_git_error (result);
289+ }
290+
291+ return git_error::ok;
292+ }
293+
294+ [[nodiscard]] git_error git_fetch_and_update (std::string const & url, std::string const & rev, std::filesystem::path path, git_checkout_flags flags)
211295{
212296 auto const & _ = git_lib_initialize ();
213297
@@ -228,9 +312,60 @@ class git_lib {
228312 return result.error ();
229313 }
230314
231- if (auto result = repository_matches_branch (repository, branch)) {
315+ auto fetch = to_bool (flags & git_checkout_flags::force_checkout);
316+ if (auto result = repository_matches_rev (repository, rev)) {
317+ switch (*result) {
318+ case rev_match::rev_not_found:
319+ case rev_match::not_checked_out:
320+ case rev_match::checked_out_branch:
321+ fetch = true ;
322+ break ;
323+ case rev_match::checked_out:
324+ break ;
325+ }
326+
327+ } else {
328+ return result.error ();
329+ }
330+
331+ if (fetch) {
332+ if (auto result = repository_fetch (repository); result != git_error::ok) {
333+ return result;
334+ }
335+ }
232336
337+ auto checkout = false ;
338+ if (auto result = repository_matches_rev (repository, rev)) {
339+ switch (*result) {
340+ case rev_match::rev_not_found:
341+ return git_error::not_found;
342+ case rev_match::not_checked_out:
343+ checkout = true ;
344+ break ;
345+ case rev_match::checked_out:
346+ case rev_match::checked_out_branch:
347+ break ;
348+ }
349+ } else {
350+ return result.error ();
233351 }
352+
353+ auto clean = to_bool (flags & git_checkout_flags::clean);
354+ clean |= checkout;
355+
356+ if (clean) {
357+ if (auto result = repository_clean (repository); result != git_error::ok) {
358+ return result;
359+ }
360+ }
361+
362+ if (checkout) {
363+ if (auto result = repository_checkout (repository, rev); result != git_error::ok) {
364+ return result;
365+ }
366+ }
367+
368+ return git_error::ok;
234369}
235370
236371[[nodiscard]] git_error git_clone (std::string const & url, std::string const & branch, std::filesystem::path path)
0 commit comments