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