1package transactional
2
3import (
4 "github.com/go-git/go-git/v5/plumbing"
5 "github.com/go-git/go-git/v5/plumbing/storer"
6 "github.com/go-git/go-git/v5/storage"
7)
8
9// ReferenceStorage implements the storer.ReferenceStorage for the transactional package.
10type ReferenceStorage struct {
11 storer.ReferenceStorer
12 temporal storer.ReferenceStorer
13
14 // deleted, remaining references at this maps are going to be deleted when
15 // commit is requested, the entries are added when RemoveReference is called
16 // and deleted if SetReference is called.
17 deleted map[plumbing.ReferenceName]struct{}
18}
19
20// NewReferenceStorage returns a new ReferenceStorer based on a base storer and
21// a temporal storer.
22func NewReferenceStorage(base, temporal storer.ReferenceStorer) *ReferenceStorage {
23 return &ReferenceStorage{
24 ReferenceStorer: base,
25 temporal: temporal,
26
27 deleted: make(map[plumbing.ReferenceName]struct{}),
28 }
29}
30
31// SetReference honors the storer.ReferenceStorer interface.
32func (r *ReferenceStorage) SetReference(ref *plumbing.Reference) error {
33 delete(r.deleted, ref.Name())
34 return r.temporal.SetReference(ref)
35}
36
37// SetReference honors the storer.ReferenceStorer interface.
38func (r *ReferenceStorage) CheckAndSetReference(ref, old *plumbing.Reference) error {
39 if old == nil {
40 return r.SetReference(ref)
41 }
42
43 tmp, err := r.temporal.Reference(old.Name())
44 if err == plumbing.ErrReferenceNotFound {
45 tmp, err = r.ReferenceStorer.Reference(old.Name())
46 }
47
48 if err != nil {
49 return err
50 }
51
52 if tmp.Hash() != old.Hash() {
53 return storage.ErrReferenceHasChanged
54 }
55
56 return r.SetReference(ref)
57}
58
59// Reference honors the storer.ReferenceStorer interface.
60func (r ReferenceStorage) Reference(n plumbing.ReferenceName) (*plumbing.Reference, error) {
61 if _, deleted := r.deleted[n]; deleted {
62 return nil, plumbing.ErrReferenceNotFound
63 }
64
65 ref, err := r.temporal.Reference(n)
66 if err == plumbing.ErrReferenceNotFound {
67 return r.ReferenceStorer.Reference(n)
68 }
69
70 return ref, err
71}
72
73// IterReferences honors the storer.ReferenceStorer interface.
74func (r ReferenceStorage) IterReferences() (storer.ReferenceIter, error) {
75 baseIter, err := r.ReferenceStorer.IterReferences()
76 if err != nil {
77 return nil, err
78 }
79
80 temporalIter, err := r.temporal.IterReferences()
81 if err != nil {
82 return nil, err
83 }
84
85 return storer.NewMultiReferenceIter([]storer.ReferenceIter{
86 baseIter,
87 temporalIter,
88 }), nil
89}
90
91// CountLooseRefs honors the storer.ReferenceStorer interface.
92func (r ReferenceStorage) CountLooseRefs() (int, error) {
93 tc, err := r.temporal.CountLooseRefs()
94 if err != nil {
95 return -1, err
96 }
97
98 bc, err := r.ReferenceStorer.CountLooseRefs()
99 if err != nil {
100 return -1, err
101 }
102
103 return tc + bc, nil
104}
105
106// PackRefs honors the storer.ReferenceStorer interface.
107func (r ReferenceStorage) PackRefs() error {
108 return nil
109}
110
111// RemoveReference honors the storer.ReferenceStorer interface.
112func (r ReferenceStorage) RemoveReference(n plumbing.ReferenceName) error {
113 r.deleted[n] = struct{}{}
114 return r.temporal.RemoveReference(n)
115}
116
117// Commit it copies the reference information of the temporal storage into the
118// base storage.
119func (r ReferenceStorage) Commit() error {
120 for name := range r.deleted {
121 if err := r.ReferenceStorer.RemoveReference(name); err != nil {
122 return err
123 }
124 }
125
126 iter, err := r.temporal.IterReferences()
127 if err != nil {
128 return err
129 }
130
131 return iter.ForEach(func(ref *plumbing.Reference) error {
132 return r.ReferenceStorer.SetReference(ref)
133 })
134}