1// Package identity contains the identity data model and low-level related functions
2package identity
3
4import (
5 "fmt"
6 "strings"
7
8 "github.com/MichaelMure/git-bug/repository"
9 "github.com/MichaelMure/git-bug/util/git"
10 "github.com/MichaelMure/git-bug/util/lamport"
11)
12
13const identityRefPattern = "refs/identities/"
14const versionEntryName = "version"
15const identityConfigKey = "git-bug.identity"
16
17type Identity struct {
18 id string
19 Versions []Version
20}
21
22func NewIdentity(name string, email string) (*Identity, error) {
23 return &Identity{
24 Versions: []Version{
25 {
26 Name: name,
27 Email: email,
28 Nonce: makeNonce(20),
29 },
30 },
31 }, nil
32}
33
34type identityJson struct {
35 Id string `json:"id"`
36}
37
38// TODO: marshal/unmarshal identity + load/write from OpBase
39
40func Read(repo repository.Repo, id string) (*Identity, error) {
41 // Todo
42 return &Identity{}, nil
43}
44
45// NewFromGitUser will query the repository for user detail and
46// build the corresponding Identity
47/*func NewFromGitUser(repo repository.Repo) (*Identity, error) {
48 name, err := repo.GetUserName()
49 if err != nil {
50 return nil, err
51 }
52 if name == "" {
53 return nil, errors.New("User name is not configured in git yet. Please use `git config --global user.name \"John Doe\"`")
54 }
55
56 email, err := repo.GetUserEmail()
57 if err != nil {
58 return nil, err
59 }
60 if email == "" {
61 return nil, errors.New("User name is not configured in git yet. Please use `git config --global user.email johndoe@example.com`")
62 }
63
64 return NewIdentity(name, email)
65}*/
66
67//
68func BuildFromGit(repo repository.Repo) *Identity {
69 version := Version{}
70
71 name, err := repo.GetUserName()
72 if err == nil {
73 version.Name = name
74 }
75
76 email, err := repo.GetUserEmail()
77 if err == nil {
78 version.Email = email
79 }
80
81 return &Identity{
82 Versions: []Version{
83 version,
84 },
85 }
86}
87
88// SetIdentity store the user identity's id in the git config
89func SetIdentity(repo repository.RepoCommon, identity Identity) error {
90 return repo.StoreConfig(identityConfigKey, identity.Id())
91}
92
93// GetIdentity read the current user identity, set with a git config entry
94func GetIdentity(repo repository.Repo) (*Identity, error) {
95 configs, err := repo.ReadConfigs(identityConfigKey)
96 if err != nil {
97 return nil, err
98 }
99
100 if len(configs) == 0 {
101 return nil, fmt.Errorf("no identity set")
102 }
103
104 if len(configs) > 1 {
105 return nil, fmt.Errorf("multiple identity config exist")
106 }
107
108 var id string
109 for _, val := range configs {
110 id = val
111 }
112
113 return Read(repo, id)
114}
115
116func (i *Identity) AddVersion(version Version) {
117 i.Versions = append(i.Versions, version)
118}
119
120func (i *Identity) Commit(repo repository.ClockedRepo) error {
121 // Todo: check for mismatch between memory and commited data
122
123 var lastCommit git.Hash = ""
124
125 for _, v := range i.Versions {
126 if v.commitHash != "" {
127 lastCommit = v.commitHash
128 // ignore already commited versions
129 continue
130 }
131
132 blobHash, err := v.Write(repo)
133 if err != nil {
134 return err
135 }
136
137 // Make a git tree referencing the blob
138 tree := []repository.TreeEntry{
139 {ObjectType: repository.Blob, Hash: blobHash, Name: versionEntryName},
140 }
141
142 treeHash, err := repo.StoreTree(tree)
143 if err != nil {
144 return err
145 }
146
147 var commitHash git.Hash
148 if lastCommit != "" {
149 commitHash, err = repo.StoreCommitWithParent(treeHash, lastCommit)
150 } else {
151 commitHash, err = repo.StoreCommit(treeHash)
152 }
153
154 if err != nil {
155 return err
156 }
157
158 lastCommit = commitHash
159
160 // if it was the first commit, use the commit hash as the Identity id
161 if i.id == "" {
162 i.id = string(commitHash)
163 }
164 }
165
166 if i.id == "" {
167 panic("identity with no id")
168 }
169
170 ref := fmt.Sprintf("%s%s", identityRefPattern, i.id)
171 err := repo.UpdateRef(ref, lastCommit)
172
173 if err != nil {
174 return err
175 }
176
177 return nil
178}
179
180// Validate check if the Identity data is valid
181func (i *Identity) Validate() error {
182 lastTime := lamport.Time(0)
183
184 for _, v := range i.Versions {
185 if err := v.Validate(); err != nil {
186 return err
187 }
188
189 if v.Time < lastTime {
190 return fmt.Errorf("non-chronological version (%d --> %d)", lastTime, v.Time)
191 }
192
193 lastTime = v.Time
194 }
195
196 return nil
197}
198
199func (i *Identity) LastVersion() Version {
200 if len(i.Versions) <= 0 {
201 panic("no version at all")
202 }
203
204 return i.Versions[len(i.Versions)-1]
205}
206
207// Id return the Identity identifier
208func (i *Identity) Id() string {
209 if i.id == "" {
210 // simply panic as it would be a coding error
211 // (using an id of an identity not stored yet)
212 panic("no id yet")
213 }
214 return i.id
215}
216
217// Name return the last version of the name
218func (i *Identity) Name() string {
219 return i.LastVersion().Name
220}
221
222// Email return the last version of the email
223func (i *Identity) Email() string {
224 return i.LastVersion().Email
225}
226
227// Login return the last version of the login
228func (i *Identity) Login() string {
229 return i.LastVersion().Login
230}
231
232// Login return the last version of the Avatar URL
233func (i *Identity) AvatarUrl() string {
234 return i.LastVersion().AvatarUrl
235}
236
237// Login return the last version of the valid keys
238func (i *Identity) Keys() []Key {
239 return i.LastVersion().Keys
240}
241
242// IsProtected return true if the chain of git commits started to be signed.
243// If that's the case, only signed commit with a valid key for this identity can be added.
244func (i *Identity) IsProtected() bool {
245 // Todo
246 return false
247}
248
249// ValidKeysAtTime return the set of keys valid at a given lamport time
250func (i *Identity) ValidKeysAtTime(time lamport.Time) []Key {
251 var result []Key
252
253 for _, v := range i.Versions {
254 if v.Time > time {
255 return result
256 }
257
258 result = v.Keys
259 }
260
261 return result
262}
263
264// Match tell is the Identity match the given query string
265func (i *Identity) Match(query string) bool {
266 query = strings.ToLower(query)
267
268 return strings.Contains(strings.ToLower(i.Name()), query) ||
269 strings.Contains(strings.ToLower(i.Login()), query)
270}
271
272// DisplayName return a non-empty string to display, representing the
273// identity, based on the non-empty values.
274func (i *Identity) DisplayName() string {
275 switch {
276 case i.Name() == "" && i.Login() != "":
277 return i.Login()
278 case i.Name() != "" && i.Login() == "":
279 return i.Name()
280 case i.Name() != "" && i.Login() != "":
281 return fmt.Sprintf("%s (%s)", i.Name(), i.Login())
282 }
283
284 panic("invalid person data")
285}