Uncouple some of the logic from Nix library and add basic tests
Dependencies
- [2]
FYYWB54CFix conflicts - [3]
H5RKFV7YDon't directly execute Pijul from fetcher - [4]
XGCRPWKLMove repo interaction into separate source file - [5]
U5AKEHEQFix build for Nix 2.19 to 2.21 - [6]
3KEFKH5FImport existing code - [*]
SGQQ6LNZAttach Boost and SYSTEM def to Nix target instead of plugin - [*]
XXCRSVXO - [*]
7YS2X7JJFix compilation against older Nix versions (tested with 2.13.6)
Change contents
- edit in src/repo.h at line 3
#include "compat.h" - edit in src/repo.h at line 7
#include <set> - edit in src/repo.h at line 10
#include <types.hh> - replacement in src/repo.h at line 20
std::string clone(const std::string_view &repoUrl,const nix::PathView &repoDir,const std::optional<std::string_view> &channel = {},const std::optional<std::string_view> &state = {});void clone(const std::string_view &repoUrl,const Path &repoDir,const std::optional<std::string_view> &channel = {},const std::optional<std::string_view> &state = {}); - replacement in src/repo.h at line 25
std::string record(const std::string_view &message, const nix::PathView &repoDir, const std::vector<nix::PathView> &paths = {});void record(const std::string_view &message, const Path &repoDir, const std::vector<Path> &paths = {}); - replacement in src/repo.h at line 27
bool isRepoDirty(const nix::PathView &repoDir);bool isRepoDirty(const Path &repoDir); - replacement in src/repo.h at line 29
std::set<std::string> getTrackedFiles(const nix::PathView &repoDir);std::set<Path> getTrackedFiles(const Path &repoDir); - replacement in src/repo.h at line 31
RepoStatus getRepoStatus(const nix::PathView &repoPath);RepoStatus getRepoStatus(const Path &repoPath); - replacement in src/repo.h at line 33
std::pair<std::string, uint64_t> getState(const nix::PathView &repoPath);std::pair<std::string, uint64_t> getState(const Path &repoPath); - replacement in src/repo.h at line 35
std::string getRepoChannel(const nix::PathView &repoPath);std::string getRepoChannel(const Path &repoPath); - edit in src/repo.cpp at line 2
#include <types.hh>#include <util.hh> - edit in src/repo.cpp at line 3
#if NIX_VERSION >= 0x021900#include <processes.hh>#endif - edit in src/repo.cpp at line 8
using namespace nix; - replacement in src/repo.cpp at line 14
std::string runPijul(Strings args, PathView chdir, std::optional<std::string> input = {}, bool isInteractive = {})void runPijul(const StringList &args, const std::optional<Path> &chdir, OutputMode mode = OutputMode::Standard) - replacement in src/repo.cpp at line 16[4.1204]→[4.1204:1375](∅→∅),[4.1375]→[3.627:657](∅→∅),[3.657]→[4.1410:1649](∅→∅),[4.1410]→[4.1410:1649](∅→∅)
auto program = "pijul"sv;auto res = runProgram(RunOptions{.program = std::string(program),.searchPath = true,.args = std::move(args),.chdir = Path(chdir),.input = std::move(input),.isInteractive = isInteractive,});if (!statusOk(res.first)) {throw ExecError(res.first, "program '%1%' %2%", program, statusToString(res.first));}return res.second;runProcess("pijul", args, chdir ? chdir->c_str() : nullptr, mode); - replacement in src/repo.cpp at line 19
std::string record(const std::string_view &message, const PathView &chdir, const std::vector<std::string_view> &paths)void record(const std::string_view &message, const Path &chdir, const std::vector<Path> &paths) - replacement in src/repo.cpp at line 21
Strings args{"record", "--message"};StringList args{"record", "--message"}; - replacement in src/repo.cpp at line 30
return runPijul(args, chdir, {}, true);runPijul(args, chdir, OutputMode::Interactive); - replacement in src/repo.cpp at line 33
std::string clone(const std::string_view &repoUrl,const std::string_view &repoDir,const std::optional<std::string_view> &channel,const std::optional<std::string_view> &state)void clone(const std::string_view &repoUrl,const Path &repoDir,const std::optional<std::string_view> &channel,const std::optional<std::string_view> &state) - replacement in src/repo.cpp at line 38
Strings args{"clone"s};StringList args{"clone"s}; - replacement in src/repo.cpp at line 53
return runPijul(args, {}, {}, true);runPijul(args, {}, OutputMode::Interactive); - replacement in src/repo.cpp at line 56
bool isRepoDirty(const PathView &chdir = {})bool isRepoDirty(const Path &chdir = {}) - replacement in src/repo.cpp at line 58
const std::string &diffOutput = runPijul({"diff", "--json"}, chdir);std::string diffOutput;runPijul({"diff", "--json"}, chdir, OutputMode::WithOutput{diffOutput}); - replacement in src/repo.cpp at line 74
std::set<std::string> getTrackedFiles(const nix::PathView &repoDir)std::set<Path> getTrackedFiles(const Path &repoDir) - replacement in src/repo.cpp at line 76
auto output = runPijul({"list"}, repoDir);std::string output;runPijul({"list"}, repoDir, OutputMode::WithOutput{output});size_t pos = 0;size_t next;std::set<Path> out;while ((next = output.find_first_of("\n\r", pos)) != std::string::npos) {out.emplace(output.substr(pos, next - pos));pos = next + 1;} - replacement in src/repo.cpp at line 89
return tokenizeString<std::set<std::string>>(output, "\n\r");return out; - replacement in src/repo.cpp at line 92
RepoStatus getRepoStatus(const PathView &repoPath)RepoStatus getRepoStatus(const Path &repoPath) - replacement in src/repo.cpp at line 104
std::pair<std::string, uint64_t> getState(const PathView &repoPath)std::pair<std::string, uint64_t> getState(const Path &repoPath) - replacement in src/repo.cpp at line 106
const auto &output = runPijul({"log", "--output-format", "json", "--state", "--limit", "1"}, Path(repoPath));std::string output;runPijul({"log", "--output-format", "json", "--state", "--limit", "1"}, Path(repoPath), OutputMode::WithOutput{output}); - replacement in src/repo.cpp at line 120
std::string getRepoChannel(const PathView &repoPath)std::string getRepoChannel(const Path &repoPath) - replacement in src/repo.cpp at line 122
const auto &output = runPijul({"channel"}, Path(repoPath));std::string output;runPijul({"channel"}, Path(repoPath), OutputMode::WithOutput{output}); - replacement in src/repo.cpp at line 142
throw Error("could not parse current channel"s);fatal("could not parse current channel"); - edit in src/fetcher.cpp at line 1
#ifdef NIX_VERSION - edit in src/fetcher.cpp at line 11
#if NIX_VERSION >= 0x021900#include <processes.hh>#endif - edit in src/fetcher.cpp at line 25
using nixpluginpijul::getTrackedFiles; - edit in src/fetcher.cpp at line 28
using nixpluginpijul::getTrackedFiles; - replacement in src/fetcher.cpp at line 165
record(*commitMsg, *root, {path.rel()});record(*commitMsg, *root, {Path(path.rel())}); - replacement in src/fetcher.cpp at line 173
record(*commitMsg, *root, {file});record(*commitMsg, *root, {std::string(file)}); - replacement in src/fetcher.cpp at line 321
static std::pair<StorePath, Input> fetchLocal(const ref<Store> &store, const Input &_input, const std::string_view &path)static std::pair<StorePath, Input> fetchLocal(const ref<Store> &store, const Input &_input, const Path &path) - edit in src/fetcher.cpp at line 413[4.12979]
#endif - file addition: compat.h[4.15]
#ifndef NIX_PLUGIN_PIJUL_COMPAT_H#define NIX_PLUGIN_PIJUL_COMPAT_H#ifdef NIX_VERSION#include <error.hh>#include <types.hh>#else#include <filesystem>#include <string>#include <vector>#endif#include <variant>namespace nixpluginpijul{#ifdef NIX_VERSIONusing StringList = nix::Strings;using Path = nix::Path;#elseusing StringList = std::vector<std::string>;using Path = std::filesystem::path;#endifstruct OutputMode final {enum Units {Standard,Interactive,};struct WithOutput {std::string &output;};OutputMode(Units data): m_data(data){}OutputMode(WithOutput data): m_data(data){}bool interactive(){if (auto *t = get_if<Units>(&m_data)) {return *t == Interactive;} else {return false;}}std::string *output(){if (auto *t = get_if<WithOutput>(&m_data)) {return &t->output;} else {return nullptr;}}private:std::variant<Units, WithOutput> m_data;};void runProcess(const char *path, const StringList &args, const char *chdir = nullptr, OutputMode mode = OutputMode::Standard);[[noreturn]] void fatal(const char *text);}#endif // NIX_PLUGIN_PIJUL_COMPAT_H - file addition: compat.cpp[4.15]
#include "compat.h"#ifdef NIX_VERSION#if NIX_VERSION >= 0x021900#include <processes.hh>#else#include <util.hh>#endif#else#include <cstdio>#include <fcntl.h>#include <format>#include <spawn.h>#include <sys/wait.h>#include <sysexits.h>#endifusing namespace std::string_literals;using namespace std::string_view_literals;namespace nixpluginpijul{void runProcess(const char *p, const StringList &args, const char *chdir, OutputMode mode){#ifdef NIX_VERSIONauto res = nix::runProgram(nix::RunOptions{.program = std::string(p),.searchPath = true,.args = args,.chdir = Path(chdir),.isInteractive = mode.interactive(),});if (!nix::statusOk(res.first)) {throw nix::ExecError(res.first, "program '%1%' %2%", p, nix::statusToString(res.first));}if (mode.output()) {*mode.output() = std::move(res.second);}#elseint fds[2];posix_spawn_file_actions_t fa;posix_spawn_file_actions_init(&fa);if (mode.output()) {if (pipe(fds) != 0) {perror("failed to create pipe");exit(EX_OSERR);}posix_spawn_file_actions_adddup2(&fa, fds[1], 0);posix_spawn_file_actions_adddup2(&fa, fds[1], 1);}if (!mode.interactive()) {posix_spawn_file_actions_addclose(&fa, 2);}if (chdir) {posix_spawn_file_actions_addchdir_np(&fa, chdir);}std::vector<const char *> argv;argv.push_back(p);for (const auto &el : args) {argv.push_back(el.c_str());}pid_t pid;if (posix_spawnp(&pid, p, &fa, nullptr, const_cast<char *const *const>(argv.data()), nullptr) != 0) {perror("failed to spawn process");exit(EX_OSERR);}close(fds[1]);posix_spawn_file_actions_destroy(&fa);fd_set set;FD_ZERO(&set);int nfds = 0;if (mode.output()) {FD_SET(fds[0], &set);fcntl(fds[0], F_SETFL, fcntl(fds[0], F_GETFL) | O_NONBLOCK);nfds = 1;}sigset_t sigs;sigfillset(&sigs);sigdelset(&sigs, SIGCHLD);timespec timeout{.tv_sec = 1};bool running = true;int status;while (running) {// FIXME: this never catches the SIGCHLDint ret = pselect(nfds, &set, nullptr, nullptr, &timeout, &sigs);printf("%d\n", ret);if (ret == -1) {if (errno != EINTR) {perror("pselect");exit(EX_OSERR);}}ret = waitpid(pid, &status, WNOHANG);if (ret == -1) {perror("waitpid");exit(EX_OSERR);} else if (ret == pid) {running = false;ret = 1; // get the last data out of the pipe}if (ret > 0 && mode.output()) {char buf[4096];ssize_t len;do {len = read(fds[0], buf, sizeof(buf));if (len < 0 && errno != EWOULDBLOCK) {perror("read");exit(EX_OSERR);}if (len > 1) {mode.output()->append(buf, len);}} while (len > 0);}}if (mode.output()) {close(fds[0]);}if (status != 0) {fatal(std::format("process `{}' exited with status {}", p, WEXITSTATUS(status)).c_str());}#endif}void fatal(const char *text){#ifdef NIX_VERSIONthrow nix::Error("%1%", text);#elsefprintf(stderr, "fatal: %s\n", text);exit(EX_SOFTWARE);#endif}} - edit in src/CMakeLists.txt at line 2
add_library(PijulTesting STATIC) - replacement in src/CMakeLists.txt at line 4
target_sources(pijul PRIVATEset(SRCcompat.cppcompat.h - edit in src/CMakeLists.txt at line 11
target_sources(pijul PRIVATE ${SRC})target_sources(PijulTesting PRIVATE ${SRC})target_include_directories(PijulTesting PUBLIC ${CMAKE_CURRENT_LIST_DIR})target_compile_options(PijulTesting PUBLIC -O1 -ggdb) - file addition: autotests[9.1]
- file addition: repotest.cpp[0.7869]
#include "repo.h"using namespace nixpluginpijul;int main(){clone("https://nest.pijul.com/dblsaiko/nix-plugin-pijul", "nix-plugin-pijul");const std::set<Path> &set = getTrackedFiles("nix-plugin-pijul");for (const auto &item : set) {printf("- %s\n", item.c_str());}} - file addition: datetest.cpp[0.7869]
#include "repo.h"#include <format>using namespace nixpluginpijul;template<typename T>void assert_eq(const T &left, const T &right){if (left != right) {const std::string &string = std::format("assertion failed: {} == {}", left, right);fprintf(stderr, "%s\n", string.c_str());exit(1);}}template<typename F>void assert_throws(F op){try {op();} catch (...) {return;}fprintf(stderr, "%s\n", "assertion failed: `op' didn't throw");exit(1);}int main(){auto point = parseRFC3339("1970-01-01T00:00:00.000Z");assert_eq(0L, point.time_since_epoch().count());point = parseRFC3339("1970-01-01T00:00:00Z");assert_eq(0L, point.time_since_epoch().count());point = parseRFC3339("1970-01-01T00:30:00+00:30");assert_eq(0L, point.time_since_epoch().count());point = parseRFC3339("1970-01-01T00:00:00.000-01:00");assert_eq(3600000L, point.time_since_epoch().count());assert_throws([] {parseRFC3339("garbage");});assert_throws([] {parseRFC3339("1");});assert_throws([] {parseRFC3339("1970-01-01T00:00:00");});// FIXME: this should be an invalid timestamp (at least, I think so) because// it has both +00:30 and 'Z' but it parsesassert_throws([] {parseRFC3339("1970-01-01T00:30:00+00:30Z");});} - file addition: CMakeLists.txt[0.7869]
add_compile_options(-O1 -ggdb)add_executable(DateTest datetest.cpp)target_link_libraries(DateTest PUBLIC PijulTesting)add_test(NAME DateTest COMMAND DateTest)add_executable(RepoTest repotest.cpp)target_link_libraries(RepoTest PUBLIC PijulTesting)add_test(NAME RepoTest COMMAND RepoTest) - edit in CMakeLists.txt at line 15
include(CTest) - edit in CMakeLists.txt at line 41[2.66][10.677]
add_compile_options(-Wall -Werror) - edit in CMakeLists.txt at line 44[10.678][4.14335]
add_subdirectory(autotests)