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