mirror of
https://github.com/NixOS/nix.git
synced 2025-11-09 12:06:01 +01:00
Merge 494c49c488 into 5b15544bdd
This commit is contained in:
commit
3e87d22c38
5 changed files with 155 additions and 65 deletions
|
|
@ -4,15 +4,15 @@
|
||||||
|
|
||||||
# Synopsis
|
# Synopsis
|
||||||
|
|
||||||
`nix-collect-garbage` [`--delete-old`] [`-d`] [`--delete-older-than` *period*] [`--max-freed` *bytes*] [`--dry-run`]
|
`nix-collect-garbage` [`--delete-old`] [`-d`] [`--delete-older-than` *period*] [`--keep-min` *generations*] [`--keep-max` *generations*] [`--max-freed` *bytes*] [`--dry-run`]
|
||||||
|
|
||||||
# Description
|
# Description
|
||||||
|
|
||||||
The command `nix-collect-garbage` is mostly an alias of [`nix-store --gc`](@docroot@/command-ref/nix-store/gc.md).
|
The command `nix-collect-garbage` is mostly an alias of [`nix-store --gc`](@docroot@/command-ref/nix-store/gc.md).
|
||||||
That is, it deletes all unreachable [store objects] in the Nix store to clean up your system.
|
That is, it deletes all unreachable [store objects] in the Nix store to clean up your system.
|
||||||
|
|
||||||
However, it provides two additional options,
|
However, it provides more additional options,
|
||||||
[`--delete-old`](#opt-delete-old) and [`--delete-older-than`](#opt-delete-older-than),
|
[`--delete-old`](#opt-delete-old), [`--delete-older-than`](#opt-delete-older-than), [`--keep-min`](#opt-keep-min), and [`--keep-max`](#opt-keep-max),
|
||||||
which also delete old [profiles], allowing potentially more [store objects] to be deleted because profiles are also garbage collection roots.
|
which also delete old [profiles], allowing potentially more [store objects] to be deleted because profiles are also garbage collection roots.
|
||||||
These options are the equivalent of running
|
These options are the equivalent of running
|
||||||
[`nix-env --delete-generations`](@docroot@/command-ref/nix-env/delete-generations.md)
|
[`nix-env --delete-generations`](@docroot@/command-ref/nix-env/delete-generations.md)
|
||||||
|
|
@ -62,7 +62,15 @@ These options are for deleting old [profiles] prior to deleting unreachable [sto
|
||||||
This is the equivalent of invoking [`nix-env --delete-generations <period>`](@docroot@/command-ref/nix-env/delete-generations.md#generations-time) on each found profile.
|
This is the equivalent of invoking [`nix-env --delete-generations <period>`](@docroot@/command-ref/nix-env/delete-generations.md#generations-time) on each found profile.
|
||||||
See the documentation of that command for additional information about the *period* argument.
|
See the documentation of that command for additional information about the *period* argument.
|
||||||
|
|
||||||
- <span id="opt-max-freed">[`--max-freed`](#opt-max-freed)</span> *bytes*
|
- <span id="opt-keep-min">[`--keep-min`](#opt-keep-min)</span> *generations*
|
||||||
|
|
||||||
|
Minimum amount of generations to keep after deletion.
|
||||||
|
|
||||||
|
- <span id="opt-keep-max">[`--keep-max`](#opt-keep-max)</span> *generations*
|
||||||
|
|
||||||
|
Maximum amount of generations to keep after deletion.
|
||||||
|
|
||||||
|
- <span id="opt-max-freed">[`--max-freed`](#opt-max-freed)</span> *bytes*
|
||||||
|
|
||||||
<!-- duplication from https://github.com/NixOS/nix/blob/442a2623e48357ff72c77bb11cf2cf06d94d2f90/doc/manual/source/command-ref/nix-store/gc.md?plain=1#L39-L44 -->
|
<!-- duplication from https://github.com/NixOS/nix/blob/442a2623e48357ff72c77bb11cf2cf06d94d2f90/doc/manual/source/command-ref/nix-store/gc.md?plain=1#L39-L44 -->
|
||||||
|
|
||||||
|
|
@ -75,7 +83,9 @@ These options are for deleting old [profiles] prior to deleting unreachable [sto
|
||||||
|
|
||||||
{{#include ./env-common.md}}
|
{{#include ./env-common.md}}
|
||||||
|
|
||||||
# Example
|
# Examples
|
||||||
|
|
||||||
|
## Delete all older
|
||||||
|
|
||||||
To delete from the Nix store everything that is not used by the current
|
To delete from the Nix store everything that is not used by the current
|
||||||
generations of each profile, do
|
generations of each profile, do
|
||||||
|
|
@ -84,5 +94,16 @@ generations of each profile, do
|
||||||
$ nix-collect-garbage -d
|
$ nix-collect-garbage -d
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Keep most-recent by time (number of days) and trim by amount
|
||||||
|
|
||||||
|
This command will delete generations older than a week if possible, while keeping an amount of generations between `10` and `20`.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ nix-collect-garbage --delete-older-than 7d --keep-min 10 --keep-max 20
|
||||||
|
```
|
||||||
|
|
||||||
|
If there were more than 20 generations built in the past week, it will only keep 20 most recent ones.
|
||||||
|
If there were less than 10 generations built in the past week, it will keep even older generations, until there is 10.
|
||||||
|
|
||||||
[profiles]: @docroot@/command-ref/files/profiles.md
|
[profiles]: @docroot@/command-ref/files/profiles.md
|
||||||
[store objects]: @docroot@/store/store-object.md
|
[store objects]: @docroot@/store/store-object.md
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,11 @@ N getIntArg(const std::string & opt, Strings::iterator & i, const Strings::itera
|
||||||
++i;
|
++i;
|
||||||
if (i == end)
|
if (i == end)
|
||||||
throw UsageError("'%1%' requires an argument", opt);
|
throw UsageError("'%1%' requires an argument", opt);
|
||||||
return string2IntWithUnitPrefix<N>(*i);
|
if (allowUnit)
|
||||||
|
return string2IntWithUnitPrefix<N>(*i);
|
||||||
|
else if (auto n = string2Int<N>(*i))
|
||||||
|
return *n;
|
||||||
|
throw UsageError("'%s' is not an integer", *i);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LegacyArgs : public MixCommonArgs, public RootArgs
|
struct LegacyArgs : public MixCommonArgs, public RootArgs
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,45 @@ void deleteGeneration(const Path & profile, GenerationNumber gen);
|
||||||
*/
|
*/
|
||||||
void deleteGenerations(const Path & profile, const std::set<GenerationNumber> & gensToDelete, bool dryRun);
|
void deleteGenerations(const Path & profile, const std::set<GenerationNumber> & gensToDelete, bool dryRun);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete old generations. Will never delete the current or future generations.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
* - All parameters are nullopt
|
||||||
|
* No generations are deleted.
|
||||||
|
* - keepMin is 5
|
||||||
|
* No generations are deleted, only keepMax and olderThan delete generations.
|
||||||
|
* - keepMax is 10
|
||||||
|
* 10 most recent generations after the current one are kept, the rest is deleted.
|
||||||
|
* - olderThan is 2025-09-16
|
||||||
|
* Generations older than 2025-09-16 are deleted.
|
||||||
|
* - olderThan is 2025-09-16, keepMin is 5, keepMax is 10 -
|
||||||
|
* Will try to delete generations older than 2025-09-16.
|
||||||
|
* If there are more than 10 generations to be kept, continues to delete old generations until there are 10.
|
||||||
|
* If there are less than 5 generations to be kept, preserves the most recent of generations to be deleted until there
|
||||||
|
* are 5.
|
||||||
|
*
|
||||||
|
* @param profile The profile, specified by its name and location combined into a path, whose generations we want to
|
||||||
|
* delete.
|
||||||
|
*
|
||||||
|
* @param olderThan Age of the oldest generation to keep.
|
||||||
|
* If nullopt, no generation will be deleted based on its age.
|
||||||
|
*
|
||||||
|
* @param keepMin Minimum amount of recent generations to keep after deletion (not counting the current or future ones).
|
||||||
|
* If nullopt, all old generations will be deleted.
|
||||||
|
*
|
||||||
|
* @param keepMax Maximum amount of recent generations to keep after deletion (not counting the current or future ones).
|
||||||
|
* If nullopt, all recent generations will be kept.
|
||||||
|
*
|
||||||
|
* @param dryRun Log what would be deleted instead of actually doing so.
|
||||||
|
*/
|
||||||
|
void deleteGenerationsFilter(
|
||||||
|
const Path & profile,
|
||||||
|
std::optional<time_t> olderThan,
|
||||||
|
std::optional<GenerationNumber> keepMin,
|
||||||
|
std::optional<GenerationNumber> keepMax,
|
||||||
|
bool dryRun);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete generations older than `max` passed the current generation.
|
* Delete generations older than `max` passed the current generation.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
@ -145,7 +146,16 @@ void deleteGenerations(const Path & profile, const std::set<GenerationNumber> &
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Advanced the iterator until the given predicate `cond` returns `true`.
|
* Advance the iterator `count` times.
|
||||||
|
*/
|
||||||
|
static inline void iterDrop(Generations & gens, auto && i, GenerationNumber count = 1)
|
||||||
|
{
|
||||||
|
for (GenerationNumber keep = 0; i != gens.rend() && keep < count; ++i, ++keep)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Advance the iterator until the given predicate `cond` returns `true`.
|
||||||
*/
|
*/
|
||||||
static inline void iterDropUntil(Generations & gens, auto && i, auto && cond)
|
static inline void iterDropUntil(Generations & gens, auto && i, auto && cond)
|
||||||
{
|
{
|
||||||
|
|
@ -153,74 +163,80 @@ static inline void iterDropUntil(Generations & gens, auto && i, auto && cond)
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
void deleteGenerationsGreaterThan(const Path & profile, GenerationNumber max, bool dryRun)
|
void deleteGenerationsFilter(
|
||||||
|
const Path & profile,
|
||||||
|
std::optional<time_t> olderThan,
|
||||||
|
std::optional<GenerationNumber> keepMin,
|
||||||
|
std::optional<GenerationNumber> keepMax,
|
||||||
|
bool dryRun)
|
||||||
{
|
{
|
||||||
if (max == 0)
|
if (keepMin.has_value() && keepMax.has_value() && *keepMin > *keepMax)
|
||||||
throw Error("Must keep at least one generation, otherwise the current one would be deleted");
|
throw Error("--keep-min cannot be greater than --keep-max");
|
||||||
|
|
||||||
PathLocks lock;
|
PathLocks lock;
|
||||||
lockProfile(lock, profile);
|
lockProfile(lock, profile);
|
||||||
|
|
||||||
auto [gens, _curGen] = findGenerations(profile);
|
auto [gens, curGen] = findGenerations(profile);
|
||||||
auto curGen = _curGen;
|
|
||||||
|
|
||||||
auto i = gens.rbegin();
|
// Keep current and future generations
|
||||||
|
auto current = gens.rbegin();
|
||||||
|
iterDropUntil(gens, current, [&](auto & g) { return g.number == curGen; });
|
||||||
|
iterDrop(gens, current);
|
||||||
|
|
||||||
// Find the current generation
|
// Compute minimum bound for kept generations
|
||||||
iterDropUntil(gens, i, [&](auto & g) { return g.number == curGen; });
|
auto start = current;
|
||||||
|
if (keepMin.has_value())
|
||||||
|
iterDrop(gens, start, *keepMin);
|
||||||
|
|
||||||
// Skip over `max` generations, preserving them
|
// Compute maximum bound for kept generations
|
||||||
for (GenerationNumber keep = 0; i != gens.rend() && keep < max; ++i, ++keep)
|
auto end = gens.rend();
|
||||||
;
|
if (keepMax.has_value()) {
|
||||||
|
end = current;
|
||||||
|
iterDrop(gens, end, *keepMax);
|
||||||
|
}
|
||||||
|
|
||||||
// Delete the rest
|
// Find the first older generation, if one exists
|
||||||
for (; i != gens.rend(); ++i)
|
auto older = gens.rend();
|
||||||
deleteGeneration2(profile, i->number, dryRun);
|
if (olderThan.has_value()) {
|
||||||
|
older = current;
|
||||||
|
iterDropUntil(gens, older, [&](auto & g) { return g.creationTime < *olderThan; });
|
||||||
|
/* Take the previous generation
|
||||||
|
|
||||||
|
We don't want delete this one yet because it
|
||||||
|
existed at the requested point in time, and
|
||||||
|
we want to be able to roll back to it. */
|
||||||
|
iterDrop(gens, older);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find first generation to delete by clamping between keepMin and keepMax
|
||||||
|
auto toDelete = older;
|
||||||
|
|
||||||
|
auto clampBackward = std::distance(gens.rbegin(), older) - std::distance(gens.rbegin(), end);
|
||||||
|
for (int i = clampBackward; i > 0; --i)
|
||||||
|
--toDelete;
|
||||||
|
|
||||||
|
auto clampForward = std::distance(gens.rbegin(), start) - std::distance(gens.rbegin(), older);
|
||||||
|
for (int i = clampForward; i > 0; --i)
|
||||||
|
++toDelete;
|
||||||
|
|
||||||
|
// Delete
|
||||||
|
for (; toDelete != gens.rend(); ++toDelete)
|
||||||
|
deleteGeneration2(profile, toDelete->number, dryRun);
|
||||||
|
}
|
||||||
|
|
||||||
|
void deleteGenerationsGreaterThan(const Path & profile, GenerationNumber max, bool dryRun)
|
||||||
|
{
|
||||||
|
deleteGenerationsFilter(profile, std::nullopt, std::nullopt, std::optional(max), dryRun);
|
||||||
}
|
}
|
||||||
|
|
||||||
void deleteOldGenerations(const Path & profile, bool dryRun)
|
void deleteOldGenerations(const Path & profile, bool dryRun)
|
||||||
{
|
{
|
||||||
PathLocks lock;
|
deleteGenerationsFilter(profile, std::nullopt, std::nullopt, std::optional(0), dryRun);
|
||||||
lockProfile(lock, profile);
|
|
||||||
|
|
||||||
auto [gens, curGen] = findGenerations(profile);
|
|
||||||
|
|
||||||
for (auto & i : gens)
|
|
||||||
if (i.number != curGen)
|
|
||||||
deleteGeneration2(profile, i.number, dryRun);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void deleteGenerationsOlderThan(const Path & profile, time_t t, bool dryRun)
|
void deleteGenerationsOlderThan(const Path & profile, time_t t, bool dryRun)
|
||||||
{
|
{
|
||||||
PathLocks lock;
|
deleteGenerationsFilter(profile, std::optional(t), std::nullopt, std::nullopt, dryRun);
|
||||||
lockProfile(lock, profile);
|
|
||||||
|
|
||||||
auto [gens, curGen] = findGenerations(profile);
|
|
||||||
|
|
||||||
auto i = gens.rbegin();
|
|
||||||
|
|
||||||
// Predicate that the generation is older than the given time.
|
|
||||||
auto older = [&](auto & g) { return g.creationTime < t; };
|
|
||||||
|
|
||||||
// Find the first older generation, if one exists
|
|
||||||
iterDropUntil(gens, i, older);
|
|
||||||
|
|
||||||
/* Take the previous generation
|
|
||||||
|
|
||||||
We don't want delete this one yet because it
|
|
||||||
existed at the requested point in time, and
|
|
||||||
we want to be able to roll back to it. */
|
|
||||||
if (i != gens.rend())
|
|
||||||
++i;
|
|
||||||
|
|
||||||
// Delete all previous generations (unless current).
|
|
||||||
for (; i != gens.rend(); ++i) {
|
|
||||||
/* Creating date and generations should be monotonic, so lower
|
|
||||||
numbered derivations should also be older. */
|
|
||||||
assert(older(*i));
|
|
||||||
if (i->number != curGen)
|
|
||||||
deleteGeneration2(profile, i->number, dryRun);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
time_t parseOlderThanTimeSpec(std::string_view timeSpec)
|
time_t parseOlderThanTimeSpec(std::string_view timeSpec)
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <cerrno>
|
#include <cerrno>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
namespace nix::fs {
|
namespace nix::fs {
|
||||||
using namespace std::filesystem;
|
using namespace std::filesystem;
|
||||||
|
|
@ -18,8 +19,10 @@ using namespace std::filesystem;
|
||||||
|
|
||||||
using namespace nix;
|
using namespace nix;
|
||||||
|
|
||||||
std::string deleteOlderThan;
|
|
||||||
bool dryRun = false;
|
bool dryRun = false;
|
||||||
|
std::optional<time_t> deleteOlderThan;
|
||||||
|
std::optional<GenerationNumber> keepMin = std::nullopt;
|
||||||
|
std::optional<GenerationNumber> keepMax = std::nullopt;
|
||||||
|
|
||||||
/* If `-d' was specified, remove all old generations of all profiles.
|
/* If `-d' was specified, remove all old generations of all profiles.
|
||||||
* Of course, this makes rollbacks to before this point in time
|
* Of course, this makes rollbacks to before this point in time
|
||||||
|
|
@ -49,10 +52,10 @@ void removeOldGenerations(std::filesystem::path dir)
|
||||||
}
|
}
|
||||||
if (link.find("link") != std::string::npos) {
|
if (link.find("link") != std::string::npos) {
|
||||||
printInfo("removing old generations of profile %s", path);
|
printInfo("removing old generations of profile %s", path);
|
||||||
if (deleteOlderThan != "") {
|
|
||||||
auto t = parseOlderThanTimeSpec(deleteOlderThan);
|
if (deleteOlderThan.has_value() || keepMax.has_value())
|
||||||
deleteGenerationsOlderThan(path, t, dryRun);
|
deleteGenerationsFilter(path, deleteOlderThan, keepMin, keepMax, dryRun);
|
||||||
} else
|
else
|
||||||
deleteOldGenerations(path, dryRun);
|
deleteOldGenerations(path, dryRun);
|
||||||
}
|
}
|
||||||
} else if (type == std::filesystem::file_type::directory) {
|
} else if (type == std::filesystem::file_type::directory) {
|
||||||
|
|
@ -77,7 +80,14 @@ static int main_nix_collect_garbage(int argc, char ** argv)
|
||||||
removeOld = true;
|
removeOld = true;
|
||||||
else if (*arg == "--delete-older-than") {
|
else if (*arg == "--delete-older-than") {
|
||||||
removeOld = true;
|
removeOld = true;
|
||||||
deleteOlderThan = getArg(*arg, arg, end);
|
deleteOlderThan = std::optional<time_t>{parseOlderThanTimeSpec(getArg(*arg, arg, end))};
|
||||||
|
} else if (*arg == "--keep-min")
|
||||||
|
keepMin = std::optional<GenerationNumber>{
|
||||||
|
std::max(getIntArg<GenerationNumber>(*arg, arg, end, false), (GenerationNumber) 1)};
|
||||||
|
else if (*arg == "--keep-max") {
|
||||||
|
removeOld = true;
|
||||||
|
keepMax = std::optional<GenerationNumber>{
|
||||||
|
std::max(getIntArg<GenerationNumber>(*arg, arg, end, false), (GenerationNumber) 1)};
|
||||||
} else if (*arg == "--dry-run")
|
} else if (*arg == "--dry-run")
|
||||||
dryRun = true;
|
dryRun = true;
|
||||||
else if (*arg == "--max-freed")
|
else if (*arg == "--max-freed")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue