1package cache
2
3import (
4 "encoding/gob"
5 "fmt"
6 "time"
7
8 "github.com/MichaelMure/git-bug/bug"
9 "github.com/MichaelMure/git-bug/entity"
10 "github.com/MichaelMure/git-bug/identity"
11 "github.com/MichaelMure/git-bug/util/lamport"
12)
13
14// Package initialisation used to register the type for (de)serialization
15func init() {
16 gob.Register(BugExcerpt{})
17}
18
19// BugExcerpt hold a subset of the bug values to be able to sort and filter bugs
20// efficiently without having to read and compile each raw bugs.
21type BugExcerpt struct {
22 Id entity.Id
23
24 CreateLamportTime lamport.Time
25 EditLamportTime lamport.Time
26 CreateUnixTime int64
27 EditUnixTime int64
28
29 Status bug.Status
30 Labels []bug.Label
31 Title string
32 LenComments int
33 Actors []entity.Id
34 Participants []entity.Id
35
36 // If author is identity.Bare, LegacyAuthor is set
37 // If author is identity.Identity, AuthorId is set and data is deported
38 // in a IdentityExcerpt
39 LegacyAuthor LegacyAuthorExcerpt
40 AuthorId entity.Id
41
42 CreateMetadata map[string]string
43}
44
45// identity.Bare data are directly embedded in the bug excerpt
46type LegacyAuthorExcerpt struct {
47 Name string
48 Login string
49}
50
51func (l LegacyAuthorExcerpt) DisplayName() string {
52 switch {
53 case l.Name == "" && l.Login != "":
54 return l.Login
55 case l.Name != "" && l.Login == "":
56 return l.Name
57 case l.Name != "" && l.Login != "":
58 return fmt.Sprintf("%s (%s)", l.Name, l.Login)
59 }
60
61 panic("invalid person data")
62}
63
64func NewBugExcerpt(b bug.Interface, snap *bug.Snapshot) *BugExcerpt {
65 participantsIds := make([]entity.Id, 0, len(snap.Participants))
66 for _, participant := range snap.Participants {
67 if _, ok := participant.(*identity.Identity); ok {
68 participantsIds = append(participantsIds, participant.Id())
69 }
70 }
71
72 actorsIds := make([]entity.Id, 0, len(snap.Actors))
73 for _, actor := range snap.Actors {
74 if _, ok := actor.(*identity.Identity); ok {
75 actorsIds = append(actorsIds, actor.Id())
76 }
77 }
78
79 e := &BugExcerpt{
80 Id: b.Id(),
81 CreateLamportTime: b.CreateLamportTime(),
82 EditLamportTime: b.EditLamportTime(),
83 CreateUnixTime: b.FirstOp().Time().Unix(),
84 EditUnixTime: snap.EditTime().Unix(),
85 Status: snap.Status,
86 Labels: snap.Labels,
87 Actors: actorsIds,
88 Participants: participantsIds,
89 Title: snap.Title,
90 LenComments: len(snap.Comments),
91 CreateMetadata: b.FirstOp().AllMetadata(),
92 }
93
94 switch snap.Author.(type) {
95 case *identity.Identity, *IdentityCache:
96 e.AuthorId = snap.Author.Id()
97 case *identity.Bare:
98 e.LegacyAuthor = LegacyAuthorExcerpt{
99 Login: snap.Author.Login(),
100 Name: snap.Author.Name(),
101 }
102 default:
103 panic("unhandled identity type")
104 }
105
106 return e
107}
108
109func (b *BugExcerpt) CreateTime() time.Time {
110 return time.Unix(b.CreateUnixTime, 0)
111}
112
113func (b *BugExcerpt) EditTime() time.Time {
114 return time.Unix(b.EditUnixTime, 0)
115}
116
117/*
118 * Sorting
119 */
120
121type BugsById []*BugExcerpt
122
123func (b BugsById) Len() int {
124 return len(b)
125}
126
127func (b BugsById) Less(i, j int) bool {
128 return b[i].Id < b[j].Id
129}
130
131func (b BugsById) Swap(i, j int) {
132 b[i], b[j] = b[j], b[i]
133}
134
135type BugsByCreationTime []*BugExcerpt
136
137func (b BugsByCreationTime) Len() int {
138 return len(b)
139}
140
141func (b BugsByCreationTime) Less(i, j int) bool {
142 if b[i].CreateLamportTime < b[j].CreateLamportTime {
143 return true
144 }
145
146 if b[i].CreateLamportTime > b[j].CreateLamportTime {
147 return false
148 }
149
150 // When the logical clocks are identical, that means we had a concurrent
151 // edition. In this case we rely on the timestamp. While the timestamp might
152 // be incorrect due to a badly set clock, the drift in sorting is bounded
153 // by the first sorting using the logical clock. That means that if users
154 // synchronize their bugs regularly, the timestamp will rarely be used, and
155 // should still provide a kinda accurate sorting when needed.
156 return b[i].CreateUnixTime < b[j].CreateUnixTime
157}
158
159func (b BugsByCreationTime) Swap(i, j int) {
160 b[i], b[j] = b[j], b[i]
161}
162
163type BugsByEditTime []*BugExcerpt
164
165func (b BugsByEditTime) Len() int {
166 return len(b)
167}
168
169func (b BugsByEditTime) Less(i, j int) bool {
170 if b[i].EditLamportTime < b[j].EditLamportTime {
171 return true
172 }
173
174 if b[i].EditLamportTime > b[j].EditLamportTime {
175 return false
176 }
177
178 // When the logical clocks are identical, that means we had a concurrent
179 // edition. In this case we rely on the timestamp. While the timestamp might
180 // be incorrect due to a badly set clock, the drift in sorting is bounded
181 // by the first sorting using the logical clock. That means that if users
182 // synchronize their bugs regularly, the timestamp will rarely be used, and
183 // should still provide a kinda accurate sorting when needed.
184 return b[i].EditUnixTime < b[j].EditUnixTime
185}
186
187func (b BugsByEditTime) Swap(i, j int) {
188 b[i], b[j] = b[j], b[i]
189}