QA5XRI3RVPCTT2JJIKIGMUF5JKMQGGWCAU4CN7676O72C5BVWY6QC MJEMOITMCQZLLLATSIHPKLLNMEKKWBJQD2IQT3TDVS5BT7I4YE2AC YCBIN3NH7DMIXWNWNQEXWJ7OVT67TG4KYO6PS2N4CIQB422345WQC UYCSZQO662T6UMRRD2QADFZG5QGVLNOZCT2EQI7TZXPPXUTIZWYAC G2QYHQFBQ6EXKM6G3LBU2KTD547RD4O3U6PQDX5ZF7YOPPT2OHOQC EXXP5DBS7AFZU4SH4D5F23BWJNWO7BTNKPREF24CQSFR5KR6EVBAC NXYGNBPOV6WE3NR7ZPRF5ILQJMB2WTDEPMBM7447UCTT4NCD4DBQC PDUL2QU3UKQLGMZLSY5NOVNZLVI7QQKU65PWVKAG3CCKP5R3YDMQC N2RTQC4PEXZEGZPWHYJHIXS6LOI3EFYDYATNG4HMCR6JGDI3NZ6QC DYNXZQZVGIOW26Y6VPP4TIBOJEMK6T6Y7HU5ZJYH4GIWFFRIJSIQC 3KEFKH5FYGFUIWMIW6NY3K4FYSLE7LEXR4ZSF6WN52PCC3TVDZBAC U5AKEHEQWHBOVXMTTQFBJA4M4VJ7OWSJJVYYB6FLTJB7NCFPYVRQC RPUKW357SJ6EXF5GBA4UOIKVM2Y5XVJXV3T3QVIGO3P3GBEAP3DAC HZA67MNO6Z6ODY3YFPZK5NRVSSUZ4RHX4MBXX6TXV5AJBBLQD7KQC YW5FM2MIJ2LBZFBLOZ53VMQZAR3PHI2PTUUXSXLQD6EIRB342OEAC 7YS2X7JJVAFT7QFDASQR4ZVUAAFYF4OLK5XLY3JJ2URSXI34ZOTAC GCOWWL5GGTIWIREP6JR2IHGXIJZS3IGAK5INPVYB7KVWNAZ46DSQC H5RKFV7YLV3QFY5HCQHGU7T232FPYMGWSBR3ABNGKMEAN5DS523AC 65LJ73XHQOBVUGLSMCBIK43EXNQCEYRWNK7C5NWL7BCKRAKNRM5AC GIEUXXMHNRLH62M2Z4IWRVG4IPPGAPYZ3CNFINTEOKWVQQD64BJAC BBPDFO4LTQQRVNJTKEUFIDVZI5CZHWDPOAX6GYKPJYKFNU7CKGJQC R7SYZNQFT6TZKG553NNRZEFQ5235YRW2GHLB2NLGAJ44QSJWOTMQC 2NVQXNBSRTMBZY64D34SGDZAGUTHWZAP6VLTCIT572QM4V7TOAPQC NQT3HEZHZUY2CXUGKFI6VE6K7CCJY26JPZBCAYB3JX7YDNZA3NJQC WUA27ICQYYPV7OJAEDYJHFTLTXO5JEZFXD733DLCW2ZTVGWHZRCQC SNHMHWHLXYN4KR5GMUXJXQ6T25AUQD4XWNXJRJMFSY2MQPAQZEDQC 6PXIKDCNXC23OYHMNMAC5U46N25HDO7THEAP7VTF77NA6XP777ZAC #if NIX_VERSION >= 0x022200#define NIX_HAS_LOOKUPPATH#endif#if NIX_VERSION >= 0x022300#define NIX_HAS_STORE_PATH_ACCESSOR_H#define NIX_INPUTSCHEME_USES_SOURCE_ACCESSOR#define NIX_HAS_CACHE_KEY_TYPE#define NIX_POSIX_SOURCE_ACCESSOR_V3#endif#if NIX_VERSION >= 0x022400#define NIX_INPUTFROMURL_HAS_SETTINGS_PARAM#define NIX_POSIX_SOURCE_ACCESSOR_V4#endif#endif#ifdef NIX_IS_LIX#define NIX_INPUTFROMURL_HAS_REQUIRETREE_PARAM#define NIX_HAS_PROCESSES_H#define NIX_GETSOURCEPATH_IS_CONST#define NIX_HAS_PUTFILE#endif#endif
#include <cache.hh>#include <fetch-settings.hh>#include <fetchers.hh>#include <store-api.hh>#ifdef NIX_HAS_POSIX_SOURCE_ACCESSOR_H#include <posix-source-accessor.hh>#endif#ifdef NIX_HAS_STORE_PATH_ACCESSOR_H#include <store-path-accessor.hh>#elif defined(NIX_HAS_FS_INPUT_ACCESSOR_H)#include <fs-input-accessor.hh>#endif
#include <nix/fetchers/cache.hh>#include <nix/fetchers/fetch-settings.hh>#include <nix/fetchers/fetchers.hh>#include <nix/fetchers/store-path-accessor.hh>#include <nix/store/store-api.hh>#include <nix/util/posix-source-accessor.hh>
#elif defined(NIX_INPUTFROMURL_HAS_REQUIRETREE_PARAM)[[nodiscard]] std::optional<Input> inputFromURL(const ParsedURL &url, bool requireTree) const override#else[[nodiscard]] std::optional<Input> inputFromURL(const ParsedURL &url) const override#endif
}#ifndef NIX_INPUTSCHEME_EXTERNAL_URI_CHECKSfor (const auto &[name, _] : attrs) {if (name != "type"sv && name != "url"sv && name != "channel"sv && name != "state"sv && name != "narHash"sv && name != "lastModified"sv) {throw Error("unsupported Pijul input attribute '%s'"s, name);}
#ifndef NIX_INPUTSCHEME_EXTERNAL_LOCKED_CHECKSif (maybeGetStrAttr(input.attrs, "channel") && maybeGetStrAttr(input.attrs, "state")) {input.locked = true;}#endif
#ifndef NIX_INPUTSCHEME_EXTERNAL_URI_CHECKS[[nodiscard]] bool hasAllInfo(const Input &input) const override{return maybeGetIntAttr(input.attrs, "lastModified").has_value();}#endif
#ifdef NIX_INPUTSCHEME_HAS_PRIVATE_FETCHstd::pair<StorePath, Input> fetchToStore(ref<Store> store, const Input &_input) const#elsestd::pair<StorePath, Input> fetch(ref<Store> store, const Input &_input) override#endif
std::optional<std::filesystem::path> getSourcePath(const Input &input) const override
if (auto localPath = getSourcePath(_input)) {return fetchLocal(store, _input, *localPath);} else {auto [storePath, infoAttrs] = doFetch(store, _input);Input input(_input);mergeAttrs(input.attrs, std::move(infoAttrs));return {std::move(storePath), input};}DBG_END}#ifdef NIX_GETSOURCEPATH_IS_CONSTstd::optional<Path> getSourcePath(const Input &input) const override#elsestd::optional<Path> getSourcePath(const Input &input) override#endif{DBG_BEGIN
#ifdef NIX_CANONPATH_HAS_SLASH_OPwriteFile((CanonPath(*root) / path).abs(), contents);#elsewriteFile((CanonPath(*root) + path).abs(), contents);#endif
auto rootStr = root->string();writeFile((CanonPath(rootStr) / path).abs(), contents);record(*commitMsg, rootStr, {Path(path.rel())});
}#elsevoid markChangedFile(const Input &input, std::string_view file, std::optional<std::string> commitMsg) override{auto root = getSourcePath(input);assert(root);record(*commitMsg, *root, {std::string(file)});
static std::pair<StorePath, Attrs> doFetch(const ref<Store> &_store, const Input &input)
std::pair<StorePath, Input> fetchToStoreImpl(ref<Store> store, const Input &_input) const{DBG_BEGINif (auto localPath = getSourcePath(_input)) {return fetchLocal(store, _input, *localPath);} else {auto [storePath, infoAttrs] = doFetchCached(store, _input);Input input(_input);mergeAttrs(input.attrs, std::move(infoAttrs));return {std::move(storePath), input};}DBG_END}static std::pair<StorePath, Attrs> doFetchCached(ref<Store> store, const Input &input)
#ifdef NIX_HAS_CACHE_KEY_TYPEkey = {"pijul", {{"name", name},{"channel", *channel},{"state", *state},}};
Attrs keyAttrs;keyAttrs.emplace("name"s, name);keyAttrs.emplace("channel"s, *channel);keyAttrs.emplace("state"s, *state);key = Cache::Key{"pijul"sv, std::move(keyAttrs)};
if (auto res = getCache()->lookupStorePath(*key, store)) {return {std::move(res->storePath), std::move(res->value)};
if (auto res = cache->lookupStorePath(*key, *store)) {return std::make_pair(std::move(res->storePath), std::move(res->value));
#elsekey = {{"type", "pijul"},{"name", name},{"channel", *channel},{"state", *state},};if (auto res = getCache()->lookup(store, *key)) {auto &[infoAttrs, storePath] = *res;return {std::move(storePath), std::move(infoAttrs)};}#endif
#ifdef NIX_HAS_CACHE_KEY_TYPEconst Cache::Key impureKey{"pijul", {{"name", name},{"url", repoUrl},}};
Attrs impureKeyAttrs;impureKeyAttrs.emplace("name"s, name);impureKeyAttrs.emplace("url"s, repoUrl);const Cache::Key impureKey{"pijul"sv, std::move(impureKeyAttrs)};
if ((!channel || *channel == getStrAttr(infoAttrs, "channel")) && (!state || *state == getStrAttr(infoAttrs, "state"))) {return {std::move(storePath), std::move(infoAttrs)};}}#endifauto [storePath, rs] = doFetch(_store, name, repoUrl, channel, state);
#ifdef NIX_HAS_CACHE_KEY_TYPEkey = {"pijul", {{"name", name},}};#elsekey = {{"type", "pijul"},{"name", name},};#endif
Attrs keyAttrs;keyAttrs.emplace("name"s, name);key = Cache::Key{"pijul"sv, std::move(keyAttrs)};
#ifdef NIX_HAS_CACHE_KEY_TYPEmergeAttrs(key->second,#elsemergeAttrs(*key,#endif{{"channel", rs.channel},{"state", rs.state},});
Attrs toMerge;toMerge.emplace("channel"s, rs.channel);toMerge.emplace("state"s, rs.state);mergeAttrs(key->second, std::move(toMerge));
Attrs infoAttrs = {{"channel", std::move(rs.channel)},{"state", std::move(rs.state)},{"lastModified", rs.lastModified},};
Attrs infoAttrs;infoAttrs.emplace("channel"s, std::move(rs.channel));infoAttrs.emplace("state"s, std::move(rs.state));infoAttrs.emplace("lastModified"s, rs.lastModified);
if (!isLocked) {#ifdef NIX_HAS_CACHE_KEY_TYPEgetCache()->upsert(impureKey, store, infoAttrs, storePath);#elsegetCache()->add(store, impureKey, infoAttrs, storePath, false);#endif
if (!isLockedInput) {cache->upsert(impureKey, *store, infoAttrs, storePath);
#ifdef NIX_HAS_CACHE_KEY_TYPEgetCache()->upsert(*key, store, infoAttrs, storePath);#elsegetCache()->add(store, *key, infoAttrs, storePath, true);#endif
cache->upsert(*key, *store, infoAttrs, storePath);
static std::pair<StorePath, RepoStatus> doFetch(const ref<Store> &store,const std::string_view &inputName,const std::string_view &repoUrl,const std::optional<std::string_view> &channel,const std::optional<std::string_view> &state)
static std::pair<StorePath, RepoStatus> doFetchClone(ref<Store> store,const std::string_view &inputName,const std::string_view &repoUrl,const std::optional<std::string_view> &channel,const std::optional<std::string_view> &state)
#elif defined(NIX_POSIX_SOURCE_ACCESSOR_V2)auto [accessor, canonPath] = PosixSourceAccessor::createAtRoot(repoDir);auto storePath = store->addToStore(inputName, accessor, canonPath);#elif defined(NIX_POSIX_SOURCE_ACCESSOR_V1)PosixSourceAccessor accessor;auto storePath = store->addToStore(inputName, accessor, CanonPath::fromCwd(repoDir));#elseauto storePath = store->addToStore(inputName, repoDir);#endif
#ifdef NIX_INPUTFROMURL_HAS_SETTINGS_PARAMauto settings = *input.settings;#elseauto settings = fetchSettings;#endifif (!settings.allowDirty) {throw Error("Pijul tree '%s' is dirty", path);
if (!_input.settings->allowDirty) {throw Error("Pijul tree '%s' is dirty", localPath.string());
#if defined(NIX_POSIX_SOURCE_ACCESSOR_V4)auto storePath = store->addToStore(input.getName(), {getFSSourceAccessor(), CanonPath(actualPath)}, ContentAddressMethod::Raw::NixArchive, HashAlgorithm::SHA256, {}, filter);#elif defined(NIX_POSIX_SOURCE_ACCESSOR_V3)auto storePath = store->addToStore(input.getName(), {getFSSourceAccessor(), CanonPath(actualPath)}, FileIngestionMethod::Recursive, HashAlgorithm::SHA256, {}, filter);#elif defined(NIX_POSIX_SOURCE_ACCESSOR_V1)PosixSourceAccessor accessor;auto storePath = store->addToStore(input.getName(), accessor, CanonPath{actualPath}, FileIngestionMethod::Recursive, HashAlgorithm::SHA256, {}, filter);#else#ifdef NIX_IS_LIXconst auto htSHA256 = HashType::SHA256;#endif
auto sourcePath = PosixSourceAccessor::createAtRoot(actualPath);auto storePath = store->addToStore(input.getName(), sourcePath, ContentAddressMethod::Raw::NixArchive, HashAlgorithm::SHA256, {}, filter);
checks = eachSystem (pkgs: letlocalRepoCheck = nix:pkgs.runCommand "localRepoCheck-${nix.name}"{nativeBuildInputs = [pkgs.pijulnix];}''export HOME=$(mktemp -d)export EDITOR=truepijul identity new --no-link --no-prompt --display-name 'Test User' --email 'test@example.com'pijul init repocd repoecho "it works" > foopijul add foopijul record --message 'Add foo'
output=$(nix \--option plugin-files ${self.packages.${pkgs.system}.nix-plugin-pijul.override {inherit nix;}}/lib/nix/plugins/pijul.so \--extra-experimental-features 'nix-command flakes' \eval --impure --raw --expr "builtins.readFile ((builtins.fetchTree \"pijul+file://$PWD\") + \"/foo\")")echo $output[[ "$output" = "it works" ]]mkdir $out'';in {nix_2_18 = localRepoCheck pkgs.nixVersions.nix_2_18;nix_2_19 = localRepoCheck pkgs.nixVersions.nix_2_19;nix_2_20 = localRepoCheck pkgs.nixVersions.nix_2_20;nix_2_21 = localRepoCheck pkgs.nixVersions.nix_2_21;nix_2_22 = localRepoCheck pkgs.nixVersions.nix_2_22;nix_2_23 = localRepoCheck pkgs.nixVersions.nix_2_23;nix_2_24 = localRepoCheck pkgs.nixVersions.nix_2_24;stable = localRepoCheck pkgs.nixVersions.stable;latest = localRepoCheck pkgs.nixVersions.latest;});
"lastModified": 1729658218,"narHash": "sha256-9Rg+AqLqvqqJniP/OQB3GtgXoAd8IlazsHp97va042Y=","owner": "NixOS",
"lastModified": 1771008912,"narHash": "sha256-gf2AmWVTs8lEq7z/3ZAsgnZDhWIckkb+ZnAo5RzSxJg=","owner": "nixos",
NB: The fetcher from this plugin does not automatically get used when runningnix commands (e.g. `nix build`) in a Pijul repository, since this is hardcodedfor Git and Mercurial in Nix. For this, I have a patch that adds the relevantcode for Pijul, it's available as[nix-pijul](https://git.sr.ht/~dblsaiko/nix-extras/tree/master/item/packages/nix-pijul.nix)in my *nix-extras* flake. It replaces the normal Nix installation and can beused in addition to this plugin.## ReleasesReleased version archives are available [here](https://dblsaiko.net/pub/nix-plugin-pijul).
**Requires Nix 2.25+.** The plugin executes pijul to actually fetchrepositories, make sure that is available in the PATH.
nix.settings.plugin-files = "${pkgs.nix-plugin-pijul.override { nix = config.nix.package; }}/lib/nix/plugins/pijul.so";# Optional: Use the patched Nix to automatically use Pijul fetcher in local# repositoriesnix.package = pkgs.nix-pijul;
nix.settings.plugin-files = "${nur.nix-plugin-pijul}/lib/nix/plugins/pijul.so";