Add abstract interface for writing files to an Input

This commit is contained in:
Eelco Dolstra 2022-07-27 14:26:15 +02:00
parent bb4d35dcca
commit f780539406
8 changed files with 102 additions and 84 deletions

View file

@ -621,65 +621,53 @@ LockedFlake lockFlake(
auto diff = LockFile::diff(oldLockFile, newLockFile); auto diff = LockFile::diff(oldLockFile, newLockFile);
if (lockFlags.writeLockFile) { if (lockFlags.writeLockFile) {
if (auto sourcePath = topRef.input.getAccessor(state.store).first->root().getPhysicalPath()) { if (auto unlockedInput = newLockFile.isUnlocked()) {
if (auto unlockedInput = newLockFile.isUnlocked()) { if (fetchSettings.warnDirty)
if (fetchSettings.warnDirty) warn("will not write lock file of flake '%s' because it has an unlocked input ('%s')", topRef, *unlockedInput);
warn("will not write lock file of flake '%s' because it has an unlocked input ('%s')", topRef, *unlockedInput); } else {
} else { if (!lockFlags.updateLockFile)
if (!lockFlags.updateLockFile) throw Error("flake '%s' requires lock file changes but they're not allowed due to '--no-update-lock-file'", topRef);
throw Error("flake '%s' requires lock file changes but they're not allowed due to '--no-update-lock-file'", topRef);
CanonPath flakeDir(*sourcePath); auto path = flake->path.parent() + "flake.lock";
auto relPath = flakeDir + "flake.lock"; bool lockFileExists = path.pathExists();
auto path = flakeDir + "flake.lock"; if (lockFileExists) {
auto s = chomp(diff);
if (s.empty())
warn("updating lock file '%s'", path);
else
warn("updating lock file '%s':\n%s", path, s);
} else
warn("creating lock file '%s'", path);
bool lockFileExists = pathExists(path.abs()); std::optional<std::string> commitMessage = std::nullopt;
if (lockFlags.commitLockFile) {
std::string cm;
if (lockFileExists) { cm = fetchSettings.commitLockFileSummary.get();
auto s = chomp(diff);
if (s.empty())
warn("updating lock file '%s'", path);
else
warn("updating lock file '%s':\n%s", path, s);
} else
warn("creating lock file '%s'", path);
newLockFile.write(path.abs()); if (cm == "")
cm = fmt("%s: %s", path.path.rel(), lockFileExists ? "Update" : "Add");
std::optional<std::string> commitMessage = std::nullopt; cm += "\n\nFlake lock file updates:\n\n";
if (lockFlags.commitLockFile) { cm += filterANSIEscapes(diff, true);
std::string cm; commitMessage = cm;
cm = fetchSettings.commitLockFileSummary.get();
if (cm == "") {
cm = fmt("%s: %s", relPath, lockFileExists ? "Update" : "Add");
}
cm += "\n\nFlake lock file updates:\n\n";
cm += filterANSIEscapes(diff, true);
commitMessage = cm;
}
topRef.input.markChangedFile(
(topRef.subdir == "" ? "" : topRef.subdir + "/") + "flake.lock",
commitMessage);
/* Rewriting the lockfile changed the top-level
repo, so we should re-read it. FIXME: we could
also just clear the 'rev' field... */
auto prevLockedRef = flake->lockedRef;
flake = std::make_unique<Flake>(getFlake(state, topRef, useRegistries));
if (lockFlags.commitLockFile &&
flake->lockedRef.input.getRev() &&
prevLockedRef.input.getRev() != flake->lockedRef.input.getRev())
warn("committed new revision '%s'", flake->lockedRef.input.getRev()->gitRev());
} }
} else
throw Error("cannot write modified lock file of flake '%s' (use '--no-write-lock-file' to ignore)", topRef); topRef.input.putFile(path.path, fmt("%s\n", newLockFile), commitMessage);
/* Rewriting the lockfile changed the top-level
repo, so we should re-read it. FIXME: we could
also just clear the 'rev' field... */
auto prevLockedRef = flake->lockedRef;
flake = std::make_unique<Flake>(getFlake(state, topRef, useRegistries));
if (lockFlags.commitLockFile &&
flake->lockedRef.input.getRev() &&
prevLockedRef.input.getRev() != flake->lockedRef.input.getRev())
warn("committed new revision '%s'", flake->lockedRef.input.getRev()->gitRev());
}
} else { } else {
warn("not writing modified lock file of flake '%s':\n%s", topRef, chomp(diff)); warn("not writing modified lock file of flake '%s':\n%s", topRef, chomp(diff));
flake->forceDirty = true; flake->forceDirty = true;

View file

@ -190,12 +190,6 @@ std::ostream & operator <<(std::ostream & stream, const LockFile & lockFile)
return stream; return stream;
} }
void LockFile::write(const Path & path) const
{
createDirs(dirOf(path));
writeFile(path, fmt("%s\n", *this));
}
std::optional<FlakeRef> LockFile::isUnlocked() const std::optional<FlakeRef> LockFile::isUnlocked() const
{ {
std::unordered_set<std::shared_ptr<const Node>> nodes; std::unordered_set<std::shared_ptr<const Node>> nodes;

View file

@ -59,10 +59,6 @@ struct LockFile
std::string to_string() const; std::string to_string() const;
static LockFile read(const Path & path);
void write(const Path & path) const;
/* Check whether this lock file has any unlocked inputs. If so, /* Check whether this lock file has any unlocked inputs. If so,
return one. */ return one. */
std::optional<FlakeRef> isUnlocked() const; std::optional<FlakeRef> isUnlocked() const;

View file

@ -187,12 +187,13 @@ void Input::clone(const Path & destDir) const
scheme->clone(*this, destDir); scheme->clone(*this, destDir);
} }
void Input::markChangedFile( void Input::putFile(
std::string_view file, const CanonPath & path,
std::string_view contents,
std::optional<std::string> commitMsg) const std::optional<std::string> commitMsg) const
{ {
assert(scheme); assert(scheme);
return scheme->markChangedFile(*this, file, commitMsg); return scheme->putFile(*this, path, contents, commitMsg);
} }
std::string Input::getName() const std::string Input::getName() const
@ -278,9 +279,13 @@ Input InputScheme::applyOverrides(
return input; return input;
} }
void InputScheme::markChangedFile(const Input & input, std::string_view file, std::optional<std::string> commitMsg) void InputScheme::putFile(
const Input & input,
const CanonPath & path,
std::string_view contents,
std::optional<std::string> commitMsg) const
{ {
assert(false); throw Error("input '%s' does not support modifying file '%s'", input.to_string(), path);
} }
void InputScheme::clone(const Input & input, const Path & destDir) void InputScheme::clone(const Input & input, const Path & destDir)

