mirror of https://gitlab.com/cppit/jucipp
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
301 lines
10 KiB
301 lines
10 KiB
#include "git.h" |
|
#include <cstring> |
|
|
|
bool Git::initialized=false; |
|
std::mutex Git::mutex; |
|
|
|
std::string Git::Error::message() noexcept { |
|
const git_error *last_error = giterr_last(); |
|
if(last_error==nullptr) |
|
return std::string(); |
|
else |
|
return last_error->message; |
|
} |
|
|
|
Git::Repository::Diff::Diff(const boost::filesystem::path &path, git_repository *repository) : repository(repository) { |
|
blob=std::shared_ptr<git_blob>(nullptr, [](git_blob *blob) { |
|
if(blob) git_blob_free(blob); |
|
}); |
|
Error error; |
|
std::lock_guard<std::mutex> lock(mutex); |
|
auto spec="HEAD:"+path.generic_string(); |
|
error.code = git_revparse_single(reinterpret_cast<git_object**>(&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; |
|
} |
|
|
|
//Based on https://github.com/atom/git-diff/blob/master/lib/git-diff-view.coffee |
|
int Git::Repository::Diff::hunk_cb(const git_diff_delta *delta, const git_diff_hunk *hunk, void *payload) noexcept { |
|
auto lines=static_cast<Lines*>(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; |
|
} |
|
|
|
Git::Repository::Diff::Lines Git::Repository::Diff::get_lines(const std::string &buffer) { |
|
Lines lines; |
|
Error error; |
|
std::lock_guard<std::mutex> lock(mutex); |
|
#if LIBGIT2_SOVERSION>=23 |
|
error.code=git_diff_blob_to_buffer(blob.get(), nullptr, buffer.c_str(), buffer.size(), nullptr, &options, nullptr, nullptr, hunk_cb, nullptr, &lines); |
|
#else |
|
error.code=git_diff_blob_to_buffer(blob.get(), nullptr, buffer.c_str(), buffer.size(), nullptr, &options, nullptr, hunk_cb, nullptr, &lines); |
|
#endif |
|
if(error) |
|
throw std::runtime_error(error.message()); |
|
return lines; |
|
} |
|
|
|
int Git::Repository::Diff::line_cb(const git_diff_delta *delta, const git_diff_hunk *hunk, const git_diff_line *line, void *payload) noexcept { |
|
auto details=static_cast<std::pair<std::string, int> *>(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; |
|
} |
|
|
|
std::string Git::Repository::Diff::get_details(const std::string &buffer, int line_nr) { |
|
std::pair<std::string, int> details; |
|
details.second=line_nr; |
|
Error error; |
|
std::lock_guard<std::mutex> lock(mutex); |
|
#if LIBGIT2_SOVERSION>=23 |
|
error.code=git_diff_blob_to_buffer(blob.get(), nullptr, buffer.c_str(), buffer.size(), nullptr, &options, nullptr, nullptr, nullptr, line_cb, &details); |
|
#else |
|
error.code=git_diff_blob_to_buffer(blob.get(), nullptr, buffer.c_str(), buffer.size(), nullptr, &options, nullptr, nullptr, line_cb, &details); |
|
#endif |
|
if(error) |
|
throw std::runtime_error(error.message()); |
|
return details.first; |
|
} |
|
|
|
Git::Repository::Repository(const boost::filesystem::path &path) { |
|
git_repository *repository_ptr; |
|
{ |
|
Error error; |
|
std::lock_guard<std::mutex> lock(mutex); |
|
auto path_str=path.generic_string(); |
|
error.code = git_repository_open_ext(&repository_ptr, path_str.c_str(), 0, nullptr); |
|
if(error) |
|
throw std::runtime_error(error.message()); |
|
} |
|
repository=std::unique_ptr<git_repository, std::function<void(git_repository *)> >(repository_ptr, [](git_repository *ptr) { |
|
git_repository_free(ptr); |
|
}); |
|
|
|
work_path=get_work_path(); |
|
|
|
auto git_path_str=boost::filesystem::canonical(get_path()).string(); |
|
auto git_directory=Glib::wrap(g_file_new_for_path(git_path_str.c_str())); //TODO: report missing constructor in giomm |
|
#if GLIBMM_MAJOR_VERSION>2 || (GLIBMM_MAJOR_VERSION==2 && GLIBMM_MINOR_VERSION>=45) |
|
monitor=git_directory->monitor_directory(Gio::FileMonitorFlags::FILE_MONITOR_WATCH_MOVES); |
|
#else |
|
monitor=git_directory->monitor_directory(Gio::FileMonitorFlags::FILE_MONITOR_SEND_MOVED); |
|
#endif |
|
|
|
monitor_changed_connection=monitor->signal_changed().connect([this](const Glib::RefPtr<Gio::File> &file, |
|
const Glib::RefPtr<Gio::File>&, |
|
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(); |
|
} |
|
|
|
std::string Git::Repository::status_string(STATUS status) noexcept { |
|
switch(status) { |
|
case STATUS::CURRENT: return "current"; |
|
case STATUS::NEW: return "new"; |
|
case STATUS::MODIFIED: return "modified"; |
|
case STATUS::DELETED: return "deleted"; |
|
case STATUS::RENAMED: return "renamed"; |
|
case STATUS::TYPECHANGE: return "typechange"; |
|
case STATUS::UNREADABLE: return "unreadable"; |
|
case STATUS::IGNORED: return "ignored"; |
|
case STATUS::CONFLICTED: return "conflicted"; |
|
default: return ""; |
|
} |
|
} |
|
|
|
int Git::Repository::status_callback(const char *path, unsigned int status_flags, void *data) noexcept { |
|
auto callback=static_cast<std::function<void(const char *path, STATUS status)>*>(data); |
|
|
|
STATUS status; |
|
if((status_flags&(GIT_STATUS_INDEX_NEW|GIT_STATUS_WT_NEW))>0) |
|
status=STATUS::NEW; |
|
else if((status_flags&(GIT_STATUS_INDEX_MODIFIED|GIT_STATUS_WT_MODIFIED))>0) |
|
status=STATUS::MODIFIED; |
|
else if((status_flags&(GIT_STATUS_INDEX_DELETED|GIT_STATUS_WT_DELETED))>0) |
|
status=STATUS::DELETED; |
|
else if((status_flags&(GIT_STATUS_INDEX_RENAMED|GIT_STATUS_WT_RENAMED))>0) |
|
status=STATUS::RENAMED; |
|
else if((status_flags&(GIT_STATUS_INDEX_TYPECHANGE|GIT_STATUS_WT_TYPECHANGE))>0) |
|
status=STATUS::TYPECHANGE; |
|
#if LIBGIT2_SOVERSION>=23 |
|
else if((status_flags&(GIT_STATUS_WT_UNREADABLE))>0) |
|
status=STATUS::UNREADABLE; |
|
#endif |
|
else if((status_flags&(GIT_STATUS_IGNORED))>0) |
|
status=STATUS::IGNORED; |
|
#if LIBGIT2_SOVERSION>=23 |
|
else if((status_flags&(GIT_STATUS_CONFLICTED))>0) |
|
status=STATUS::CONFLICTED; |
|
#endif |
|
else |
|
status=STATUS::CURRENT; |
|
|
|
if(*callback) |
|
(*callback)(path, status); |
|
|
|
return 0; |
|
} |
|
|
|
Git::Repository::Status Git::Repository::get_status() { |
|
{ |
|
std::unique_lock<std::mutex> lock(saved_status_mutex); |
|
if(has_saved_status) |
|
return saved_status; |
|
} |
|
|
|
Status status; |
|
bool first=true; |
|
std::unique_lock<std::mutex> status_saved_lock(saved_status_mutex, std::defer_lock); |
|
std::function<void(const char *path, STATUS status)> callback=[this, &status, &first, &status_saved_lock](const char *path_cstr, STATUS status_enum) { |
|
if(first) { |
|
status_saved_lock.lock(); |
|
first=false; |
|
} |
|
boost::filesystem::path rel_path(path_cstr); |
|
do { |
|
if(status_enum==STATUS::MODIFIED) |
|
status.modified.emplace((work_path/rel_path).generic_string()); |
|
if(status_enum==STATUS::NEW) |
|
status.added.emplace((work_path/rel_path).generic_string()); |
|
rel_path=rel_path.parent_path(); |
|
} while(!rel_path.empty()); |
|
}; |
|
Error error; |
|
std::lock_guard<std::mutex> lock(mutex); |
|
error.code = git_status_foreach(repository.get(), status_callback, &callback); |
|
if(error) |
|
throw std::runtime_error(error.message()); |
|
saved_status=status; |
|
has_saved_status=true; |
|
if(status_saved_lock) |
|
status_saved_lock.unlock(); |
|
return status; |
|
} |
|
|
|
void Git::Repository::clear_saved_status() { |
|
std::unique_lock<std::mutex> lock(saved_status_mutex); |
|
saved_status.added.clear(); |
|
saved_status.modified.clear(); |
|
has_saved_status=false; |
|
} |
|
|
|
boost::filesystem::path Git::Repository::get_work_path() noexcept { |
|
std::lock_guard<std::mutex> lock(mutex); |
|
return Git::path(git_repository_workdir(repository.get())); |
|
} |
|
|
|
boost::filesystem::path Git::Repository::get_path() noexcept { |
|
std::lock_guard<std::mutex> lock(mutex); |
|
return Git::path(git_repository_path(repository.get())); |
|
} |
|
|
|
boost::filesystem::path Git::Repository::get_root_path(const boost::filesystem::path &path) { |
|
initialize(); |
|
git_buf root = {0, 0, 0}; |
|
{ |
|
Error error; |
|
std::lock_guard<std::mutex> lock(mutex); |
|
auto path_str=path.generic_string(); |
|
error.code = git_repository_discover(&root, path_str.c_str(), 0, nullptr); |
|
if(error) |
|
throw std::runtime_error(error.message()); |
|
} |
|
auto root_path=Git::path(root.ptr, root.size); |
|
git_buf_free(&root); |
|
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; |
|
if(git_repository_head(&reference, repository.get())==0) { |
|
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 { |
|
std::lock_guard<std::mutex> lock(mutex); |
|
if(!initialized) { |
|
#if LIBGIT2_SOVERSION>=22 |
|
git_libgit2_init(); |
|
#else |
|
git_threads_init(); |
|
#endif |
|
initialized=true; |
|
} |
|
} |
|
|
|
std::shared_ptr<Git::Repository> Git::get_repository(const boost::filesystem::path &path) { |
|
initialize(); |
|
static std::unordered_map<std::string, std::weak_ptr<Git::Repository> > cache; |
|
static std::mutex mutex; |
|
|
|
std::lock_guard<std::mutex> lock(mutex); |
|
auto root_path=Repository::get_root_path(path).generic_string(); |
|
auto it=cache.find(root_path); |
|
if(it==cache.end()) |
|
it=cache.emplace(root_path, std::weak_ptr<Git::Repository>()).first; |
|
auto instance=it->second.lock(); |
|
if(!instance) |
|
it->second=instance=std::shared_ptr<Repository>(new Repository(root_path)); |
|
return instance; |
|
} |
|
|
|
boost::filesystem::path Git::path(const char *cpath, size_t cpath_length) noexcept { |
|
if(cpath_length==static_cast<size_t>(-1)) |
|
cpath_length=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); |
|
}
|
|
|