I'm working on a personal project to build an open-source gui for git. I'm hoping to learn a lot from the project, and maybe produce something useful for folks as I do.
I'm making use of libgit2 to interface with git repositories. The library is implemented in C, and any time a new object is created (be it a commit, repository, branch, generic object) it's done by making a call into the library and passing a pointer to a pointer of where that object will be stored (foo**), and then these objects are deleted by the client code by making another libgit2 call to free the resource (e.g. git_repository_open and git_repository_free)
I want to get all the benefits of RAII types like std::unique_ptr but also make use of this library. Because we're using libgit2 function calls to create and destroy objects instead of the new, delete operators, I wrote my own adapter class to and make the calls to the right libgit2 functions but manage the created pointer in a std::unique_ptr:
https://github.com/StephenLHern/fons/blob/main/git/unique_libgit_ptr.hpp
#pragma once
#include <functional>
#include <git2.h>
#include <memory>
namespace fons::git
{
template <typename T>
class unique_libgit_ptr
{
public:
template <class CreateMethodT, class... CreateMethodArgs>
unique_libgit_ptr(CreateMethodT creator, std::function<void __cdecl(T *)> free_method, CreateMethodArgs &&...args)
{
T *rawPtr{};
int createResult = creator(&rawPtr, std::forward<CreateMethodArgs>(args)...);
if (createResult != GIT_OK)
return;
base_ptr = std::unique_ptr<T, std::function<void __cdecl(T *)>>{std::move(rawPtr), free_method};
}
T &operator*()
{
return &base_ptr;
}
T *operator->()
{
return *base_ptr;
}
operator bool()
{
return static_cast<bool>(base_ptr);
}
auto release()
{
return base_ptr.release();
}
auto reset(std::unique_ptr<T, std::function<void __cdecl(T *)>>::pointer ptr)
{
return base_ptr.reset(ptr);
}
auto get()
{
return base_ptr.get();
}
auto get_deleter()
{
return base_ptr.get_deleter();
}
private:
std::unique_ptr<T, std::function<void __cdecl(T *)>> base_ptr;
};
using unique_commit_ptr = unique_libgit_ptr<git_commit>;
using unique_revwalk_ptr = unique_libgit_ptr<git_revwalk>;
using unique_git_obj_ptr = unique_libgit_ptr<git_object>;
using unique_repo_ptr = unique_libgit_ptr<git_repository>;
using unique_branch_itr = unique_libgit_ptr<git_branch_iterator>;
using unique_ref_ptr = unique_libgit_ptr<git_reference>;
} // namespace fons::git
Here's how this class gets used, (unique_repo_ptr, unique_git_obj_ptr, unique_commit_ptr, etc.):
https://github.com/StephenLHern/fons/blob/main/git/commands/revwalk.cpp
#include "revwalk.hpp"
#include "app_main.hpp"
#include "git/git_observer.hpp"
#include "git/gitlib_manager.hpp"
#include "git/unique_libgit_ptr.hpp"
namespace fons::git
{
wxDEFINE_EVENT(EVENT_REVWALK, revwalk_event);
void revwalk::execute()
{
gitlib_manager gitlib;
unique_repo_ptr repo(git_repository_open, git_repository_free, app->settings.active_repo.c_str());
unique_revwalk_ptr walk(git_revwalk_new, git_revwalk_free, repo.get());
if (!walk.get())
return;
git_revwalk_sorting(walk.get(), GIT_SORT_TOPOLOGICAL | GIT_SORT_TIME);
git_revwalk_push_head(walk.get());
git_revwalk_hide_glob(walk.get(), "tags/*");
unique_git_obj_ptr obj(git_revparse_single, git_object_free, repo.get(), "HEAD~1000");
git_revwalk_hide(walk.get(), git_object_id(obj.get()));
git_oid oid;
while (git_revwalk_next(&oid, walk.get()) == 0 && status != common::cmd_status::cancelled)
{
unique_commit_ptr commit(git_commit_lookup, git_commit_free, repo.get(), &oid);
char oidstr[GIT_OID_HEXSZ + 1]{};
std::string id(git_oid_tostr(oidstr, sizeof(oidstr), &oid));
std::string message((git_commit_message(commit.get())));
std::string author_email(git_commit_author(commit.get())->email);
std::string author_name(git_commit_author(commit.get())->name);
__time64_t timestamp = git_commit_time(commit.get());
fons::git::commit commit_data(id, message, author_name, author_email, timestamp);
queue_event<revwalk_event>(EVENT_REVWALK, EVENT_FOUND_COMMIT, commit_data);
}
return;
}
} // namespace fons::git
I was interested in feedback on this code. Are there other ways to approach this problem I may not have considered? Or further ways to simplify the implementation? In particular I was wondering if there are ways to automatically deduce the template parameter for these pointers from the **foo arguments and eliminate the need for using alias statements.