Current section
Files
Jump to
Current section
Files
c_src/git_add.hpp
//-----------------------------------------------------------------------------
// This code was derived from
// https://github.com/libgit2/libgit2/blob/main/examples/add.c
//-----------------------------------------------------------------------------
// libgit2 "add" - modify the index
//-----------------------------------------------------------------------------
// 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>
enum index_mode {
INDEX_NONE,
INDEX_ADD
};
struct index_options {
index_options()
: dry_run(false), update(false), force(false)
, repo(nullptr), mode(INDEX_ADD)
{}
bool dry_run;
bool update;
bool force;
git_repository* repo;
index_mode mode;
ErlNifEnv* env;
std::vector<ERL_NIF_TERM> files;
};
// Forward declarations for helpers
static ERL_NIF_TERM
parse_opts(ErlNifEnv* env, std::vector<std::string>& file_specs, ERL_NIF_TERM file_specs_list, ERL_NIF_TERM opts, index_options& o)
{
ERL_NIF_TERM spec, opt;
file_specs.clear();
while (enif_get_list_cell(env, file_specs_list, &spec, &file_specs_list)) {
ErlNifBinary bin;
if (!enif_inspect_binary(env, spec, &bin)) [[unlikely]]
return raise_badarg_exception(env, spec);
file_specs.emplace_back(bin_to_str(bin));
}
while (enif_get_list_cell(env, opts, &opt, &opts)) {
if (enif_is_identical(opt, ATOM_DRY_RUN)) o.dry_run = true;
else if (enif_is_identical(opt, ATOM_UPDATE)) o.update = true;
else if (enif_is_identical(opt, ATOM_FORCE)) o.force = true;
else [[unlikely]]
return raise_badarg_exception(env, opt);
}
return 0;
}
// This callback is called for each file under consideration by
// git_index_(update|add)_all.
// It makes use of the callback's ability to abort the action.
int visit_matched_cb(const char* path, [[maybe_unused]] const char* matched_pathspec, void* payload)
{
auto opts = static_cast<index_options*>(payload);
unsigned status;
/* Get the file status */
if (git_status_file(&status, opts->repo, path) < 0)
return -1; // Abort
auto should_add = (status & GIT_STATUS_WT_MODIFIED) || (status & GIT_STATUS_WT_NEW);
int ret = opts->dry_run || !should_add; // Skip = 1, Add = 0
if (should_add)
opts->files.push_back(make_binary(opts->env, path));
return ret;
}
// Implementation of "add" logic
ERL_NIF_TERM lg2_add(ErlNifEnv* env, git_repository* repo, ERL_NIF_TERM file_specs_list, ERL_NIF_TERM opts)
{
assert(repo);
git_index_matched_path_cb matched_cb = NULL;
index_options options;
std::vector<std::string> path_specs;
git_strarray array{};
options.env = env;
options.repo = repo;
options.mode = INDEX_ADD;
// Parse the options & arguments
auto res = parse_opts(env, path_specs, file_specs_list, opts, options);
if (res != 0) [[unlikely]]
return res;
SmartPtr<git_index> index(git_index_free);
// Grab the repository's index
if (git_repository_index(&index, repo) != GIT_OK)
return make_git_error(env, "Could not open repository index");
// Setup a callback if the requested options need it
matched_cb = &visit_matched_cb;
array.count = path_specs.size();
std::vector<const char*> strings(array.count);
for (auto i = 0u; i < array.count; ++i)
strings[i] = path_specs[i].c_str();
array.strings = const_cast<char**>(&strings[0]);
auto flags = options.force ? GIT_INDEX_ADD_FORCE : 0;
// Perform the requested action with the index and files
auto err = options.update
? git_index_update_all(index, &array, matched_cb, &options)
: git_index_add_all(index, &array, flags, matched_cb, &options);
// Cleanup memory
if (err != GIT_OK)
return err;
if (options.files.size())
git_index_write(index);
auto files = enif_make_list_from_array(env, &options.files.front(), options.files.size());
if (options.files.empty())
return ATOM_NIL;
auto mode = options.dry_run ? ATOM_DRY_RUN : ATOM_ADDED;
ERL_NIF_TERM keys[] = {ATOM_MODE, ATOM_FILES};
ERL_NIF_TERM vals[] = {mode, files};
ERL_NIF_TERM map;
return enif_make_map_from_arrays(env, keys, vals, 2, &map) ? map : make_error(env, ATOM_ENOMEM);
}