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