11
22#include " git.hpp"
33#include " defer.hpp"
4+ #include " path.hpp"
45#include < git2.h>
56#include < mutex>
67#include < format>
@@ -184,7 +185,7 @@ enum class rev_match {
184185 * @param rev The rev to compare.
185186 * @retval error An error during query.
186187 */
187- [[nodiscard]] std::expected<rev_match, git_error> repository_matches_rev (::git_repository *repository, std::string const & rev)
188+ [[nodiscard]] static std::expected<rev_match, git_error> repository_matches_rev (::git_repository *repository, std::string const & rev)
188189{
189190 ::git_object *rev_obj = nullptr ;
190191 ::git_reference *rev_ref = nullptr ;
@@ -237,35 +238,26 @@ enum class rev_match {
237238/* * Check if one of the remote has a url that matches the given url.
238239 *
239240 * @param repository The repository to check all the remotes
240- * @param url The url to compare.
241- * @retval true The url is a remote of the @a repository.
242- * @retval false None of the remotes match.
241+ * @param remote_name The remote to get the url for.
242+ * @retval string The url of the remote.
243243 * @retval error An error during query.
244244 */
245- [[nodiscard]] std::expected<bool , git_error> repository_matches_url (::git_repository *repository, std::string const & url )
245+ [[nodiscard]] static std::expected<std::string , git_error> repository_remote_url (::git_repository *repository, std::string const & remote_name = std::string{ " origin " } )
246246{
247247 assert (repository != nullptr );
248248
249- auto remote_names = ::git_strarray{} ;
250- if (auto const result = ::git_remote_list (&remote_names , repository); result != GIT_OK) {
249+ ::git_remote *remote = nullptr ;
250+ if (auto const result = ::git_remote_lookup (&remote , repository, remote_name. c_str () ); result != GIT_OK) {
251251 return std::unexpected{make_git_error (result)};
252252 }
253- auto const _ = defer{[&]{ ::git_strarray_dispose (&remote_names); }};
254-
255- for (auto i = 0uz; i != remote_names.count ; ++i) {
256- ::git_remote *remote = nullptr ;
257- if (auto const result = ::git_remote_lookup (&remote, repository, remote_names.strings [i]); result != GIT_OK) {
258- return std::unexpected{make_git_error (result)};
259- }
260- auto const _ = defer{[&]{ ::git_remote_free (remote); }};
253+ auto const d1 = defer{[&]{ ::git_remote_free (remote); }};
261254
262- auto const remote_url = ::git_remote_url (remote);
263- if (remote_url and url == remote_url) {
264- return true ;
265- }
255+ auto const remote_url = ::git_remote_url (remote);
256+ if (remote_url == nullptr ) {
257+ return std::string{" " };
266258 }
267259
268- return false ;
260+ return std::string{remote_url} ;
269261}
270262
271263[[nodiscard]] static git_error repository_fetch (::git_repository *repository, std::string const & remote_name = std::string{" origin" })
@@ -291,25 +283,99 @@ enum class rev_match {
291283 return git_error::ok;
292284}
293285
286+ [[nodiscard]] static std::filesystem::path repository_workdir (::git_repository *repository)
287+ {
288+ assert (repository != nullptr );
289+
290+ if (auto *p = ::git_repository_workdir (repository); p != nullptr ) {
291+ return std::filesystem::path{p};
292+ } else {
293+ return std::filesystem::path{};
294+ }
295+ }
296+
297+ /* * Clean the repository in preperation for a new checkout.
298+ *
299+ * This will remove all untracked and ignored files. It is expected that these
300+ * are the only files that need to be removed to rebuild the repository.
301+ *
302+ * @param repository An open repository.
303+ * @return An error code or ok.
304+ */
305+ [[nodiscard]] static git_error repository_clean (::git_repository *repository)
306+ {
307+ assert (repository != nullptr );
308+
309+ auto repository_dir = repository_workdir (repository);
310+ if (repository_dir.empty ()) {
311+ return git_error::bare_repo;
312+ }
313+ if (not repository_dir.is_absolute ()) {
314+ return git_error::relative_workdir;
315+ }
316+ repository_dir = std::filesystem::canonical (repository_dir);
317+
318+ auto status_opts = ::git_status_options{};
319+ if (::git_status_options_init (&status_opts, GIT_STATUS_OPTIONS_VERSION) != 0 ) {
320+ return git_error::error;
321+ }
322+
323+ status_opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY;
324+ status_opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
325+ status_opts.flags |= GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
326+ status_opts.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED;
327+
328+ ::git_status_list *status_list = nullptr ;
329+ if (auto result = ::git_status_list_new (&status_list, repository, &status_opts); result != GIT_OK) {
330+ return make_git_error (result);
331+ }
332+ auto const d1 = defer{[&] { ::git_status_list_free (status_list); }};
333+
334+ auto const count = ::git_status_list_entrycount (status_list);
335+ for (auto i = 0uz; i != count; ++i) {
336+ auto const *entry = ::git_status_byindex (status_list, i);
337+ if (entry->head_to_index == nullptr ) {
338+ continue ;
339+
340+ } else if (entry->status == GIT_STATUS_WT_NEW or entry->status == GIT_STATUS_IGNORED) {
341+ auto path = repository_dir / entry->head_to_index ->new_file .path ;
342+ path = std::filesystem::canonical (path);
343+
344+ if (not is_subpath (path, repository_dir)) {
345+ std::print (stderr, " security: file '{}' is outside of directory of repository at '{}'" , path, repository_dir);
346+ std::terminate ();
347+ }
348+
349+ std::print (stderr, " info: removing file '{}'" , path.string ());
350+ auto ec = std::error_code{};
351+ if (not std::filesystem::remove (path, ec)) {
352+ std::print (stderr, " error: failed removing file '{}': {}" , path.string (), ec);
353+ }
354+ }
355+ }
356+
357+ return git_error::ok;
358+ }
359+
294360[[nodiscard]] git_error git_fetch_and_update (std::string const & url, std::string const & rev, std::filesystem::path path, git_checkout_flags flags)
295361{
296362 auto const & _ = git_lib_initialize ();
297363
298364 auto r = git_error::ok;
299365
300366 ::git_repository *repository = nullptr ;
301- if (auto const result = ::git_repository_open (&repository, url .c_str ()); result != GIT_OK) {
367+ if (auto const result = ::git_repository_open (&repository, path. string () .c_str ()); result != GIT_OK) {
302368 return make_git_error (result);
303369 }
304370 auto const d1 = defer{[&]{ ::git_repository_free (repository); }};
305371
306- if (auto result = repository_matches_url (repository, url)) {
307- if (not *result ) {
372+ if (auto remote_url_o = repository_remote_url (repository, url)) {
373+ if (*remote_url_o != url ) {
308374 std::print (" The repository at {}, does not have a remote with the url {}" , path.string (), url);
309375 return git_error::remote_url_mismatch;
310376 }
311377 } else {
312- return result .error ();
378+ return remote_url_o .error ();
313379 }
314380
315381 auto fetch = to_bool (flags & git_checkout_flags::force_checkout);
0 commit comments