Skip to content

Commit c45dfee

Browse files
authored
Merge pull request #12220 from DeterminateSystems/allow-dirty-locks
Add setting 'allow-dirty-locks'
2 parents 9c239d4 + e161393 commit c45dfee

File tree

12 files changed

+67
-17
lines changed

12 files changed

+67
-17
lines changed

src/libcmd/installables.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,7 @@ ref<eval_cache::EvalCache> openEvalCache(
450450
std::shared_ptr<flake::LockedFlake> lockedFlake)
451451
{
452452
auto fingerprint = evalSettings.useEvalCache && evalSettings.pureEval
453-
? lockedFlake->getFingerprint(state.store)
453+
? lockedFlake->getFingerprint(state.store, state.fetchSettings)
454454
: std::nullopt;
455455
auto rootLoader = [&state, lockedFlake]()
456456
{

src/libexpr/primops/fetchTree.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ static void fetchTree(
182182
if (!state.settings.pureEval && !input.isDirect() && experimentalFeatureSettings.isEnabled(Xp::Flakes))
183183
input = lookupInRegistries(state.store, input).first;
184184

185-
if (state.settings.pureEval && !input.isLocked()) {
185+
if (state.settings.pureEval && !input.isConsideredLocked(state.fetchSettings)) {
186186
auto fetcher = "fetchTree";
187187
if (params.isFetchGit)
188188
fetcher = "fetchGit";

src/libfetchers/fetch-settings.hh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,22 @@ struct Settings : public Config
7070
Setting<bool> warnDirty{this, true, "warn-dirty",
7171
"Whether to warn about dirty Git/Mercurial trees."};
7272

73+
Setting<bool> allowDirtyLocks{
74+
this,
75+
false,
76+
"allow-dirty-locks",
77+
R"(
78+
Whether to allow dirty inputs (such as dirty Git workdirs)
79+
to be locked via their NAR hash. This is generally bad
80+
practice since Nix has no way to obtain such inputs if they
81+
are subsequently modified. Therefore lock files with dirty
82+
locks should generally only be used for local testing, and
83+
should not be pushed to other users.
84+
)",
85+
{},
86+
true,
87+
Xp::Flakes};
88+
7389
Setting<bool> trustTarballsFromGitForges{
7490
this, true, "trust-tarballs-from-git-forges",
7591
R"(

src/libfetchers/fetchers.cc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "fetch-to-store.hh"
55
#include "json-utils.hh"
66
#include "store-path-accessor.hh"
7+
#include "fetch-settings.hh"
78

89
#include <nlohmann/json.hpp>
910

@@ -154,6 +155,12 @@ bool Input::isLocked() const
154155
return scheme && scheme->isLocked(*this);
155156
}
156157

158+
bool Input::isConsideredLocked(
159+
const Settings & settings) const
160+
{
161+
return isLocked() || (settings.allowDirtyLocks && getNarHash());
162+
}
163+
157164
bool Input::isFinal() const
158165
{
159166
return maybeGetBoolAttr(attrs, "__final").value_or(false);

src/libfetchers/fetchers.hh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,15 @@ public:
9595
*/
9696
bool isLocked() const;
9797

98+
/**
99+
* Return whether the input is either locked, or, if
100+
* `allow-dirty-locks` is enabled, it has a NAR hash. In the
101+
* latter case, we can verify the input but we may not be able to
102+
* fetch it from anywhere.
103+
*/
104+
bool isConsideredLocked(
105+
const Settings & settings) const;
106+
98107
/**
99108
* Return whether this is a "final" input, meaning that fetching
100109
* it will not add, remove or change any attributes. (See

src/libflake/flake/flake.cc

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool allowLookup
345345
}
346346

347347
static LockFile readLockFile(
348+
const Settings & settings,
348349
const fetchers::Settings & fetchSettings,
349350
const SourcePath & lockFilePath)
350351
{
@@ -380,6 +381,7 @@ LockedFlake lockFlake(
380381
}
381382

382383
auto oldLockFile = readLockFile(
384+
settings,
383385
state.fetchSettings,
384386
lockFlags.referenceLockFilePath.value_or(
385387
flake.lockFilePath()));
@@ -616,7 +618,7 @@ LockedFlake lockFlake(
616618
inputFlake.inputs, childNode, inputPath,
617619
oldLock
618620
? std::dynamic_pointer_cast<const Node>(oldLock)
619-
: readLockFile(state.fetchSettings, inputFlake.lockFilePath()).root.get_ptr(),
621+
: readLockFile(settings, state.fetchSettings, inputFlake.lockFilePath()).root.get_ptr(),
620622
oldLock ? lockRootPath : inputPath,
621623
localPath,
622624
false);
@@ -678,9 +680,11 @@ LockedFlake lockFlake(
678680

679681
if (lockFlags.writeLockFile) {
680682
if (sourcePath || lockFlags.outputLockFilePath) {
681-
if (auto unlockedInput = newLockFile.isUnlocked()) {
683+
if (auto unlockedInput = newLockFile.isUnlocked(state.fetchSettings)) {
682684
if (lockFlags.failOnUnlocked)
683-
throw Error("cannot write lock file of flake '%s' because it has an unlocked input ('%s').\n", topRef, *unlockedInput);
685+
throw Error(
686+
"Will not write lock file of flake '%s' because it has an unlocked input ('%s'). "
687+
"Use '--allow-dirty-locks' to allow this anyway.", topRef, *unlockedInput);
684688
if (state.fetchSettings.warnDirty)
685689
warn("will not write lock file of flake '%s' because it has an unlocked input ('%s')", topRef, *unlockedInput);
686690
} else {
@@ -979,9 +983,11 @@ static RegisterPrimOp r4({
979983

980984
}
981985

982-
std::optional<Fingerprint> LockedFlake::getFingerprint(ref<Store> store) const
986+
std::optional<Fingerprint> LockedFlake::getFingerprint(
987+
ref<Store> store,
988+
const fetchers::Settings & fetchSettings) const
983989
{
984-
if (lockFile.isUnlocked()) return std::nullopt;
990+
if (lockFile.isUnlocked(fetchSettings)) return std::nullopt;
985991

986992
auto fingerprint = flake.lockedRef.input.getFingerprint(store);
987993
if (!fingerprint) return std::nullopt;

src/libflake/flake/flake.hh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,9 @@ struct LockedFlake
129129
*/
130130
std::map<ref<Node>, SourcePath> nodePaths;
131131

132-
std::optional<Fingerprint> getFingerprint(ref<Store> store) const;
132+
std::optional<Fingerprint> getFingerprint(
133+
ref<Store> store,
134+
const fetchers::Settings & fetchSettings) const;
133135
};
134136

135137
struct LockFlags

src/libflake/flake/lockfile.cc

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <nlohmann/json.hpp>
1111

1212
#include "strings.hh"
13+
#include "flake/settings.hh"
1314

1415
namespace nix::flake {
1516

@@ -43,8 +44,8 @@ LockedNode::LockedNode(
4344
, originalRef(getFlakeRef(fetchSettings, json, "original", nullptr))
4445
, isFlake(json.find("flake") != json.end() ? (bool) json["flake"] : true)
4546
{
46-
if (!lockedRef.input.isLocked())
47-
throw Error("lock file contains unlocked input '%s'",
47+
if (!lockedRef.input.isConsideredLocked(fetchSettings))
48+
throw Error("Lock file contains unlocked input '%s'. Use '--allow-dirty-locks' to accept this lock file.",
4849
fetchers::attrsToJSON(lockedRef.input.toAttrs()));
4950

5051
// For backward compatibility, lock file entries are implicitly final.
@@ -228,7 +229,7 @@ std::ostream & operator <<(std::ostream & stream, const LockFile & lockFile)
228229
return stream;
229230
}
230231

231-
std::optional<FlakeRef> LockFile::isUnlocked() const
232+
std::optional<FlakeRef> LockFile::isUnlocked(const fetchers::Settings & fetchSettings) const
232233
{
233234
std::set<ref<const Node>> nodes;
234235

@@ -247,7 +248,7 @@ std::optional<FlakeRef> LockFile::isUnlocked() const
247248
for (auto & i : nodes) {
248249
if (i == ref<const Node>(root)) continue;
249250
auto node = i.dynamic_pointer_cast<const LockedNode>();
250-
if (node && (!node->lockedRef.input.isLocked() || !node->lockedRef.input.isFinal()))
251+
if (node && (!node->lockedRef.input.isConsideredLocked(fetchSettings) || !node->lockedRef.input.isFinal()))
251252
return node->lockedRef;
252253
}
253254

src/libflake/flake/lockfile.hh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ struct LockFile
7171
* Check whether this lock file has any unlocked or non-final
7272
* inputs. If so, return one.
7373
*/
74-
std::optional<FlakeRef> isUnlocked() const;
74+
std::optional<FlakeRef> isUnlocked(const fetchers::Settings & fetchSettings) const;
7575

7676
bool operator ==(const LockFile & other) const;
7777

src/libflake/flake/settings.hh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ struct Settings : public Config
2929
this,
3030
false,
3131
"accept-flake-config",
32-
"Whether to accept nix configuration from a flake without prompting.",
32+
"Whether to accept Nix configuration settings from a flake without prompting.",
3333
{},
3434
true,
3535
Xp::Flakes};

0 commit comments

Comments
 (0)