Current section

Files

Jump to
egit c_src git_status.hpp
Raw

c_src/git_status.hpp

//-----------------------------------------------------------------------------
// This code was derived from
// https://github.com/libgit2/libgit2/blob/main/examples/status.c
//-----------------------------------------------------------------------------
// libgit2 "status" - get the repository status
//-----------------------------------------------------------------------------
// Written by the libgit2 contributors
//
// To the extent possible under law, the author(s) have dedicated all copyright
// and related and neighboring rights to this software to the public domain
// worldwide. This software is distributed without any warranty.
//
// You should have received a copy of the CC0 Public Domain Dedication along
// with this software. If not, see
// <http://creativecommons.org/publicdomain/zero/1.0/>.
//-----------------------------------------------------------------------------
#pragma once
#include <git2/common.h>
namespace {
struct VisitorState {
VisitorState(std::vector<ERL_NIF_TERM>& submods, ErlNifEnv* env)
: submods(submods), env(env)
{}
std::vector<ERL_NIF_TERM>& submods;
ErlNifEnv* env;
};
}
ERL_NIF_TERM lg2_status(ErlNifEnv* env, git_repository* repo, ERL_NIF_TERM opts)
{
assert(repo);
git_status_options statusopt = GIT_STATUS_OPTIONS_INIT;
statusopt.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
statusopt.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX |
GIT_STATUS_OPT_SORT_CASE_SENSITIVELY;
std::vector<std::string> paths;
auto showbranch = false;
auto showsubmod = false;
if (git_repository_is_bare(repo)) [[unlikely]]
return make_error(env,
std::format("Cannot report status on bare repository {}", git_repository_path(repo)));
//---- Parse args
for (ERL_NIF_TERM opt; enif_get_list_cell(env, opts, &opt, &opts);) {
int arity;
const ERL_NIF_TERM* tagvals;
if (enif_get_tuple(env, opt, &arity, &tagvals) && arity == 2) {
ERL_NIF_TERM val;
if (parse_atom_if(env, ATOM_UNTRACKED, tagvals, val)) [[likely]] {
if (enif_is_identical(ATOM_NONE, val)) statusopt.flags &= ~GIT_STATUS_OPT_INCLUDE_UNTRACKED;
else if (enif_is_identical(ATOM_NORMAL, val)) statusopt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
else if (enif_is_identical(ATOM_RECURSIVE, val)) statusopt.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED |
GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
else
return raise_badarg_exception(env, opt);
}
else if (enif_is_identical(ATOM_PATHS, tagvals[0]) && enif_is_list(env, tagvals[1])) {
for (ERL_NIF_TERM o, list=tagvals[1]; enif_get_list_cell(env, list, &o, &list);) {
std::string tmp;
if (!term_to_str(env, o, tmp))
return raise_badarg_exception(env, opt);
paths.push_back(tmp);
}
}
else
return raise_badarg_exception(env, opt);
}
else if (enif_is_identical(ATOM_BRANCH, opt)) showbranch = true;
else if (enif_is_identical(ATOM_IGNORED, opt)) statusopt.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED;
else if (enif_is_identical(ATOM_IGNORE_SUBMODULES, opt)) statusopt.flags |= GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
else if (enif_is_identical(ATOM_SUBMODULES, opt)) {
showsubmod = true;
statusopt.flags &= ~GIT_STATUS_OPT_EXCLUDE_SUBMODULES;
}
else [[unlikely]]
return raise_badarg_exception(env, opt);
}
std::vector<const char*> cpaths;
if (paths.size()) {
cpaths.reserve(paths.size());
for (auto& s : paths)
cpaths.push_back(s.c_str());
statusopt.pathspec = { .strings = const_cast<char**>(&cpaths.front()), .count = cpaths.size() };
}
//---- Process status command
// We use `git_status_list_new()` to generate a list of status
// information which lets us iterate over it at our
// convenience and extract the data we want to show out of
// each entry.
//
// You can use `git_status_foreach()` or
// `git_status_foreach_ext()` if you'd prefer to execute a
// callback for each entry. The latter gives you more control
// about what results are presented.
SmartPtr<git_status_list> status(git_status_list_free);
if (git_status_list_new(&status, repo, &statusopt) != GIT_OK) [[unlikely]]
return make_git_error(env, "Could not get status");
std::vector<ERL_NIF_TERM> keys;
std::vector<ERL_NIF_TERM> vals;
if (showbranch) {
SmartPtr<git_reference> head(git_reference_free);
auto error = git_repository_head(&head, repo);
auto branch = error == GIT_OK ? git_reference_shorthand(head) : nullptr;
if (error < 0 && error != GIT_EUNBORNBRANCH && error != GIT_ENOTFOUND)
return make_git_error(env, "Failed to get current branch");
keys.push_back(ATOM_BRANCH);
vals.push_back(branch ? make_binary(env, branch) : ATOM_NIL);
}
if (showsubmod) {
std::vector<ERL_NIF_TERM> submods;
auto visit = [](git_submodule* sm, const char*, void* payload)
{
VisitorState& state = *static_cast<VisitorState*>(payload);
auto path = git_submodule_path(sm);
auto url = git_submodule_url(sm);
state.submods.push_back(
enif_make_tuple2(state.env, make_binary(state.env, path), make_binary(state.env, url)));
return 0;
};
VisitorState state(submods, env);
if (git_submodule_foreach(repo, visit, &state) != GIT_OK) [[unlikely]]
return make_git_error(env, "Cannot iterate submodules");
if (submods.size()) {
keys.push_back(ATOM_SUBMODULES);
vals.push_back(enif_make_list_from_array(env, &submods.front(), submods.size()));
}
}
size_t maxi = git_status_list_entrycount(status);
std::vector<ERL_NIF_TERM> index_changes;
std::vector<ERL_NIF_TERM> wt_changes;
std::vector<ERL_NIF_TERM> untracked;
std::vector<ERL_NIF_TERM> ignored;
std::vector<ERL_NIF_TERM> submodules;
// Changes in index
for (auto i = 0u; i < maxi; ++i) {
auto s = git_status_byindex(status, i);
if (s->status == GIT_STATUS_CURRENT)
continue;
auto istatus =
(s->status & GIT_STATUS_INDEX_NEW) ? ATOM_NEW :
(s->status & GIT_STATUS_INDEX_MODIFIED) ? ATOM_MODIFIED :
(s->status & GIT_STATUS_INDEX_DELETED) ? ATOM_DELETED :
(s->status & GIT_STATUS_INDEX_RENAMED) ? ATOM_RENAMED :
(s->status & GIT_STATUS_INDEX_TYPECHANGE) ? ATOM_TYPECHANGE : ATOM_NIL;
auto wstatus =
(s->status & GIT_STATUS_WT_MODIFIED) ? ATOM_MODIFIED :
(s->status & GIT_STATUS_WT_DELETED) ? ATOM_DELETED :
(s->status & GIT_STATUS_WT_RENAMED) ? ATOM_RENAMED :
(s->status & GIT_STATUS_WT_TYPECHANGE) ? ATOM_TYPECHANGE : ATOM_NIL;
auto idx = s->head_to_index;
auto wt = s->index_to_workdir;
auto fmt_path = [env](auto& v, auto xstatus, auto delta) {
ERL_NIF_TERM val = ATOM_NIL;
if (delta->old_file.path && delta->new_file.path) {
val = strcmp(delta->old_file.path, delta->new_file.path) == 0
? enif_make_tuple2(env, xstatus, make_binary(env, delta->old_file.path))
: enif_make_tuple3(env, xstatus,
make_binary(env, delta->old_file.path),
make_binary(env, delta->new_file.path));
}
else if (delta->old_file.path && !delta->new_file.path)
val = enif_make_tuple2(env, xstatus, make_binary(env, delta->old_file.path));
else if (delta->new_file.path && !delta->old_file.path)
val = enif_make_tuple2(env, xstatus, make_binary(env, delta->new_file.path));
if (val != ATOM_NIL)
v.push_back(val);
};
if (idx && istatus != ATOM_NIL)
fmt_path(index_changes, istatus, idx);
if (wt && wstatus != ATOM_NIL)
fmt_path(wt_changes, wstatus, wt);
if (s->status == GIT_STATUS_WT_NEW && wt->old_file.path)
untracked.push_back(make_binary(env, wt->old_file.path));
if (s->status == GIT_STATUS_IGNORED && wt->old_file.path)
ignored.push_back(make_binary(env, wt->old_file.path));
// A commit in a tree is how submodules are stored, take a look at its status
auto smstatus = 0u;
if (wt && wt->new_file.mode == GIT_FILEMODE_COMMIT &&
git_submodule_status(&smstatus, repo, wt->new_file.path, GIT_SUBMODULE_IGNORE_UNSPECIFIED) == GIT_OK)
{
auto smst =
(smstatus & GIT_SUBMODULE_STATUS_WD_MODIFIED) ? ATOM_NEW :
(smstatus & GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED) ? ATOM_MODIFIED :
(smstatus & GIT_SUBMODULE_STATUS_WD_WD_MODIFIED) ? ATOM_MODIFIED :
(smstatus & GIT_SUBMODULE_STATUS_WD_UNTRACKED) ? ATOM_UNTRACKED : ATOM_NIL;
if (smst != ATOM_NIL) {
submodules.push_back(
enif_make_tuple2(env, smst, make_binary(env,
idx && idx->old_file.path ? idx->old_file.path :
idx && idx->new_file.path ? idx->new_file.path :
wt && wt->old_file.path ? wt->old_file.path :
wt && wt->new_file.path ? wt->new_file.path : "")));
}
}
}
if (index_changes.size()) {
keys.push_back(ATOM_INDEX);
vals.push_back(enif_make_list_from_array(env, &index_changes.front(), index_changes.size()));
}
if (wt_changes.size()) {
keys.push_back(ATOM_WORKTREE);
vals.push_back(enif_make_list_from_array(env, &wt_changes.front(), wt_changes.size()));
}
if (untracked.size()) {
keys.push_back(ATOM_UNTRACKED);
vals.push_back(enif_make_list_from_array(env, &untracked.front(), untracked.size()));
}
if (ignored.size()) {
keys.push_back(ATOM_IGNORED);
vals.push_back(enif_make_list_from_array(env, &ignored.front(), ignored.size()));
}
if (submodules.size()) {
keys.push_back(ATOM_SUBMODULES);
vals.push_back(enif_make_list_from_array(env, &submodules.front(), submodules.size()));
}
ERL_NIF_TERM map;
return enif_make_map_from_arrays(env, &keys.front(), &vals.front(), keys.size(), &map)
? map : make_error(env, ATOM_ENOMEM);
}