1package identity
2
3import (
4 "crypto/sha256"
5 "encoding/json"
6 "fmt"
7 "strings"
8
9 "github.com/MichaelMure/git-bug/entity"
10 "github.com/MichaelMure/git-bug/repository"
11 "github.com/MichaelMure/git-bug/util/lamport"
12 "github.com/MichaelMure/git-bug/util/text"
13 "github.com/MichaelMure/git-bug/util/timestamp"
14)
15
16var _ Interface = &Bare{}
17var _ entity.Interface = &Bare{}
18
19// Bare is a very minimal identity, designed to be fully embedded directly along
20// other data.
21//
22// in particular, this identity is designed to be compatible with the handling of
23// identities in the early version of git-bug.
24// Deprecated: legacy identity for compat, might make sense to ditch entirely for
25// simplicity but that would be a breaking change.
26type Bare struct {
27 id entity.Id
28 name string
29 email string
30 login string
31 avatarUrl string
32}
33
34func NewBare(name string, email string) *Bare {
35 return &Bare{id: entity.UnsetId, name: name, email: email}
36}
37
38func NewBareFull(name string, email string, login string, avatarUrl string) *Bare {
39 return &Bare{id: entity.UnsetId, name: name, email: email, login: login, avatarUrl: avatarUrl}
40}
41
42func deriveId(data []byte) entity.Id {
43 sum := sha256.Sum256(data)
44 return entity.Id(fmt.Sprintf("%x", sum))
45}
46
47type bareIdentityJSON struct {
48 Name string `json:"name,omitempty"`
49 Email string `json:"email,omitempty"`
50 Login string `json:"login,omitempty"`
51 AvatarUrl string `json:"avatar_url,omitempty"`
52}
53
54func (i *Bare) MarshalJSON() ([]byte, error) {
55 return json.Marshal(bareIdentityJSON{
56 Name: i.name,
57 Email: i.email,
58 Login: i.login,
59 AvatarUrl: i.avatarUrl,
60 })
61}
62
63func (i *Bare) UnmarshalJSON(data []byte) error {
64 // Compute the Id when loading the op from disk.
65 i.id = deriveId(data)
66
67 aux := bareIdentityJSON{}
68
69 if err := json.Unmarshal(data, &aux); err != nil {
70 return err
71 }
72
73 i.name = aux.Name
74 i.email = aux.Email
75 i.login = aux.Login
76 i.avatarUrl = aux.AvatarUrl
77
78 return nil
79}
80
81// Id return the Identity identifier
82func (i *Bare) Id() entity.Id {
83 // We don't have a proper Id at hand, so let's hash all the data to get one.
84
85 if i.id == "" {
86 // something went really wrong
87 panic("identity's id not set")
88 }
89 if i.id == entity.UnsetId {
90 // This means we are trying to get the identity identifier *before* it has been stored
91 // As the Id is computed based on the actual bytes written on the disk, we are going to predict
92 // those and then get the Id. This is safe as it will be the exact same code writing on disk later.
93
94 data, err := json.Marshal(i)
95 if err != nil {
96 panic(err)
97 }
98
99 i.id = deriveId(data)
100 }
101 return i.id
102}
103
104// Name return the last version of the name
105func (i *Bare) Name() string {
106 return i.name
107}
108
109// Email return the last version of the email
110func (i *Bare) Email() string {
111 return i.email
112}
113
114// Login return the last version of the login
115func (i *Bare) Login() string {
116 return i.login
117}
118
119// AvatarUrl return the last version of the Avatar URL
120func (i *Bare) AvatarUrl() string {
121 return i.avatarUrl
122}
123
124// Keys return the last version of the valid keys
125func (i *Bare) Keys() []*Key {
126 return nil
127}
128
129// ValidKeysAtTime return the set of keys valid at a given lamport time
130func (i *Bare) ValidKeysAtTime(_ lamport.Time) []*Key {
131 return nil
132}
133
134// DisplayName return a non-empty string to display, representing the
135// identity, based on the non-empty values.
136func (i *Bare) DisplayName() string {
137 switch {
138 case i.name == "" && i.login != "":
139 return i.login
140 case i.name != "" && i.login == "":
141 return i.name
142 case i.name != "" && i.login != "":
143 return fmt.Sprintf("%s (%s)", i.name, i.login)
144 }
145
146 panic("invalid person data")
147}
148
149// Validate check if the Identity data is valid
150func (i *Bare) Validate() error {
151 if text.Empty(i.name) && text.Empty(i.login) {
152 return fmt.Errorf("either name or login should be set")
153 }
154
155 if strings.Contains(i.name, "\n") {
156 return fmt.Errorf("name should be a single line")
157 }
158
159 if !text.Safe(i.name) {
160 return fmt.Errorf("name is not fully printable")
161 }
162
163 if strings.Contains(i.login, "\n") {
164 return fmt.Errorf("login should be a single line")
165 }
166
167 if !text.Safe(i.login) {
168 return fmt.Errorf("login is not fully printable")
169 }
170
171 if strings.Contains(i.email, "\n") {
172 return fmt.Errorf("email should be a single line")
173 }
174
175 if !text.Safe(i.email) {
176 return fmt.Errorf("email is not fully printable")
177 }
178
179 if i.avatarUrl != "" && !text.ValidUrl(i.avatarUrl) {
180 return fmt.Errorf("avatarUrl is not a valid URL")
181 }
182
183 return nil
184}
185
186// Write the identity into the Repository. In particular, this ensure that
187// the Id is properly set.
188func (i *Bare) CommitWithRepo(repo repository.ClockedRepo) error {
189 // Nothing to do, everything is directly embedded
190 return nil
191}
192
193// If needed, write the identity into the Repository. In particular, this
194// ensure that the Id is properly set.
195func (i *Bare) CommitAsNeededWithRepo(repo repository.ClockedRepo) error {
196 // Nothing to do, everything is directly embedded
197 return nil
198}
199
200// IsProtected return true if the chain of git commits started to be signed.
201// If that's the case, only signed commit with a valid key for this identity can be added.
202func (i *Bare) IsProtected() bool {
203 return false
204}
205
206// LastModificationLamportTime return the Lamport time at which the last version of the identity became valid.
207func (i *Bare) LastModificationLamport() lamport.Time {
208 return 0
209}
210
211// LastModification return the timestamp at which the last version of the identity became valid.
212func (i *Bare) LastModification() timestamp.Timestamp {
213 return 0
214}
215
216func (i *Bare) NeedCommit() bool {
217 return false
218}