Merge pull request #5249 from edolstra/nix-profile

Add missing 'nix profile' subcommands
This commit is contained in:
Eelco Dolstra 2021-09-14 22:27:19 +02:00 committed by GitHub
commit 5ee3ee1a6b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 181 additions and 46 deletions

View file

@ -126,9 +126,9 @@ void deleteGeneration(const Path & profile, GenerationNumber gen)
static void deleteGeneration2(const Path & profile, GenerationNumber gen, bool dryRun) static void deleteGeneration2(const Path & profile, GenerationNumber gen, bool dryRun)
{ {
if (dryRun) if (dryRun)
printInfo(format("would remove generation %1%") % gen); notice("would remove profile version %1%", gen);
else { else {
printInfo(format("removing generation %1%") % gen); notice("removing profile version %1%", gen);
deleteGeneration(profile, gen); deleteGeneration(profile, gen);
} }
} }
@ -142,7 +142,7 @@ void deleteGenerations(const Path & profile, const std::set<GenerationNumber> &
auto [gens, curGen] = findGenerations(profile); auto [gens, curGen] = findGenerations(profile);
if (gensToDelete.count(*curGen)) if (gensToDelete.count(*curGen))
throw Error("cannot delete current generation of profile %1%'", profile); throw Error("cannot delete current version of profile %1%'", profile);
for (auto & i : gens) { for (auto & i : gens) {
if (!gensToDelete.count(i.number)) continue; if (!gensToDelete.count(i.number)) continue;
@ -236,6 +236,37 @@ void switchLink(Path link, Path target)
} }
void switchGeneration(
const Path & profile,
std::optional<GenerationNumber> dstGen,
bool dryRun)
{
PathLocks lock;
lockProfile(lock, profile);
auto [gens, curGen] = findGenerations(profile);
std::optional<Generation> dst;
for (auto & i : gens)
if ((!dstGen && i.number < curGen) ||
(dstGen && i.number == *dstGen))
dst = i;
if (!dst) {
if (dstGen)
throw Error("profile version %1% does not exist", *dstGen);
else
throw Error("no profile version older than the current (%1%) exists", curGen.value_or(0));
}
notice("switching profile from version %d to %d", curGen.value_or(0), dst->number);
if (dryRun) return;
switchLink(profile, dst->path);
}
void lockProfile(PathLocks & lock, const Path & profile) void lockProfile(PathLocks & lock, const Path & profile)
{ {
lock.lockPaths({profile}, (format("waiting for lock on profile '%1%'") % profile).str()); lock.lockPaths({profile}, (format("waiting for lock on profile '%1%'") % profile).str());

View file

@ -11,7 +11,7 @@ namespace nix {
class StorePath; class StorePath;
typedef unsigned int GenerationNumber; typedef uint64_t GenerationNumber;
struct Generation struct Generation
{ {
@ -46,6 +46,13 @@ void deleteGenerationsOlderThan(const Path & profile, const string & timeSpec, b
void switchLink(Path link, Path target); void switchLink(Path link, Path target);
/* Roll back a profile to the specified generation, or to the most
recent one older than the current. */
void switchGeneration(
const Path & profile,
std::optional<GenerationNumber> dstGen,
bool dryRun);
/* Ensure exclusive access to a profile. Any command that modifies /* Ensure exclusive access to a profile. Any command that modifies
the profile first acquires this lock. */ the profile first acquires this lock. */
void lockProfile(PathLocks & lock, const Path & profile); void lockProfile(PathLocks & lock, const Path & profile);

View file

@ -91,6 +91,14 @@ protected:
}) })
, arity(1) , arity(1)
{ } { }
template<class I>
Handler(std::optional<I> * dest)
: fun([=](std::vector<std::string> ss) {
*dest = string2IntWithUnitPrefix<I>(ss[0]);
})
, arity(1)
{ }
}; };
/* Options. */ /* Options. */

View file

@ -1204,37 +1204,6 @@ static void opSwitchProfile(Globals & globals, Strings opFlags, Strings opArgs)
} }
static constexpr GenerationNumber prevGen = std::numeric_limits<GenerationNumber>::max();
static void switchGeneration(Globals & globals, GenerationNumber dstGen)
{
PathLocks lock;
lockProfile(lock, globals.profile);
auto [gens, curGen] = findGenerations(globals.profile);
std::optional<Generation> dst;
for (auto & i : gens)
if ((dstGen == prevGen && i.number < curGen) ||
(dstGen >= 0 && i.number == dstGen))
dst = i;
if (!dst) {
if (dstGen == prevGen)
throw Error("no generation older than the current (%1%) exists", curGen.value_or(0));
else
throw Error("generation %1% does not exist", dstGen);
}
printInfo("switching from generation %1% to %2%", curGen.value_or(0), dst->number);
if (globals.dryRun) return;
switchLink(globals.profile, dst->path);
}
static void opSwitchGeneration(Globals & globals, Strings opFlags, Strings opArgs) static void opSwitchGeneration(Globals & globals, Strings opFlags, Strings opArgs)
{ {
if (opFlags.size() > 0) if (opFlags.size() > 0)
@ -1243,7 +1212,7 @@ static void opSwitchGeneration(Globals & globals, Strings opFlags, Strings opArg
throw UsageError("exactly one argument expected"); throw UsageError("exactly one argument expected");
if (auto dstGen = string2Int<GenerationNumber>(opArgs.front())) if (auto dstGen = string2Int<GenerationNumber>(opArgs.front()))
switchGeneration(globals, *dstGen); switchGeneration(globals.profile, *dstGen, globals.dryRun);
else else
throw UsageError("expected a generation number"); throw UsageError("expected a generation number");
} }
@ -1256,7 +1225,7 @@ static void opRollback(Globals & globals, Strings opFlags, Strings opArgs)
if (opArgs.size() != 0) if (opArgs.size() != 0)
throw UsageError("no arguments expected"); throw UsageError("no arguments expected");
switchGeneration(globals, prevGen); switchGeneration(globals.profile, {}, globals.dryRun);
} }
@ -1297,11 +1266,11 @@ static void opDeleteGenerations(Globals & globals, Strings opFlags, Strings opAr
deleteGenerationsOlderThan(globals.profile, opArgs.front(), globals.dryRun); deleteGenerationsOlderThan(globals.profile, opArgs.front(), globals.dryRun);
} else if (opArgs.size() == 1 && opArgs.front().find('+') != string::npos) { } else if (opArgs.size() == 1 && opArgs.front().find('+') != string::npos) {
if (opArgs.front().size() < 2) if (opArgs.front().size() < 2)
throw Error("invalid number of generations %1%", opArgs.front()); throw Error("invalid number of generations '%1%'", opArgs.front());
string str_max = string(opArgs.front(), 1, opArgs.front().size()); string str_max = string(opArgs.front(), 1, opArgs.front().size());
auto max = string2Int<GenerationNumber>(str_max); auto max = string2Int<GenerationNumber>(str_max);
if (!max || *max == 0) if (!max || *max == 0)
throw Error("invalid number of generations to keep %1%", opArgs.front()); throw Error("invalid number of generations to keep '%1%'", opArgs.front());
deleteGenerationsGreaterThan(globals.profile, *max, globals.dryRun); deleteGenerationsGreaterThan(globals.profile, *max, globals.dryRun);
} else { } else {
std::set<GenerationNumber> gens; std::set<GenerationNumber> gens;

View file

@ -6,10 +6,10 @@ R""(
```console ```console
# nix profile history # nix profile history
Version 508 -> 509: Version 508 (2020-04-10):
flake:nixpkgs#legacyPackages.x86_64-linux.awscli: ∅ -> 1.17.13 flake:nixpkgs#legacyPackages.x86_64-linux.awscli: ∅ -> 1.17.13
Version 509 -> 510: Version 509 (2020-05-16) <- 508:
flake:nixpkgs#legacyPackages.x86_64-linux.awscli: 1.17.13 -> 1.18.211 flake:nixpkgs#legacyPackages.x86_64-linux.awscli: 1.17.13 -> 1.18.211
``` ```

View file

@ -0,0 +1,26 @@
R""(
# Examples
* Roll back your default profile to the previous version:
```console
# nix profile rollback
switching profile from version 519 to 518
```
* Switch your default profile to version 510:
```console
# nix profile rollback --to 510
switching profile from version 518 to 510
```
# Description
This command switches a profile to the most recent version older
than the currently active version, or if `--to` *N* is given, to
version *N* of the profile. To see the available versions of a
profile, use `nix profile history`.
)""

View file

@ -0,0 +1,20 @@
R""(
# Examples
* Delete all versions of the default profile older than 100 days:
```console
# nix profile wipe-history --profile /tmp/profile --older-than 100d
removing profile version 515
removing profile version 514
```
# Description
This command deletes non-current versions of a profile, making it
impossible to roll back to these versions. By default, all non-current
versions are deleted. With `--older-than` *N*`d`, all non-current
versions older than *N* days are deleted.
)""

View file

@ -12,6 +12,7 @@
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <regex> #include <regex>
#include <iomanip>
using namespace nix; using namespace nix;
@ -528,10 +529,11 @@ struct CmdProfileHistory : virtual StoreCommand, EvalCommand, MixDefaultProfile
if (!first) std::cout << "\n"; if (!first) std::cout << "\n";
first = false; first = false;
if (prevGen) std::cout << fmt("Version %s%d" ANSI_NORMAL " (%s)%s:\n",
std::cout << fmt("Version %d -> %d:\n", prevGen->first.number, gen.number); gen.number == curGen ? ANSI_GREEN : ANSI_BOLD,
else gen.number,
std::cout << fmt("Version %d:\n", gen.number); std::put_time(std::gmtime(&gen.creationTime), "%Y-%m-%d"),
prevGen ? fmt(" <- %d", prevGen->first.number) : "");
ProfileManifest::printDiff( ProfileManifest::printDiff(
prevGen ? prevGen->second : ProfileManifest(), prevGen ? prevGen->second : ProfileManifest(),
@ -543,6 +545,76 @@ struct CmdProfileHistory : virtual StoreCommand, EvalCommand, MixDefaultProfile
} }
}; };
struct CmdProfileRollback : virtual StoreCommand, MixDefaultProfile, MixDryRun
{
std::optional<GenerationNumber> version;
CmdProfileRollback()
{
addFlag({
.longName = "to",
.description = "The profile version to roll back to.",
.labels = {"version"},
.handler = {&version},
});
}
std::string description() override
{
return "roll back to the previous version or a specified version of a profile";
}
std::string doc() override
{
return
#include "profile-rollback.md"
;
}
void run(ref<Store> store) override
{
switchGeneration(*profile, version, dryRun);
}
};
struct CmdProfileWipeHistory : virtual StoreCommand, MixDefaultProfile, MixDryRun
{
std::optional<std::string> minAge;
CmdProfileWipeHistory()
{
addFlag({
.longName = "older-than",
.description =
"Delete versions older than the specified age. *age* "
"must be in the format *N*`d`, where *N* denotes a number "
"of days.",
.labels = {"age"},
.handler = {&minAge},
});
}
std::string description() override
{
return "delete non-current versions of a profile";
}
std::string doc() override
{
return
#include "profile-wipe-history.md"
;
}
void run(ref<Store> store) override
{
if (minAge)
deleteGenerationsOlderThan(*profile, *minAge, dryRun);
else
deleteOldGenerations(*profile, dryRun);
}
};
struct CmdProfile : NixMultiCommand struct CmdProfile : NixMultiCommand
{ {
CmdProfile() CmdProfile()
@ -553,6 +625,8 @@ struct CmdProfile : NixMultiCommand
{"list", []() { return make_ref<CmdProfileList>(); }}, {"list", []() { return make_ref<CmdProfileList>(); }},
{"diff-closures", []() { return make_ref<CmdProfileDiffClosures>(); }}, {"diff-closures", []() { return make_ref<CmdProfileDiffClosures>(); }},
{"history", []() { return make_ref<CmdProfileHistory>(); }}, {"history", []() { return make_ref<CmdProfileHistory>(); }},
{"rollback", []() { return make_ref<CmdProfileRollback>(); }},
{"wipe-history", []() { return make_ref<CmdProfileWipeHistory>(); }},
}) })
{ } { }