View file

@ -79,8 +79,9 @@ public:
void clone(const Path & destDir) const; void clone(const Path & destDir) const;
void markChangedFile( void putFile(
std::string_view file, const CanonPath & path,
std::string_view contents,
std::optional<std::string> commitMsg) const; std::optional<std::string> commitMsg) const;
std::string getName() const; std::string getName() const;
@ -130,7 +131,11 @@ struct InputScheme
virtual void clone(const Input & input, const Path & destDir); virtual void clone(const Input & input, const Path & destDir);
virtual void markChangedFile(const Input & input, std::string_view file, std::optional<std::string> commitMsg); virtual void putFile(
const Input & input,
const CanonPath & path,
std::string_view contents,
std::optional<std::string> commitMsg) const;
/* Note: the default implementations of fetchToStore() and /* Note: the default implementations of fetchToStore() and
getAccessor() are defined using the other, so implementations getAccessor() are defined using the other, so implementations

View file

@ -245,17 +245,28 @@ struct GitInputScheme : InputScheme
runProgram("git", true, args); runProgram("git", true, args);
} }
void markChangedFile(const Input & input, std::string_view file, std::optional<std::string> commitMsg) override void putFile(
const Input & input,
const CanonPath & path,
std::string_view contents,
std::optional<std::string> commitMsg) const
{ {
auto repoInfo = getRepoInfo(input); auto repoInfo = getRepoInfo(input);
assert(repoInfo.isLocal); if (!repoInfo.isLocal)
throw Error("cannot commit '%s' to Git repository '%s' because it's not a working tree", path, input.to_string());
auto absPath = CanonPath(repoInfo.url) + path;
// FIXME: make sure that absPath is not a symlink that escapes
// the repo.
writeFile(absPath.abs(), contents);
runProgram("git", true, runProgram("git", true,
{ "-C", repoInfo.url, "--git-dir", repoInfo.gitDir, "add", "--force", "--intent-to-add", "--", std::string(file) }); { "-C", repoInfo.url, "--git-dir", repoInfo.gitDir, "add", "--force", "--intent-to-add", "--", std::string(path.rel()) });
if (commitMsg) if (commitMsg)
runProgram("git", true, runProgram("git", true,
{ "-C", repoInfo.url, "--git-dir", repoInfo.gitDir, "commit", std::string(file), "-m", *commitMsg }); { "-C", repoInfo.url, "--git-dir", repoInfo.gitDir, "commit", std::string(path.rel()), "-m", *commitMsg });
} }
struct RepoInfo struct RepoInfo
@ -292,7 +303,7 @@ struct GitInputScheme : InputScheme
std::string gitDir = ".git"; std::string gitDir = ".git";
}; };
RepoInfo getRepoInfo(const Input & input) RepoInfo getRepoInfo(const Input & input) const
{ {
auto checkHashType = [&](const std::optional<Hash> & hash) auto checkHashType = [&](const std::optional<Hash> & hash)
{ {

View file

@ -116,18 +116,29 @@ struct MercurialInputScheme : InputScheme
return res; return res;
} }
void markChangedFile(const Input & input, std::string_view file, std::optional<std::string> commitMsg) override void putFile(
const Input & input,
const CanonPath & path,
std::string_view contents,
std::optional<std::string> commitMsg) const
{ {
auto [isLocal, path] = getActualUrl(input); auto [isLocal, repoPath] = getActualUrl(input);
assert(isLocal); if (!isLocal)
throw Error("cannot commit '%s' to Mercurial repository '%s' because it's not a working tree", path, input.to_string());
auto absPath = CanonPath(repoPath) + path;
// FIXME: make sure that absPath is not a symlink that escapes
// the repo.
writeFile(absPath.abs(), contents);
// FIXME: shut up if file is already tracked. // FIXME: shut up if file is already tracked.
runHg( runHg(
{ "add", path + "/" + file }); { "add", absPath.abs() });
if (commitMsg) if (commitMsg)
runHg( runHg(
{ "commit", path + "/" + file, "-m", *commitMsg }); { "commit", absPath.abs(), "-m", *commitMsg });
} }
std::pair<bool, std::string> getActualUrl(const Input & input) const std::pair<bool, std::string> getActualUrl(const Input & input) const

View file

@ -80,12 +80,20 @@ struct PathInputScheme : InputScheme
return true; return true;
} }
void markChangedFile(const Input & input, std::string_view file, std::optional<std::string> commitMsg) override void putFile(
const Input & input,
const CanonPath & path,
std::string_view contents,
std::optional<std::string> commitMsg) const
{ {
// nothing to do auto absPath = CanonPath(getAbsPath(input)) + path;
// FIXME: make sure that absPath is not a symlink that escapes
// the repo.
writeFile(absPath.abs(), contents);
} }
CanonPath getAbsPath(ref<Store> store, const Input & input) const CanonPath getAbsPath(const Input & input) const
{ {
auto path = getStrAttr(input.attrs, "path"); auto path = getStrAttr(input.attrs, "path");
@ -97,7 +105,7 @@ struct PathInputScheme : InputScheme
std::pair<ref<InputAccessor>, Input> getAccessor(ref<Store> store, const Input & input) override std::pair<ref<InputAccessor>, Input> getAccessor(ref<Store> store, const Input & input) override
{ {
auto absPath = getAbsPath(store, input); auto absPath = getAbsPath(input);
auto input2(input); auto input2(input);
input2.attrs.emplace("path", (std::string) absPath.abs()); input2.attrs.emplace("path", (std::string) absPath.abs());
return {makeFSInputAccessor(absPath), std::move(input2)}; return {makeFSInputAccessor(absPath), std::move(input2)};
@ -109,7 +117,7 @@ struct PathInputScheme : InputScheme
locked, so just use the path as its fingerprint. Maybe we locked, so just use the path as its fingerprint. Maybe we
should restrict this to CA paths but that's not should restrict this to CA paths but that's not
super-important. */ super-important. */
auto path = getAbsPath(store, input); auto path = getAbsPath(input);
if (store->isInStore(path.abs())) if (store->isInStore(path.abs()))
return path.abs(); return path.abs();
return std::nullopt; return std::nullopt;