218 lines
7.1 KiB
Go
218 lines
7.1 KiB
Go
|
package otbuiltin
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"errors"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"time"
|
||
|
"unsafe"
|
||
|
|
||
|
glib "github.com/ostreedev/ostree-go/pkg/glibobject"
|
||
|
)
|
||
|
|
||
|
// #cgo pkg-config: ostree-1
|
||
|
// #include <stdlib.h>
|
||
|
// #include <glib.h>
|
||
|
// #include <ostree.h>
|
||
|
// #include "builtin.go.h"
|
||
|
import "C"
|
||
|
|
||
|
// Declare gobal variable for options
|
||
|
var pruneOpts pruneOptions
|
||
|
|
||
|
// Contains all of the options for pruning an ostree repo. Use
|
||
|
// NewPruneOptions() to initialize
|
||
|
type pruneOptions struct {
|
||
|
NoPrune bool // Only display unreachable objects; don't delete
|
||
|
RefsOnly bool // Only compute reachability via refs
|
||
|
DeleteCommit string // Specify a commit to delete
|
||
|
KeepYoungerThan time.Time // All commits older than this date will be pruned
|
||
|
Depth int // Only traverse depths (integer) parents for each commit (default: -1=infinite)
|
||
|
StaticDeltasOnly int // Change the behavior of --keep-younger-than and --delete-commit to prune only the static delta files
|
||
|
}
|
||
|
|
||
|
// Instantiates and returns a pruneOptions struct with default values set
|
||
|
func NewPruneOptions() pruneOptions {
|
||
|
po := new(pruneOptions)
|
||
|
po.Depth = -1
|
||
|
return *po
|
||
|
}
|
||
|
|
||
|
// Search for unreachable objects in the repository given by repoPath. Removes the
|
||
|
// objects unless pruneOptions.NoPrune is specified
|
||
|
func Prune(repoPath string, options pruneOptions) (string, error) {
|
||
|
pruneOpts = options
|
||
|
// attempt to open the repository
|
||
|
repo, err := OpenRepo(repoPath)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
|
||
|
var pruneFlags C.OstreeRepoPruneFlags
|
||
|
var numObjectsTotal int
|
||
|
var numObjectsPruned int
|
||
|
var objSizeTotal uint64
|
||
|
var gerr = glib.NewGError()
|
||
|
var cerr = (*C.GError)(gerr.Ptr())
|
||
|
defer C.free(unsafe.Pointer(cerr))
|
||
|
var cancellable *glib.GCancellable
|
||
|
|
||
|
if !pruneOpts.NoPrune && !glib.GoBool(glib.GBoolean(C.ostree_repo_is_writable(repo.native(), &cerr))) {
|
||
|
return "", generateError(cerr)
|
||
|
}
|
||
|
|
||
|
cerr = nil
|
||
|
if strings.Compare(pruneOpts.DeleteCommit, "") != 0 {
|
||
|
if pruneOpts.NoPrune {
|
||
|
return "", errors.New("Cannot specify both pruneOptions.DeleteCommit and pruneOptions.NoPrune")
|
||
|
}
|
||
|
|
||
|
if pruneOpts.StaticDeltasOnly > 0 {
|
||
|
if glib.GoBool(glib.GBoolean(C.ostree_repo_prune_static_deltas(repo.native(), C.CString(pruneOpts.DeleteCommit), (*C.GCancellable)(cancellable.Ptr()), &cerr))) {
|
||
|
return "", generateError(cerr)
|
||
|
}
|
||
|
} else if err = deleteCommit(repo, pruneOpts.DeleteCommit, cancellable); err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if !pruneOpts.KeepYoungerThan.IsZero() {
|
||
|
if pruneOpts.NoPrune {
|
||
|
return "", errors.New("Cannot specify both pruneOptions.KeepYoungerThan and pruneOptions.NoPrune")
|
||
|
}
|
||
|
|
||
|
if err = pruneCommitsKeepYoungerThanDate(repo, pruneOpts.KeepYoungerThan, cancellable); err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if pruneOpts.RefsOnly {
|
||
|
pruneFlags |= C.OSTREE_REPO_PRUNE_FLAGS_REFS_ONLY
|
||
|
}
|
||
|
if pruneOpts.NoPrune {
|
||
|
pruneFlags |= C.OSTREE_REPO_PRUNE_FLAGS_NO_PRUNE
|
||
|
}
|
||
|
|
||
|
formattedFreedSize := C.GoString((*C.char)(C.g_format_size_full((C.guint64)(objSizeTotal), 0)))
|
||
|
|
||
|
var buffer bytes.Buffer
|
||
|
|
||
|
buffer.WriteString("Total objects: ")
|
||
|
buffer.WriteString(strconv.Itoa(numObjectsTotal))
|
||
|
if numObjectsPruned == 0 {
|
||
|
buffer.WriteString("\nNo unreachable objects")
|
||
|
} else if pruneOpts.NoPrune {
|
||
|
buffer.WriteString("\nWould delete: ")
|
||
|
buffer.WriteString(strconv.Itoa(numObjectsPruned))
|
||
|
buffer.WriteString(" objects, freeing ")
|
||
|
buffer.WriteString(formattedFreedSize)
|
||
|
} else {
|
||
|
buffer.WriteString("\nDeleted ")
|
||
|
buffer.WriteString(strconv.Itoa(numObjectsPruned))
|
||
|
buffer.WriteString(" objects, ")
|
||
|
buffer.WriteString(formattedFreedSize)
|
||
|
buffer.WriteString(" freed")
|
||
|
}
|
||
|
|
||
|
return buffer.String(), nil
|
||
|
}
|
||
|
|
||
|
// Delete an unreachable commit from the repo
|
||
|
func deleteCommit(repo *Repo, commitToDelete string, cancellable *glib.GCancellable) error {
|
||
|
var refs *glib.GHashTable
|
||
|
var hashIter glib.GHashTableIter
|
||
|
var hashkey, hashvalue C.gpointer
|
||
|
var gerr = glib.NewGError()
|
||
|
var cerr = (*C.GError)(gerr.Ptr())
|
||
|
defer C.free(unsafe.Pointer(cerr))
|
||
|
|
||
|
if glib.GoBool(glib.GBoolean(C.ostree_repo_list_refs(repo.native(), nil, (**C.GHashTable)(refs.Ptr()), (*C.GCancellable)(cancellable.Ptr()), &cerr))) {
|
||
|
return generateError(cerr)
|
||
|
}
|
||
|
|
||
|
C.g_hash_table_iter_init((*C.GHashTableIter)(hashIter.Ptr()), (*C.GHashTable)(refs.Ptr()))
|
||
|
for C.g_hash_table_iter_next((*C.GHashTableIter)(hashIter.Ptr()), &hashkey, &hashvalue) != 0 {
|
||
|
var ref string = C.GoString((*C.char)(hashkey))
|
||
|
var commit string = C.GoString((*C.char)(hashvalue))
|
||
|
if strings.Compare(commitToDelete, commit) == 0 {
|
||
|
var buffer bytes.Buffer
|
||
|
buffer.WriteString("Commit ")
|
||
|
buffer.WriteString(commitToDelete)
|
||
|
buffer.WriteString(" is referenced by ")
|
||
|
buffer.WriteString(ref)
|
||
|
return errors.New(buffer.String())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if err := enableTombstoneCommits(repo); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if !glib.GoBool(glib.GBoolean(C.ostree_repo_delete_object(repo.native(), C.OSTREE_OBJECT_TYPE_COMMIT, C.CString(commitToDelete), (*C.GCancellable)(cancellable.Ptr()), &cerr))) {
|
||
|
return generateError(cerr)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Prune commits but keep any younger than the given date regardless of whether they
|
||
|
// are reachable
|
||
|
func pruneCommitsKeepYoungerThanDate(repo *Repo, date time.Time, cancellable *glib.GCancellable) error {
|
||
|
var objects *glib.GHashTable
|
||
|
defer C.free(unsafe.Pointer(objects))
|
||
|
var hashIter glib.GHashTableIter
|
||
|
var key, value C.gpointer
|
||
|
defer C.free(unsafe.Pointer(key))
|
||
|
defer C.free(unsafe.Pointer(value))
|
||
|
var gerr = glib.NewGError()
|
||
|
var cerr = (*C.GError)(gerr.Ptr())
|
||
|
defer C.free(unsafe.Pointer(cerr))
|
||
|
|
||
|
if err := enableTombstoneCommits(repo); err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if !glib.GoBool(glib.GBoolean(C.ostree_repo_list_objects(repo.native(), C.OSTREE_REPO_LIST_OBJECTS_ALL, (**C.GHashTable)(objects.Ptr()), (*C.GCancellable)(cancellable.Ptr()), &cerr))) {
|
||
|
return generateError(cerr)
|
||
|
}
|
||
|
|
||
|
C.g_hash_table_iter_init((*C.GHashTableIter)(hashIter.Ptr()), (*C.GHashTable)(objects.Ptr()))
|
||
|
for C.g_hash_table_iter_next((*C.GHashTableIter)(hashIter.Ptr()), &key, &value) != 0 {
|
||
|
var serializedKey *glib.GVariant
|
||
|
defer C.free(unsafe.Pointer(serializedKey))
|
||
|
var checksum *C.char
|
||
|
defer C.free(unsafe.Pointer(checksum))
|
||
|
var objType C.OstreeObjectType
|
||
|
var commitTimestamp uint64
|
||
|
var commit *glib.GVariant = nil
|
||
|
|
||
|
C.ostree_object_name_deserialize((*C.GVariant)(serializedKey.Ptr()), &checksum, &objType)
|
||
|
|
||
|
if objType != C.OSTREE_OBJECT_TYPE_COMMIT {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
cerr = nil
|
||
|
if !glib.GoBool(glib.GBoolean(C.ostree_repo_load_variant(repo.native(), C.OSTREE_OBJECT_TYPE_COMMIT, checksum, (**C.GVariant)(commit.Ptr()), &cerr))) {
|
||
|
return generateError(cerr)
|
||
|
}
|
||
|
|
||
|
commitTimestamp = (uint64)(C.ostree_commit_get_timestamp((*C.GVariant)(commit.Ptr())))
|
||
|
if commitTimestamp < (uint64)(date.Unix()) {
|
||
|
cerr = nil
|
||
|
if pruneOpts.StaticDeltasOnly != 0 {
|
||
|
if !glib.GoBool(glib.GBoolean(C.ostree_repo_prune_static_deltas(repo.native(), checksum, (*C.GCancellable)(cancellable.Ptr()), &cerr))) {
|
||
|
return generateError(cerr)
|
||
|
}
|
||
|
} else {
|
||
|
if !glib.GoBool(glib.GBoolean(C.ostree_repo_delete_object(repo.native(), C.OSTREE_OBJECT_TYPE_COMMIT, checksum, (*C.GCancellable)(cancellable.Ptr()), &cerr))) {
|
||
|
return generateError(cerr)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|