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