identity.go

  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}