Merge pull request #5249 from edolstra/nix-profile
Add missing 'nix profile' subcommands
This commit is contained in:
commit
5ee3ee1a6b
|
@ -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());
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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. */
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1296,12 +1265,12 @@ static void opDeleteGenerations(Globals & globals, Strings opFlags, Strings opAr
|
||||||
} else if (opArgs.size() == 1 && opArgs.front().find('d') != string::npos) {
|
} else if (opArgs.size() == 1 && opArgs.front().find('d') != string::npos) {
|
||||||
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;
|
||||||
|
|
|
@ -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
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
26
src/nix/profile-rollback.md
Normal file
26
src/nix/profile-rollback.md
Normal 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`.
|
||||||
|
|
||||||
|
)""
|
20
src/nix/profile-wipe-history.md
Normal file
20
src/nix/profile-wipe-history.md
Normal 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.
|
||||||
|
|
||||||
|
)""
|
|
@ -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>(); }},
|
||||||
})
|
})
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue