#include "git.hpp" #include #include bool Git::initialized = false; Mutex Git::mutex; Git::Error Git::error; std::string Git::Error::message() noexcept { #if LIBGIT2_VER_MAJOR > 0 || (LIBGIT2_VER_MAJOR == 0 && LIBGIT2_VER_MINOR >= 28) auto last_error = git_error_last(); #else auto last_error = giterr_last(); #endif if(!last_error) return std::string(); else return last_error->message; } Git::Repository::Diff::Diff(const boost::filesystem::path &path, git_repository *repository) { blob = std::shared_ptr(nullptr, [](git_blob *blob) { if(blob) git_blob_free(blob); }); auto spec = "HEAD:" + path.generic_string(); LockGuard lock(mutex); error.code = git_revparse_single(reinterpret_cast(&blob), repository, spec.c_str()); if(error) throw std::runtime_error(error.message()); git_diff_init_options(&options, GIT_DIFF_OPTIONS_VERSION); options.context_lines = 0; } Git::Repository::Diff::Lines Git::Repository::Diff::get_lines(const std::string &buffer) { Lines lines; LockGuard lock(mutex); error.code = git_diff_blob_to_buffer( blob.get(), nullptr, buffer.c_str(), buffer.size(), nullptr, &options, nullptr, nullptr, [](const git_diff_delta *delta, const git_diff_hunk *hunk, void *payload) { //Based on https://github.com/atom/git-diff/blob/master/lib/git-diff-view.coffee auto lines = static_cast(payload); auto start = hunk->new_start - 1; auto end = hunk->new_start + hunk->new_lines - 1; if(hunk->old_lines == 0 && hunk->new_lines > 0) lines->added.emplace_back(start, end); else if(hunk->new_lines == 0 && hunk->old_lines > 0) lines->removed.emplace_back(start); else lines->modified.emplace_back(start, end); return 0; }, nullptr, &lines); if(error) throw std::runtime_error(error.message()); return lines; } std::vector Git::Repository::Diff::get_hunks(const std::string &old_buffer, const std::string &new_buffer) { std::vector hunks; LockGuard lock(mutex); initialize(); git_diff_options options; git_diff_init_options(&options, GIT_DIFF_OPTIONS_VERSION); options.context_lines = 0; error.code = git_diff_buffers( old_buffer.c_str(), old_buffer.size(), nullptr, new_buffer.c_str(), new_buffer.size(), nullptr, &options, nullptr, nullptr, [](const git_diff_delta *delta, const git_diff_hunk *hunk, void *payload) { auto hunks = static_cast *>(payload); hunks->emplace_back(hunk->old_start, hunk->old_lines, hunk->new_start, hunk->new_lines); return 0; }, nullptr, &hunks); if(error) throw std::runtime_error(error.message()); return hunks; } std::string Git::Repository::Diff::get_details(const std::string &buffer, int line_nr) { std::pair details; details.second = line_nr; LockGuard lock(mutex); error.code = git_diff_blob_to_buffer( blob.get(), nullptr, buffer.c_str(), buffer.size(), nullptr, &options, nullptr, nullptr, nullptr, [](const git_diff_delta *delta, const git_diff_hunk *hunk, const git_diff_line *line, void *payload) { auto details = static_cast *>(payload); auto line_nr = details->second; auto start = hunk->new_start - 1; auto end = hunk->new_start + hunk->new_lines - 1; if(line_nr == start || (line_nr >= start && line_nr < end)) { if(details->first.empty()) details->first += std::string(hunk->header, hunk->header_len); details->first += line->origin + std::string(line->content, line->content_len); } return 0; }, &details); if(error) throw std::runtime_error(error.message()); return details.first; } Git::Repository::Repository(const boost::filesystem::path &path) { git_repository *repository_ptr; { LockGuard lock(mutex); error.code = git_repository_open_ext(&repository_ptr, path.generic_string().c_str(), 0, nullptr); if(error) throw std::runtime_error(error.message()); } repository = std::unique_ptr>(repository_ptr, [](git_repository *ptr) { git_repository_free(ptr); }); work_path = get_work_path(); if(work_path.empty()) throw std::runtime_error("Could not find work path"); auto git_directory = Gio::File::create_for_path(get_path().string()); monitor = git_directory->monitor_directory(Gio::FileMonitorFlags::FILE_MONITOR_WATCH_MOVES); monitor_changed_connection = monitor->signal_changed().connect( [this](const Glib::RefPtr &file, const Glib::RefPtr &, Gio::FileMonitorEvent monitor_event) { if(monitor_event != Gio::FileMonitorEvent::FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { this->clear_saved_status(); } }, false); } Git::Repository::~Repository() { monitor_changed_connection.disconnect(); } Git::Repository::Status Git::Repository::get_status() { { LockGuard lock(saved_status_mutex); if(has_saved_status) return saved_status; } struct Data { const boost::filesystem::path &work_path; Status status = {}; }; Data data{work_path}; { LockGuard lock(mutex); error.code = git_status_foreach( repository.get(), [](const char *path, unsigned int status_flags, void *payload) { auto data = static_cast(payload); bool new_ = false; bool modified = false; if((status_flags & (GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_NEW)) > 0) new_ = true; else if((status_flags & (GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_MODIFIED)) > 0) modified = true; boost::filesystem::path rel_path(path); do { if(new_) data->status.added.emplace((data->work_path / rel_path).generic_string()); else if(modified) data->status.modified.emplace((data->work_path / rel_path).generic_string()); rel_path = rel_path.parent_path(); } while(!rel_path.empty()); return 0; }, &data); if(error) throw std::runtime_error(error.message()); } LockGuard lock(saved_status_mutex); saved_status = std::move(data.status); has_saved_status = true; return saved_status; } void Git::Repository::clear_saved_status() { LockGuard lock(saved_status_mutex); saved_status = {}; has_saved_status = false; } boost::filesystem::path Git::Repository::get_work_path() noexcept { LockGuard lock(mutex); return Git::path(git_repository_workdir(repository.get())); } boost::filesystem::path Git::Repository::get_path() noexcept { LockGuard lock(mutex); return Git::path(git_repository_path(repository.get())); } boost::filesystem::path Git::Repository::get_root_path(const boost::filesystem::path &path) { git_buf root = {nullptr, 0, 0}; LockGuard lock(mutex); initialize(); error.code = git_repository_discover(&root, path.generic_string().c_str(), 0, nullptr); if(error) throw std::runtime_error(error.message()); auto root_path = Git::path(root.ptr, root.size); #if LIBGIT2_VER_MAJOR > 0 || (LIBGIT2_VER_MAJOR == 0 && LIBGIT2_VER_MINOR >= 28) git_buf_dispose(&root); #else git_buf_free(&root); #endif return root_path; } Git::Repository::Diff Git::Repository::get_diff(const boost::filesystem::path &path) { return Diff(path, repository.get()); } std::string Git::Repository::get_branch() noexcept { std::string branch; git_reference *reference; LockGuard lock(mutex); error.code = git_repository_head(&reference, repository.get()); if(!error) { if(auto reference_name_cstr = git_reference_name(reference)) { std::string reference_name(reference_name_cstr); size_t pos; if((pos = reference_name.rfind('/')) != std::string::npos) { if(pos + 1 < reference_name.size()) branch = reference_name.substr(pos + 1); } else if((pos = reference_name.rfind('\\')) != std::string::npos) { if(pos + 1 < reference_name.size()) branch = reference_name.substr(pos + 1); } } git_reference_free(reference); } return branch; } void Git::initialize() noexcept { if(!initialized) { git_libgit2_init(); initialized = true; } } std::shared_ptr Git::get_repository(const boost::filesystem::path &path) { auto root_path = Repository::get_root_path(path).generic_string(); static Mutex mutex; static std::unordered_map> cache GUARDED_BY(mutex); LockGuard lock(mutex); auto it = cache.find(root_path); if(it == cache.end()) it = cache.emplace(root_path, std::weak_ptr()).first; auto instance = it->second.lock(); if(!instance) it->second = instance = std::shared_ptr(new Repository(root_path)); return instance; } boost::filesystem::path Git::path(const char *cpath, boost::optional cpath_length_) noexcept { if(!cpath) return boost::filesystem::path(); auto cpath_length = cpath_length_.value_or(strlen(cpath)); if(cpath_length > 0 && (cpath[cpath_length - 1] == '/' || cpath[cpath_length - 1] == '\\')) return std::string(cpath, cpath_length - 1); else return std::string(cpath, cpath_length); }