#include "repo.h"
#include <nlohmann/json.hpp>
#include <utility>
using nlohmann::json;
using namespace std::string_literals;
using namespace std::string_view_literals;
namespace nixpluginpijul
{
void runPijul(const StringList &args, const std::optional<Path> &chdir, OutputMode mode = OutputMode::Standard)
{
runProcess("pijul", args, chdir ? chdir->c_str() : nullptr, mode);
}
void record(const std::string_view &message, const Path &chdir, const std::vector<Path> &paths)
{
StringList args{"record", "--message"};
args.emplace_back(message);
args.emplace_back("--");
for (auto &s : paths) {
args.emplace_back(s);
}
runPijul(args, chdir, OutputMode::Interactive);
}
void clone(const std::string_view &repoUrl,
const Path &repoDir,
const std::optional<std::string_view> &channel,
const std::optional<std::string_view> &state)
{
StringList args{"clone"s};
if (channel) {
args.push_back("--channel"s);
args.emplace_back(*channel);
}
if (state) {
args.push_back("--state"s);
args.emplace_back(*state);
}
args.emplace_back(repoUrl);
args.emplace_back(repoDir);
runPijul(args, {}, OutputMode::Interactive);
}
bool isRepoDirty(const Path &chdir = {})
{
std::string diffOutput;
runPijul({"diff", "--json"}, chdir, OutputMode::WithOutput{diffOutput});
bool dirty = false;
if (!diffOutput.empty()) {
const auto &json = nlohmann::json::parse(diffOutput);
if (!json.empty()) {
dirty = true;
}
}
return dirty;
}
std::set<Path> getTrackedFiles(const Path &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;
}
return out;
}
RepoStatus getRepoStatus(const Path &repoPath)
{
auto [state, lastModified] = getState(repoPath);
auto channel = getRepoChannel(repoPath);
return RepoStatus{
.channel = std::move(channel),
.state = std::move(state),
.lastModified = lastModified,
};
}
std::pair<std::string, uint64_t> getState(const Path &repoPath)
{
std::string output;
runPijul({"log", "--output-format", "json", "--state", "--limit", "1"}, Path(repoPath), OutputMode::WithOutput{output});
const auto &json = json::parse(output);
const auto &commitInfo = json.at(0);
const auto ×tampSpec = commitInfo.at("timestamp").get<std::string>();
const uint64_t timestamp = std::chrono::duration_cast<std::chrono::seconds>(parseRFC3339(timestampSpec).time_since_epoch()).count();
const std::string &state = commitInfo.at("state");
return {state, timestamp};
}
std::string getRepoChannel(const Path &repoPath)
{
std::string output;
runPijul({"channel"}, Path(repoPath), OutputMode::WithOutput{output});
std::string::size_type pos = 0;
do {
const auto nl = output.find('\n', pos);
const auto line = std::string_view(output).substr(pos, nl - pos);
if (line.empty()) {
continue;
}
if (line.at(0) == '*') {
return std::string(line.substr(2));
}
pos = nl;
} while (pos != std::string::npos);
fatal("could not parse current channel");
}
date::sys_time<std::chrono::milliseconds> parseRFC3339(const std::string &spec)
{
std::istringstream in{spec};
date::sys_time<std::chrono::milliseconds> pt;
in >> date::parse("%FT%TZ", pt);
if (in.fail()) {
in.clear();
in.exceptions(std::ios::failbit);
in.str(spec);
in >> date::parse("%FT%T%Ez", pt);
}
return pt;
}
}