diff options
author | Taru Karttunen <taruti@taruti.net> | 2017-11-01 21:06:37 +0200 |
---|---|---|
committer | Jeremy Stribling <strib@alum.mit.edu> | 2017-11-29 10:40:46 -0800 |
commit | ac1914eac3c20efa63de8809229994364ad9639b (patch) | |
tree | 4595bfceec10aea603517e5dc7f493b93e068d30 /repository.go | |
parent | a6202cacd12aa5ee20c54aff799b0e91330526c5 (diff) | |
download | go-git-ac1914eac3c20efa63de8809229994364ad9639b.tar.gz |
First pass of prune design
Diffstat (limited to 'repository.go')
-rw-r--r-- | repository.go | 124 |
1 files changed, 124 insertions, 0 deletions
diff --git a/repository.go b/repository.go index b159ff0..7079fd1 100644 --- a/repository.go +++ b/repository.go @@ -8,10 +8,12 @@ import ( "os" "path/filepath" "strings" + "time" "gopkg.in/src-d/go-git.v4/config" "gopkg.in/src-d/go-git.v4/internal/revision" "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/filemode" "gopkg.in/src-d/go-git.v4/plumbing/object" "gopkg.in/src-d/go-git.v4/plumbing/storer" "gopkg.in/src-d/go-git.v4/storage" @@ -1011,3 +1013,125 @@ func (r *Repository) ResolveRevision(rev plumbing.Revision) (*plumbing.Hash, err return &commit.Hash, nil } + +type PruneHandler func(unreferencedObjectHash plumbing.Hash) error +type PruneOptions struct { + // OnlyObjectsOlderThan if set to non-zero value + // selects only objects older than the time provided. + OnlyObjectsOlderThan time.Time + // Handler is called on matching objects + Handler PruneHandler +} + +// DeleteObject deletes an object from a repository. +// The type conveniently matches PruneHandler. +func (r *Repository) DeleteObject(hash plumbing.Hash) error { + return r.Storer.DeleteLooseObject(hash) +} + +func (r *Repository) Prune(opt PruneOptions) error { + pw := &pruneWalker{ + r: r, + seen: map[plumbing.Hash]struct{}{}, + } + // Walk over all the references in the repo. + it, err := r.Storer.IterReferences() + if err != nil { + return nil + } + defer it.Close() + err = it.ForEach(func(ref *plumbing.Reference) error { + // Exit this iteration early for non-hash references. + if ref.Type() != plumbing.HashReference { + return nil + } + return pw.walkObjectTree(ref.Hash()) + }) + if err != nil { + return err + } + // Now walk all (loose) objects in storage. + err = r.Storer.ForEachObjectHash(func(hash plumbing.Hash) error { + // Get out if we have seen this object. + if pw.isSeen(hash) { + return nil + } + // Otherwise it is a candidate for pruning. + // Check out for too new objects next. + if opt.OnlyObjectsOlderThan != (time.Time{}) { + // Errors here are non-fatal. The object may be e.g. packed. + // Or concurrently deleted. Skip such objects. + t, err := r.Storer.LooseObjectTime(hash) + if err != nil { + return nil + } + // Skip too new objects. + if !t.Before(opt.OnlyObjectsOlderThan) { + return nil + } + } + return opt.Handler(hash) + }) + if err != nil { + return err + } + return nil +} + +type pruneWalker struct { + r *Repository + seen map[plumbing.Hash]struct{} +} + +func (p *pruneWalker) isSeen(hash plumbing.Hash) bool { + _, seen := p.seen[hash] + return seen +} + +func (p *pruneWalker) add(hash plumbing.Hash) { + p.seen[hash] = struct{}{} +} + +func (p *pruneWalker) walkObjectTree(hash plumbing.Hash) error { + // Check if we have already seen, and mark this object + if p.isSeen(hash) { + return nil + } + p.add(hash) + // Fetch the object. + obj, err := object.GetObject(p.r.Storer, hash) + if err != nil { + return fmt.Errorf("Getting object %s failed: %v", hash, err) + } + // Walk all children depending on object type. + switch obj := obj.(type) { + case *object.Commit: + err = p.walkObjectTree(obj.TreeHash) + if err != nil { + return err + } + for _, h := range obj.ParentHashes { + err = p.walkObjectTree(h) + if err != nil { + return err + } + } + case *object.Tree: + for i := range obj.Entries { + // Shortcut for blob objects: + if obj.Entries[i].Mode|0755 == filemode.Executable { + p.add(obj.Entries[i].Hash) + continue + } + // Normal walk for sub-trees (and symlinks etc). + err = p.walkObjectTree(obj.Entries[i].Hash) + if err != nil { + return err + } + } + default: + // Error out on unhandled object types. + return fmt.Errorf("Unknown object %X %s %T\n", obj.ID(), obj.Type(), obj) + } + return nil +} |