mirror of
https://github.com/NixOS/nix
synced 2025-06-25 02:21:16 +02:00
Merge pull request #12220 from DeterminateSystems/allow-dirty-locks
Add setting 'allow-dirty-locks'
This commit is contained in:
commit
c45dfeeef3
12 changed files with 67 additions and 17 deletions
|
@ -450,7 +450,7 @@ ref<eval_cache::EvalCache> openEvalCache(
|
|||
std::shared_ptr<flake::LockedFlake> lockedFlake)
|
||||
{
|
||||
auto fingerprint = evalSettings.useEvalCache && evalSettings.pureEval
|
||||
? lockedFlake->getFingerprint(state.store)
|
||||
? lockedFlake->getFingerprint(state.store, state.fetchSettings)
|
||||
: std::nullopt;
|
||||
auto rootLoader = [&state, lockedFlake]()
|
||||
{
|
||||
|
|
|
@ -182,7 +182,7 @@ static void fetchTree(
|
|||
if (!state.settings.pureEval && !input.isDirect() && experimentalFeatureSettings.isEnabled(Xp::Flakes))
|
||||
input = lookupInRegistries(state.store, input).first;
|
||||
|
||||
if (state.settings.pureEval && !input.isLocked()) {
|
||||
if (state.settings.pureEval && !input.isConsideredLocked(state.fetchSettings)) {
|
||||
auto fetcher = "fetchTree";
|
||||
if (params.isFetchGit)
|
||||
fetcher = "fetchGit";
|
||||
|
|
|
@ -70,6 +70,22 @@ struct Settings : public Config
|
|||
Setting<bool> warnDirty{this, true, "warn-dirty",
|
||||
"Whether to warn about dirty Git/Mercurial trees."};
|
||||
|
||||
Setting<bool> allowDirtyLocks{
|
||||
this,
|
||||
false,
|
||||
"allow-dirty-locks",
|
||||
R"(
|
||||
Whether to allow dirty inputs (such as dirty Git workdirs)
|
||||
to be locked via their NAR hash. This is generally bad
|
||||
practice since Nix has no way to obtain such inputs if they
|
||||
are subsequently modified. Therefore lock files with dirty
|
||||
locks should generally only be used for local testing, and
|
||||
should not be pushed to other users.
|
||||
)",
|
||||
{},
|
||||
true,
|
||||
Xp::Flakes};
|
||||
|
||||
Setting<bool> trustTarballsFromGitForges{
|
||||
this, true, "trust-tarballs-from-git-forges",
|
||||
R"(
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include "fetch-to-store.hh"
|
||||
#include "json-utils.hh"
|
||||
#include "store-path-accessor.hh"
|
||||
#include "fetch-settings.hh"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
|
@ -154,6 +155,12 @@ bool Input::isLocked() const
|
|||
return scheme && scheme->isLocked(*this);
|
||||
}
|
||||
|
||||
bool Input::isConsideredLocked(
|
||||
const Settings & settings) const
|
||||
{
|
||||
return isLocked() || (settings.allowDirtyLocks && getNarHash());
|
||||
}
|
||||
|
||||
bool Input::isFinal() const
|
||||
{
|
||||
return maybeGetBoolAttr(attrs, "__final").value_or(false);
|
||||
|
|
|
@ -95,6 +95,15 @@ public:
|
|||
*/
|
||||
bool isLocked() const;
|
||||
|
||||
/**
|
||||
* Return whether the input is either locked, or, if
|
||||
* `allow-dirty-locks` is enabled, it has a NAR hash. In the
|
||||
* latter case, we can verify the input but we may not be able to
|
||||
* fetch it from anywhere.
|
||||
*/
|
||||
bool isConsideredLocked(
|
||||
const Settings & settings) const;
|
||||
|
||||
/**
|
||||
* Return whether this is a "final" input, meaning that fetching
|
||||
* it will not add, remove or change any attributes. (See
|
||||
|
|
|
@ -345,6 +345,7 @@ Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool allowLookup
|
|||
}
|
||||
|
||||
static LockFile readLockFile(
|
||||
const Settings & settings,
|
||||
const fetchers::Settings & fetchSettings,
|
||||
const SourcePath & lockFilePath)
|
||||
{
|
||||
|
@ -380,6 +381,7 @@ LockedFlake lockFlake(
|
|||
}
|
||||
|
||||
auto oldLockFile = readLockFile(
|
||||
settings,
|
||||
state.fetchSettings,
|
||||
lockFlags.referenceLockFilePath.value_or(
|
||||
flake.lockFilePath()));
|
||||
|
@ -616,7 +618,7 @@ LockedFlake lockFlake(
|
|||
inputFlake.inputs, childNode, inputPath,
|
||||
oldLock
|
||||
? std::dynamic_pointer_cast<const Node>(oldLock)
|
||||
: readLockFile(state.fetchSettings, inputFlake.lockFilePath()).root.get_ptr(),
|
||||
: readLockFile(settings, state.fetchSettings, inputFlake.lockFilePath()).root.get_ptr(),
|
||||
oldLock ? lockRootPath : inputPath,
|
||||
localPath,
|
||||
false);
|
||||
|
@ -678,9 +680,11 @@ LockedFlake lockFlake(
|
|||
|
||||
if (lockFlags.writeLockFile) {
|
||||
if (sourcePath || lockFlags.outputLockFilePath) {
|
||||
if (auto unlockedInput = newLockFile.isUnlocked()) {
|
||||
if (auto unlockedInput = newLockFile.isUnlocked(state.fetchSettings)) {
|
||||
if (lockFlags.failOnUnlocked)
|
||||
throw Error("cannot write lock file of flake '%s' because it has an unlocked input ('%s').\n", topRef, *unlockedInput);
|
||||
throw Error(
|
||||
"Will not write lock file of flake '%s' because it has an unlocked input ('%s'). "
|
||||
"Use '--allow-dirty-locks' to allow this anyway.", topRef, *unlockedInput);
|
||||
if (state.fetchSettings.warnDirty)
|
||||
warn("will not write lock file of flake '%s' because it has an unlocked input ('%s')", topRef, *unlockedInput);
|
||||
} else {
|
||||
|
@ -979,9 +983,11 @@ static RegisterPrimOp r4({
|
|||
|
||||
}
|
||||
|
||||
std::optional<Fingerprint> LockedFlake::getFingerprint(ref<Store> store) const
|
||||
std::optional<Fingerprint> LockedFlake::getFingerprint(
|
||||
ref<Store> store,
|
||||
const fetchers::Settings & fetchSettings) const
|
||||
{
|
||||
if (lockFile.isUnlocked()) return std::nullopt;
|
||||
if (lockFile.isUnlocked(fetchSettings)) return std::nullopt;
|
||||
|
||||
auto fingerprint = flake.lockedRef.input.getFingerprint(store);
|
||||
if (!fingerprint) return std::nullopt;
|
||||
|
|
|
@ -129,7 +129,9 @@ struct LockedFlake
|
|||
*/
|
||||
std::map<ref<Node>, SourcePath> nodePaths;
|
||||
|
||||
std::optional<Fingerprint> getFingerprint(ref<Store> store) const;
|
||||
std::optional<Fingerprint> getFingerprint(
|
||||
ref<Store> store,
|
||||
const fetchers::Settings & fetchSettings) const;
|
||||
};
|
||||
|
||||
struct LockFlags
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "strings.hh"
|
||||
#include "flake/settings.hh"
|
||||
|
||||
namespace nix::flake {
|
||||
|
||||
|
@ -43,8 +44,8 @@ LockedNode::LockedNode(
|
|||
, originalRef(getFlakeRef(fetchSettings, json, "original", nullptr))
|
||||
, isFlake(json.find("flake") != json.end() ? (bool) json["flake"] : true)
|
||||
{
|
||||
if (!lockedRef.input.isLocked())
|
||||
throw Error("lock file contains unlocked input '%s'",
|
||||
if (!lockedRef.input.isConsideredLocked(fetchSettings))
|
||||
throw Error("Lock file contains unlocked input '%s'. Use '--allow-dirty-locks' to accept this lock file.",
|
||||
fetchers::attrsToJSON(lockedRef.input.toAttrs()));
|
||||
|
||||
// For backward compatibility, lock file entries are implicitly final.
|
||||
|
@ -228,7 +229,7 @@ std::ostream & operator <<(std::ostream & stream, const LockFile & lockFile)
|
|||
return stream;
|
||||
}
|
||||
|
||||
std::optional<FlakeRef> LockFile::isUnlocked() const
|
||||
std::optional<FlakeRef> LockFile::isUnlocked(const fetchers::Settings & fetchSettings) const
|
||||
{
|
||||
std::set<ref<const Node>> nodes;
|
||||
|
||||
|
@ -247,7 +248,7 @@ std::optional<FlakeRef> LockFile::isUnlocked() const
|
|||
for (auto & i : nodes) {
|
||||
if (i == ref<const Node>(root)) continue;
|
||||
auto node = i.dynamic_pointer_cast<const LockedNode>();
|
||||
if (node && (!node->lockedRef.input.isLocked() || !node->lockedRef.input.isFinal()))
|
||||
if (node && (!node->lockedRef.input.isConsideredLocked(fetchSettings) || !node->lockedRef.input.isFinal()))
|
||||
return node->lockedRef;
|
||||
}
|
||||
|
||||
|
|
|
@ -71,7 +71,7 @@ struct LockFile
|
|||
* Check whether this lock file has any unlocked or non-final
|
||||
* inputs. If so, return one.
|
||||
*/
|
||||
std::optional<FlakeRef> isUnlocked() const;
|
||||
std::optional<FlakeRef> isUnlocked(const fetchers::Settings & fetchSettings) const;
|
||||
|
||||
bool operator ==(const LockFile & other) const;
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ struct Settings : public Config
|
|||
this,
|
||||
false,
|
||||
"accept-flake-config",
|
||||
"Whether to accept nix configuration from a flake without prompting.",
|
||||
"Whether to accept Nix configuration settings from a flake without prompting.",
|
||||
{},
|
||||
true,
|
||||
Xp::Flakes};
|
||||
|
|
|
@ -238,7 +238,7 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON
|
|||
j["lastModified"] = *lastModified;
|
||||
j["path"] = storePath;
|
||||
j["locks"] = lockedFlake.lockFile.toJSON().first;
|
||||
if (auto fingerprint = lockedFlake.getFingerprint(store))
|
||||
if (auto fingerprint = lockedFlake.getFingerprint(store, fetchSettings))
|
||||
j["fingerprint"] = fingerprint->to_string(HashFormat::Base16, false);
|
||||
logger->cout("%s", j.dump());
|
||||
} else {
|
||||
|
@ -272,7 +272,7 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON
|
|||
logger->cout(
|
||||
ANSI_BOLD "Last modified:" ANSI_NORMAL " %s",
|
||||
std::put_time(std::localtime(&*lastModified), "%F %T"));
|
||||
if (auto fingerprint = lockedFlake.getFingerprint(store))
|
||||
if (auto fingerprint = lockedFlake.getFingerprint(store, fetchSettings))
|
||||
logger->cout(
|
||||
ANSI_BOLD "Fingerprint:" ANSI_NORMAL " %s",
|
||||
fingerprint->to_string(HashFormat::Base16, false));
|
||||
|
|
|
@ -31,5 +31,14 @@ echo 456 > "$flake1Dir"/x.nix
|
|||
|
||||
[[ $(nix eval --json "$flake2Dir#x" --override-input flake1 "$TEST_ROOT/flake1") = 456 ]]
|
||||
|
||||
# Dirty overrides require --allow-dirty-locks.
|
||||
expectStderr 1 nix flake lock "$flake2Dir" --override-input flake1 "$TEST_ROOT/flake1" |
|
||||
grepQuiet "cannot write lock file.*because it has an unlocked input"
|
||||
grepQuiet "Will not write lock file.*because it has an unlocked input"
|
||||
|
||||
nix flake lock "$flake2Dir" --override-input flake1 "$TEST_ROOT/flake1" --allow-dirty-locks
|
||||
|
||||
# Using a lock file with a dirty lock requires --allow-dirty-locks as well.
|
||||
expectStderr 1 nix eval "$flake2Dir#x" |
|
||||
grepQuiet "Lock file contains unlocked input"
|
||||
|
||||
[[ $(nix eval "$flake2Dir#x" --allow-dirty-locks) = 456 ]]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue