package transactional import ( "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/storer" "github.com/go-git/go-git/v5/storage" ) // ReferenceStorage implements the storer.ReferenceStorage for the transactional package. type ReferenceStorage struct { storer.ReferenceStorer temporal storer.ReferenceStorer // deleted, remaining references at this maps are going to be deleted when // commit is requested, the entries are added when RemoveReference is called // and deleted if SetReference is called. deleted map[plumbing.ReferenceName]struct{} // packRefs if true PackRefs is going to be called in the based storer when // commit is called. packRefs bool } // NewReferenceStorage returns a new ReferenceStorer based on a base storer and // a temporal storer. func NewReferenceStorage(base, temporal storer.ReferenceStorer) *ReferenceStorage { return &ReferenceStorage{ ReferenceStorer: base, temporal: temporal, deleted: make(map[plumbing.ReferenceName]struct{}, 0), } } // SetReference honors the storer.ReferenceStorer interface. func (r *ReferenceStorage) SetReference(ref *plumbing.Reference) error { delete(r.deleted, ref.Name()) return r.temporal.SetReference(ref) } // SetReference honors the storer.ReferenceStorer interface. func (r *ReferenceStorage) CheckAndSetReference(ref, old *plumbing.Reference) error { if old == nil { return r.SetReference(ref) } tmp, err := r.temporal.Reference(old.Name()) if err == plumbing.ErrReferenceNotFound { tmp, err = r.ReferenceStorer.Reference(old.Name()) } if err != nil { return err } if tmp.Hash() != old.Hash() { return storage.ErrReferenceHasChanged } return r.SetReference(ref) } // Reference honors the storer.ReferenceStorer interface. func (r ReferenceStorage) Reference(n plumbing.ReferenceName) (*plumbing.Reference, error) { if _, deleted := r.deleted[n]; deleted { return nil, plumbing.ErrReferenceNotFound } ref, err := r.temporal.Reference(n) if err == plumbing.ErrReferenceNotFound { return r.ReferenceStorer.Reference(n) } return ref, err } // IterReferences honors the storer.ReferenceStorer interface. func (r ReferenceStorage) IterReferences() (storer.ReferenceIter, error) { baseIter, err := r.ReferenceStorer.IterReferences() if err != nil { return nil, err } temporalIter, err := r.temporal.IterReferences() if err != nil { return nil, err } return storer.NewMultiReferenceIter([]storer.ReferenceIter{ baseIter, temporalIter, }), nil } // CountLooseRefs honors the storer.ReferenceStorer interface. func (r ReferenceStorage) CountLooseRefs() (int, error) { tc, err := r.temporal.CountLooseRefs() if err != nil { return -1, err } bc, err := r.ReferenceStorer.CountLooseRefs() if err != nil { return -1, err } return tc + bc, nil } // PackRefs honors the storer.ReferenceStorer interface. func (r ReferenceStorage) PackRefs() error { r.packRefs = true return nil } // RemoveReference honors the storer.ReferenceStorer interface. func (r ReferenceStorage) RemoveReference(n plumbing.ReferenceName) error { r.deleted[n] = struct{}{} return r.temporal.RemoveReference(n) } // Commit it copies the reference information of the temporal storage into the // base storage. func (r ReferenceStorage) Commit() error { for name := range r.deleted { if err := r.ReferenceStorer.RemoveReference(name); err != nil { return err } } iter, err := r.temporal.IterReferences() if err != nil { return err } return iter.ForEach(func(ref *plumbing.Reference) error { return r.ReferenceStorer.SetReference(ref) }) }