mirror of
https://github.com/NixOS/nix.git
synced 2025-11-09 03:56: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
|
||||
|
||||
`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
|
||||
|
||||
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.
|
||||
|
||||
However, it provides two additional options,
|
||||
[`--delete-old`](#opt-delete-old) and [`--delete-older-than`](#opt-delete-older-than),
|
||||
However, it provides more additional options,
|
||||
[`--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.
|
||||
These options are the equivalent of running
|
||||
[`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.
|
||||
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 -->
|
||||
|
||||
|
|
@ -70,12 +78,14 @@ These options are for deleting old [profiles] prior to deleting unreachable [sto
|
|||
then stop. The argument *bytes* can be followed by the
|
||||
multiplicative suffix `K`, `M`, `G` or `T`, denoting KiB, MiB, GiB
|
||||
or TiB units.
|
||||
|
||||
|
||||
{{#include ./opt-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
|
||||
generations of each profile, do
|
||||
|
|
@ -84,5 +94,16 @@ generations of each profile, do
|
|||
$ 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
|
||||
[store objects]: @docroot@/store/store-object.md
|
||||
|
|
|
|||
|
|
@ -51,7 +51,11 @@ N getIntArg(const std::string & opt, Strings::iterator & i, const Strings::itera
|
|||
++i;
|
||||
if (i == end)
|
||||
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
|
||||
|
|
|
|||
|
|
@ -130,6 +130,45 @@ void deleteGeneration(const Path & profile, GenerationNumber gen);
|
|||
*/
|
||||
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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <algorithm>
|
||||
|
||||
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)
|
||||
{
|
||||
|
|
@ -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)
|
||||
throw Error("Must keep at least one generation, otherwise the current one would be deleted");
|
||||
if (keepMin.has_value() && keepMax.has_value() && *keepMin > *keepMax)
|
||||
throw Error("--keep-min cannot be greater than --keep-max");
|
||||
|
||||
PathLocks lock;
|
||||
lockProfile(lock, profile);
|
||||
|
||||
auto [gens, _curGen] = findGenerations(profile);
|
||||
auto curGen = _curGen;
|
||||
auto [gens, curGen] = findGenerations(profile);
|
||||
|
||||
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
|
||||
iterDropUntil(gens, i, [&](auto & g) { return g.number == curGen; });
|
||||
// Compute minimum bound for kept generations
|
||||
auto start = current;
|
||||
if (keepMin.has_value())
|
||||
iterDrop(gens, start, *keepMin);
|
||||
|
||||
// Skip over `max` generations, preserving them
|
||||
for (GenerationNumber keep = 0; i != gens.rend() && keep < max; ++i, ++keep)
|
||||
;
|
||||
// Compute maximum bound for kept generations
|
||||
auto end = gens.rend();
|
||||
if (keepMax.has_value()) {
|
||||
end = current;
|
||||
iterDrop(gens, end, *keepMax);
|
||||
}
|
||||
|
||||
// Delete the rest
|
||||
for (; i != gens.rend(); ++i)
|
||||
deleteGeneration2(profile, i->number, dryRun);
|
||||
// Find the first older generation, if one exists
|
||||
auto older = gens.rend();
|
||||
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)
|
||||
{
|
||||
PathLocks lock;
|
||||
lockProfile(lock, profile);
|
||||
|
||||
auto [gens, curGen] = findGenerations(profile);
|
||||
|
||||
for (auto & i : gens)
|
||||
if (i.number != curGen)
|
||||
deleteGeneration2(profile, i.number, dryRun);
|
||||
deleteGenerationsFilter(profile, std::nullopt, std::nullopt, std::optional(0), dryRun);
|
||||
}
|
||||
|
||||
void deleteGenerationsOlderThan(const Path & profile, time_t t, bool dryRun)
|
||||
{
|
||||
PathLocks lock;
|
||||
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);
|
||||
}
|
||||
deleteGenerationsFilter(profile, std::optional(t), std::nullopt, std::nullopt, dryRun);
|
||||
}
|
||||
|
||||
time_t parseOlderThanTimeSpec(std::string_view timeSpec)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <iostream>
|
||||
#include <cerrno>
|
||||
#include <optional>
|
||||
|
||||
namespace nix::fs {
|
||||
using namespace std::filesystem;
|
||||
|
|
@ -18,8 +19,10 @@ using namespace std::filesystem;
|
|||
|
||||
using namespace nix;
|
||||
|
||||
std::string deleteOlderThan;
|
||||
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.
|
||||
* 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) {
|
||||
printInfo("removing old generations of profile %s", path);
|
||||
if (deleteOlderThan != "") {
|
||||
auto t = parseOlderThanTimeSpec(deleteOlderThan);
|
||||
deleteGenerationsOlderThan(path, t, dryRun);
|
||||
} else
|
||||
|
||||
if (deleteOlderThan.has_value() || keepMax.has_value())
|
||||
deleteGenerationsFilter(path, deleteOlderThan, keepMin, keepMax, dryRun);
|
||||
else
|
||||
deleteOldGenerations(path, dryRun);
|
||||
}
|
||||
} else if (type == std::filesystem::file_type::directory) {
|
||||
|
|
@ -77,7 +80,14 @@ static int main_nix_collect_garbage(int argc, char ** argv)
|
|||
removeOld = true;
|
||||
else if (*arg == "--delete-older-than") {
|
||||
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")
|
||||
dryRun = true;
|
||||
else if (*arg == "--max-freed")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue