repo_cache.go

   1package cache
   2
   3import (
   4	"bytes"
   5	"encoding/gob"
   6	"fmt"
   7	"io"
   8	"io/ioutil"
   9	"os"
  10	"path"
  11	"sort"
  12	"strconv"
  13	"sync"
  14	"time"
  15
  16	"github.com/pkg/errors"
  17
  18	"github.com/MichaelMure/git-bug/bug"
  19	"github.com/MichaelMure/git-bug/entity"
  20	"github.com/MichaelMure/git-bug/identity"
  21	"github.com/MichaelMure/git-bug/query"
  22	"github.com/MichaelMure/git-bug/repository"
  23	"github.com/MichaelMure/git-bug/util/git"
  24	"github.com/MichaelMure/git-bug/util/process"
  25)
  26
  27const bugCacheFile = "bug-cache"
  28const identityCacheFile = "identity-cache"
  29
  30// 1: original format
  31// 2: added cache for identities with a reference in the bug cache
  32// 3: CreateUnixTime --> createUnixTime, EditUnixTime --> editUnixTime
  33const formatVersion = 3
  34
  35var _ repository.RepoCommon = &RepoCache{}
  36
  37// RepoCache is a cache for a Repository. This cache has multiple functions:
  38//
  39// 1. After being loaded, a Bug is kept in memory in the cache, allowing for fast
  40// 		access later.
  41// 2. The cache maintain in memory and on disk a pre-digested excerpt for each bug,
  42// 		allowing for fast querying the whole set of bugs without having to load
  43//		them individually.
  44// 3. The cache guarantee that a single instance of a Bug is loaded at once, avoiding
  45// 		loss of data that we could have with multiple copies in the same process.
  46// 4. The same way, the cache maintain in memory a single copy of the loaded identities.
  47//
  48// The cache also protect the on-disk data by locking the git repository for its
  49// own usage, by writing a lock file. Of course, normal git operations are not
  50// affected, only git-bug related one.
  51type RepoCache struct {
  52	// the underlying repo
  53	repo repository.ClockedRepo
  54
  55	// the name of the repository, as defined in the MultiRepoCache
  56	name string
  57
  58	muBug sync.RWMutex
  59	// excerpt of bugs data for all bugs
  60	bugExcerpts map[entity.Id]*BugExcerpt
  61	// bug loaded in memory
  62	bugs map[entity.Id]*BugCache
  63
  64	muIdentity sync.RWMutex
  65	// excerpt of identities data for all identities
  66	identitiesExcerpts map[entity.Id]*IdentityExcerpt
  67	// identities loaded in memory
  68	identities map[entity.Id]*IdentityCache
  69
  70	// the user identity's id, if known
  71	userIdentityId entity.Id
  72}
  73
  74func NewRepoCache(r repository.ClockedRepo) (*RepoCache, error) {
  75	return NewNamedRepoCache(r, "")
  76}
  77
  78func NewNamedRepoCache(r repository.ClockedRepo, name string) (*RepoCache, error) {
  79	c := &RepoCache{
  80		repo:       r,
  81		name:       name,
  82		bugs:       make(map[entity.Id]*BugCache),
  83		identities: make(map[entity.Id]*IdentityCache),
  84	}
  85
  86	err := c.lock()
  87	if err != nil {
  88		return &RepoCache{}, err
  89	}
  90
  91	err = c.load()
  92	if err == nil {
  93		return c, nil
  94	}
  95
  96	// Cache is either missing, broken or outdated. Rebuilding.
  97	err = c.buildCache()
  98	if err != nil {
  99		return nil, err
 100	}
 101
 102	return c, c.write()
 103}
 104
 105func (c *RepoCache) Name() string {
 106	return c.name
 107}
 108
 109// LocalConfig give access to the repository scoped configuration
 110func (c *RepoCache) LocalConfig() repository.Config {
 111	return c.repo.LocalConfig()
 112}
 113
 114// GlobalConfig give access to the git global configuration
 115func (c *RepoCache) GlobalConfig() repository.Config {
 116	return c.repo.GlobalConfig()
 117}
 118
 119// GetPath returns the path to the repo.
 120func (c *RepoCache) GetPath() string {
 121	return c.repo.GetPath()
 122}
 123
 124// GetCoreEditor returns the name of the editor that the user has used to configure git.
 125func (c *RepoCache) GetCoreEditor() (string, error) {
 126	return c.repo.GetCoreEditor()
 127}
 128
 129// GetRemotes returns the configured remotes repositories.
 130func (c *RepoCache) GetRemotes() (map[string]string, error) {
 131	return c.repo.GetRemotes()
 132}
 133
 134// GetUserName returns the name the the user has used to configure git
 135func (c *RepoCache) GetUserName() (string, error) {
 136	return c.repo.GetUserName()
 137}
 138
 139// GetUserEmail returns the email address that the user has used to configure git.
 140func (c *RepoCache) GetUserEmail() (string, error) {
 141	return c.repo.GetUserEmail()
 142}
 143
 144func (c *RepoCache) lock() error {
 145	lockPath := repoLockFilePath(c.repo)
 146
 147	err := repoIsAvailable(c.repo)
 148	if err != nil {
 149		return err
 150	}
 151
 152	f, err := os.Create(lockPath)
 153	if err != nil {
 154		return err
 155	}
 156
 157	pid := fmt.Sprintf("%d", os.Getpid())
 158	_, err = f.WriteString(pid)
 159	if err != nil {
 160		return err
 161	}
 162
 163	return f.Close()
 164}
 165
 166func (c *RepoCache) Close() error {
 167	c.muBug.Lock()
 168	defer c.muBug.Unlock()
 169	c.muIdentity.Lock()
 170	defer c.muIdentity.Unlock()
 171
 172	c.identities = make(map[entity.Id]*IdentityCache)
 173	c.identitiesExcerpts = nil
 174	c.bugs = make(map[entity.Id]*BugCache)
 175	c.bugExcerpts = nil
 176
 177	lockPath := repoLockFilePath(c.repo)
 178	return os.Remove(lockPath)
 179}
 180
 181// bugUpdated is a callback to trigger when the excerpt of a bug changed,
 182// that is each time a bug is updated
 183func (c *RepoCache) bugUpdated(id entity.Id) error {
 184	c.muBug.Lock()
 185
 186	b, ok := c.bugs[id]
 187	if !ok {
 188		c.muBug.Unlock()
 189		panic("missing bug in the cache")
 190	}
 191
 192	c.bugExcerpts[id] = NewBugExcerpt(b.bug, b.Snapshot())
 193	c.muBug.Unlock()
 194
 195	// we only need to write the bug cache
 196	return c.writeBugCache()
 197}
 198
 199// identityUpdated is a callback to trigger when the excerpt of an identity
 200// changed, that is each time an identity is updated
 201func (c *RepoCache) identityUpdated(id entity.Id) error {
 202	c.muIdentity.Lock()
 203
 204	i, ok := c.identities[id]
 205	if !ok {
 206		c.muIdentity.Unlock()
 207		panic("missing identity in the cache")
 208	}
 209
 210	c.identitiesExcerpts[id] = NewIdentityExcerpt(i.Identity)
 211	c.muIdentity.Unlock()
 212
 213	// we only need to write the identity cache
 214	return c.writeIdentityCache()
 215}
 216
 217// load will try to read from the disk all the cache files
 218func (c *RepoCache) load() error {
 219	err := c.loadBugCache()
 220	if err != nil {
 221		return err
 222	}
 223	return c.loadIdentityCache()
 224}
 225
 226// load will try to read from the disk the bug cache file
 227func (c *RepoCache) loadBugCache() error {
 228	c.muBug.Lock()
 229	defer c.muBug.Unlock()
 230
 231	f, err := os.Open(bugCacheFilePath(c.repo))
 232	if err != nil {
 233		return err
 234	}
 235
 236	decoder := gob.NewDecoder(f)
 237
 238	aux := struct {
 239		Version  uint
 240		Excerpts map[entity.Id]*BugExcerpt
 241	}{}
 242
 243	err = decoder.Decode(&aux)
 244	if err != nil {
 245		return err
 246	}
 247
 248	if aux.Version != formatVersion {
 249		return fmt.Errorf("unknown cache format version %v", aux.Version)
 250	}
 251
 252	c.bugExcerpts = aux.Excerpts
 253	return nil
 254}
 255
 256// load will try to read from the disk the identity cache file
 257func (c *RepoCache) loadIdentityCache() error {
 258	c.muIdentity.Lock()
 259	defer c.muIdentity.Unlock()
 260
 261	f, err := os.Open(identityCacheFilePath(c.repo))
 262	if err != nil {
 263		return err
 264	}
 265
 266	decoder := gob.NewDecoder(f)
 267
 268	aux := struct {
 269		Version  uint
 270		Excerpts map[entity.Id]*IdentityExcerpt
 271	}{}
 272
 273	err = decoder.Decode(&aux)
 274	if err != nil {
 275		return err
 276	}
 277
 278	if aux.Version != formatVersion {
 279		return fmt.Errorf("unknown cache format version %v", aux.Version)
 280	}
 281
 282	c.identitiesExcerpts = aux.Excerpts
 283	return nil
 284}
 285
 286// write will serialize on disk all the cache files
 287func (c *RepoCache) write() error {
 288	err := c.writeBugCache()
 289	if err != nil {
 290		return err
 291	}
 292	return c.writeIdentityCache()
 293}
 294
 295// write will serialize on disk the bug cache file
 296func (c *RepoCache) writeBugCache() error {
 297	c.muBug.RLock()
 298	defer c.muBug.RUnlock()
 299
 300	var data bytes.Buffer
 301
 302	aux := struct {
 303		Version  uint
 304		Excerpts map[entity.Id]*BugExcerpt
 305	}{
 306		Version:  formatVersion,
 307		Excerpts: c.bugExcerpts,
 308	}
 309
 310	encoder := gob.NewEncoder(&data)
 311
 312	err := encoder.Encode(aux)
 313	if err != nil {
 314		return err
 315	}
 316
 317	f, err := os.Create(bugCacheFilePath(c.repo))
 318	if err != nil {
 319		return err
 320	}
 321
 322	_, err = f.Write(data.Bytes())
 323	if err != nil {
 324		return err
 325	}
 326
 327	return f.Close()
 328}
 329
 330// write will serialize on disk the identity cache file
 331func (c *RepoCache) writeIdentityCache() error {
 332	c.muIdentity.RLock()
 333	defer c.muIdentity.RUnlock()
 334
 335	var data bytes.Buffer
 336
 337	aux := struct {
 338		Version  uint
 339		Excerpts map[entity.Id]*IdentityExcerpt
 340	}{
 341		Version:  formatVersion,
 342		Excerpts: c.identitiesExcerpts,
 343	}
 344
 345	encoder := gob.NewEncoder(&data)
 346
 347	err := encoder.Encode(aux)
 348	if err != nil {
 349		return err
 350	}
 351
 352	f, err := os.Create(identityCacheFilePath(c.repo))
 353	if err != nil {
 354		return err
 355	}
 356
 357	_, err = f.Write(data.Bytes())
 358	if err != nil {
 359		return err
 360	}
 361
 362	return f.Close()
 363}
 364
 365func bugCacheFilePath(repo repository.Repo) string {
 366	return path.Join(repo.GetPath(), "git-bug", bugCacheFile)
 367}
 368
 369func identityCacheFilePath(repo repository.Repo) string {
 370	return path.Join(repo.GetPath(), "git-bug", identityCacheFile)
 371}
 372
 373func (c *RepoCache) buildCache() error {
 374	c.muBug.Lock()
 375	defer c.muBug.Unlock()
 376	c.muIdentity.Lock()
 377	defer c.muIdentity.Unlock()
 378
 379	_, _ = fmt.Fprintf(os.Stderr, "Building identity cache... ")
 380
 381	c.identitiesExcerpts = make(map[entity.Id]*IdentityExcerpt)
 382
 383	allIdentities := identity.ReadAllLocalIdentities(c.repo)
 384
 385	for i := range allIdentities {
 386		if i.Err != nil {
 387			return i.Err
 388		}
 389
 390		c.identitiesExcerpts[i.Identity.Id()] = NewIdentityExcerpt(i.Identity)
 391	}
 392
 393	_, _ = fmt.Fprintln(os.Stderr, "Done.")
 394
 395	_, _ = fmt.Fprintf(os.Stderr, "Building bug cache... ")
 396
 397	c.bugExcerpts = make(map[entity.Id]*BugExcerpt)
 398
 399	allBugs := bug.ReadAllLocalBugs(c.repo)
 400
 401	for b := range allBugs {
 402		if b.Err != nil {
 403			return b.Err
 404		}
 405
 406		snap := b.Bug.Compile()
 407		c.bugExcerpts[b.Bug.Id()] = NewBugExcerpt(b.Bug, &snap)
 408	}
 409
 410	_, _ = fmt.Fprintln(os.Stderr, "Done.")
 411	return nil
 412}
 413
 414// ResolveBugExcerpt retrieve a BugExcerpt matching the exact given id
 415func (c *RepoCache) ResolveBugExcerpt(id entity.Id) (*BugExcerpt, error) {
 416	c.muBug.RLock()
 417	defer c.muBug.RUnlock()
 418
 419	e, ok := c.bugExcerpts[id]
 420	if !ok {
 421		return nil, bug.ErrBugNotExist
 422	}
 423
 424	return e, nil
 425}
 426
 427// ResolveBug retrieve a bug matching the exact given id
 428func (c *RepoCache) ResolveBug(id entity.Id) (*BugCache, error) {
 429	c.muBug.RLock()
 430	cached, ok := c.bugs[id]
 431	c.muBug.RUnlock()
 432	if ok {
 433		return cached, nil
 434	}
 435
 436	b, err := bug.ReadLocalBug(c.repo, id)
 437	if err != nil {
 438		return nil, err
 439	}
 440
 441	cached = NewBugCache(c, b)
 442
 443	c.muBug.Lock()
 444	c.bugs[id] = cached
 445	c.muBug.Unlock()
 446
 447	return cached, nil
 448}
 449
 450// ResolveBugExcerptPrefix retrieve a BugExcerpt matching an id prefix. It fails if multiple
 451// bugs match.
 452func (c *RepoCache) ResolveBugExcerptPrefix(prefix string) (*BugExcerpt, error) {
 453	return c.ResolveBugExcerptMatcher(func(excerpt *BugExcerpt) bool {
 454		return excerpt.Id.HasPrefix(prefix)
 455	})
 456}
 457
 458// ResolveBugPrefix retrieve a bug matching an id prefix. It fails if multiple
 459// bugs match.
 460func (c *RepoCache) ResolveBugPrefix(prefix string) (*BugCache, error) {
 461	return c.ResolveBugMatcher(func(excerpt *BugExcerpt) bool {
 462		return excerpt.Id.HasPrefix(prefix)
 463	})
 464}
 465
 466// ResolveBugCreateMetadata retrieve a bug that has the exact given metadata on
 467// its Create operation, that is, the first operation. It fails if multiple bugs
 468// match.
 469func (c *RepoCache) ResolveBugCreateMetadata(key string, value string) (*BugCache, error) {
 470	return c.ResolveBugMatcher(func(excerpt *BugExcerpt) bool {
 471		return excerpt.CreateMetadata[key] == value
 472	})
 473}
 474
 475func (c *RepoCache) ResolveBugExcerptMatcher(f func(*BugExcerpt) bool) (*BugExcerpt, error) {
 476	id, err := c.resolveBugMatcher(f)
 477	if err != nil {
 478		return nil, err
 479	}
 480	return c.ResolveBugExcerpt(id)
 481}
 482
 483func (c *RepoCache) ResolveBugMatcher(f func(*BugExcerpt) bool) (*BugCache, error) {
 484	id, err := c.resolveBugMatcher(f)
 485	if err != nil {
 486		return nil, err
 487	}
 488	return c.ResolveBug(id)
 489}
 490
 491func (c *RepoCache) resolveBugMatcher(f func(*BugExcerpt) bool) (entity.Id, error) {
 492	c.muBug.RLock()
 493	defer c.muBug.RUnlock()
 494
 495	// preallocate but empty
 496	matching := make([]entity.Id, 0, 5)
 497
 498	for _, excerpt := range c.bugExcerpts {
 499		if f(excerpt) {
 500			matching = append(matching, excerpt.Id)
 501		}
 502	}
 503
 504	if len(matching) > 1 {
 505		return entity.UnsetId, bug.NewErrMultipleMatchBug(matching)
 506	}
 507
 508	if len(matching) == 0 {
 509		return entity.UnsetId, bug.ErrBugNotExist
 510	}
 511
 512	return matching[0], nil
 513}
 514
 515// QueryBugs return the id of all Bug matching the given Query
 516func (c *RepoCache) QueryBugs(q *query.Query) []entity.Id {
 517	c.muBug.RLock()
 518	defer c.muBug.RUnlock()
 519
 520	if q == nil {
 521		return c.AllBugsIds()
 522	}
 523
 524	matcher := compileMatcher(q.Filters)
 525
 526	var filtered []*BugExcerpt
 527
 528	for _, excerpt := range c.bugExcerpts {
 529		if matcher.Match(excerpt, c) {
 530			filtered = append(filtered, excerpt)
 531		}
 532	}
 533
 534	var sorter sort.Interface
 535
 536	switch q.OrderBy {
 537	case query.OrderById:
 538		sorter = BugsById(filtered)
 539	case query.OrderByCreation:
 540		sorter = BugsByCreationTime(filtered)
 541	case query.OrderByEdit:
 542		sorter = BugsByEditTime(filtered)
 543	default:
 544		panic("missing sort type")
 545	}
 546
 547	switch q.OrderDirection {
 548	case query.OrderAscending:
 549		// Nothing to do
 550	case query.OrderDescending:
 551		sorter = sort.Reverse(sorter)
 552	default:
 553		panic("missing sort direction")
 554	}
 555
 556	sort.Sort(sorter)
 557
 558	result := make([]entity.Id, len(filtered))
 559
 560	for i, val := range filtered {
 561		result[i] = val.Id
 562	}
 563
 564	return result
 565}
 566
 567// AllBugsIds return all known bug ids
 568func (c *RepoCache) AllBugsIds() []entity.Id {
 569	c.muBug.RLock()
 570	defer c.muBug.RUnlock()
 571
 572	result := make([]entity.Id, len(c.bugExcerpts))
 573
 574	i := 0
 575	for _, excerpt := range c.bugExcerpts {
 576		result[i] = excerpt.Id
 577		i++
 578	}
 579
 580	return result
 581}
 582
 583// ValidLabels list valid labels
 584//
 585// Note: in the future, a proper label policy could be implemented where valid
 586// labels are defined in a configuration file. Until that, the default behavior
 587// is to return the list of labels already used.
 588func (c *RepoCache) ValidLabels() []bug.Label {
 589	c.muBug.RLock()
 590	defer c.muBug.RUnlock()
 591
 592	set := map[bug.Label]interface{}{}
 593
 594	for _, excerpt := range c.bugExcerpts {
 595		for _, l := range excerpt.Labels {
 596			set[l] = nil
 597		}
 598	}
 599
 600	result := make([]bug.Label, len(set))
 601
 602	i := 0
 603	for l := range set {
 604		result[i] = l
 605		i++
 606	}
 607
 608	// Sort
 609	sort.Slice(result, func(i, j int) bool {
 610		return string(result[i]) < string(result[j])
 611	})
 612
 613	return result
 614}
 615
 616// NewBug create a new bug
 617// The new bug is written in the repository (commit)
 618func (c *RepoCache) NewBug(title string, message string) (*BugCache, *bug.CreateOperation, error) {
 619	return c.NewBugWithFiles(title, message, nil)
 620}
 621
 622// NewBugWithFiles create a new bug with attached files for the message
 623// The new bug is written in the repository (commit)
 624func (c *RepoCache) NewBugWithFiles(title string, message string, files []git.Hash) (*BugCache, *bug.CreateOperation, error) {
 625	author, err := c.GetUserIdentity()
 626	if err != nil {
 627		return nil, nil, err
 628	}
 629
 630	return c.NewBugRaw(author, time.Now().Unix(), title, message, files, nil)
 631}
 632
 633// NewBugWithFilesMeta create a new bug with attached files for the message, as
 634// well as metadata for the Create operation.
 635// The new bug is written in the repository (commit)
 636func (c *RepoCache) NewBugRaw(author *IdentityCache, unixTime int64, title string, message string, files []git.Hash, metadata map[string]string) (*BugCache, *bug.CreateOperation, error) {
 637	b, op, err := bug.CreateWithFiles(author.Identity, unixTime, title, message, files)
 638	if err != nil {
 639		return nil, nil, err
 640	}
 641
 642	for key, value := range metadata {
 643		op.SetMetadata(key, value)
 644	}
 645
 646	err = b.Commit(c.repo)
 647	if err != nil {
 648		return nil, nil, err
 649	}
 650
 651	c.muBug.Lock()
 652	if _, has := c.bugs[b.Id()]; has {
 653		c.muBug.Unlock()
 654		return nil, nil, fmt.Errorf("bug %s already exist in the cache", b.Id())
 655	}
 656
 657	cached := NewBugCache(c, b)
 658	c.bugs[b.Id()] = cached
 659	c.muBug.Unlock()
 660
 661	// force the write of the excerpt
 662	err = c.bugUpdated(b.Id())
 663	if err != nil {
 664		return nil, nil, err
 665	}
 666
 667	return cached, op, nil
 668}
 669
 670// Fetch retrieve updates from a remote
 671// This does not change the local bugs or identities state
 672func (c *RepoCache) Fetch(remote string) (string, error) {
 673	stdout1, err := identity.Fetch(c.repo, remote)
 674	if err != nil {
 675		return stdout1, err
 676	}
 677
 678	stdout2, err := bug.Fetch(c.repo, remote)
 679	if err != nil {
 680		return stdout2, err
 681	}
 682
 683	return stdout1 + stdout2, nil
 684}
 685
 686// MergeAll will merge all the available remote bug and identities
 687func (c *RepoCache) MergeAll(remote string) <-chan entity.MergeResult {
 688	out := make(chan entity.MergeResult)
 689
 690	// Intercept merge results to update the cache properly
 691	go func() {
 692		defer close(out)
 693
 694		results := identity.MergeAll(c.repo, remote)
 695		for result := range results {
 696			out <- result
 697
 698			if result.Err != nil {
 699				continue
 700			}
 701
 702			switch result.Status {
 703			case entity.MergeStatusNew, entity.MergeStatusUpdated:
 704				i := result.Entity.(*identity.Identity)
 705				c.muIdentity.Lock()
 706				c.identitiesExcerpts[result.Id] = NewIdentityExcerpt(i)
 707				c.muIdentity.Unlock()
 708			}
 709		}
 710
 711		results = bug.MergeAll(c.repo, remote)
 712		for result := range results {
 713			out <- result
 714
 715			if result.Err != nil {
 716				continue
 717			}
 718
 719			switch result.Status {
 720			case entity.MergeStatusNew, entity.MergeStatusUpdated:
 721				b := result.Entity.(*bug.Bug)
 722				snap := b.Compile()
 723				c.muBug.Lock()
 724				c.bugExcerpts[result.Id] = NewBugExcerpt(b, &snap)
 725				c.muBug.Unlock()
 726			}
 727		}
 728
 729		err := c.write()
 730
 731		// No easy way out here ..
 732		if err != nil {
 733			panic(err)
 734		}
 735	}()
 736
 737	return out
 738}
 739
 740// Push update a remote with the local changes
 741func (c *RepoCache) Push(remote string) (string, error) {
 742	stdout1, err := identity.Push(c.repo, remote)
 743	if err != nil {
 744		return stdout1, err
 745	}
 746
 747	stdout2, err := bug.Push(c.repo, remote)
 748	if err != nil {
 749		return stdout2, err
 750	}
 751
 752	return stdout1 + stdout2, nil
 753}
 754
 755// Pull will do a Fetch + MergeAll
 756// This function will return an error if a merge fail
 757func (c *RepoCache) Pull(remote string) error {
 758	_, err := c.Fetch(remote)
 759	if err != nil {
 760		return err
 761	}
 762
 763	for merge := range c.MergeAll(remote) {
 764		if merge.Err != nil {
 765			return merge.Err
 766		}
 767		if merge.Status == entity.MergeStatusInvalid {
 768			return errors.Errorf("merge failure: %s", merge.Reason)
 769		}
 770	}
 771
 772	return nil
 773}
 774
 775func repoLockFilePath(repo repository.Repo) string {
 776	return path.Join(repo.GetPath(), "git-bug", lockfile)
 777}
 778
 779// repoIsAvailable check is the given repository is locked by a Cache.
 780// Note: this is a smart function that will cleanup the lock file if the
 781// corresponding process is not there anymore.
 782// If no error is returned, the repo is free to edit.
 783func repoIsAvailable(repo repository.Repo) error {
 784	lockPath := repoLockFilePath(repo)
 785
 786	// Todo: this leave way for a racey access to the repo between the test
 787	// if the file exist and the actual write. It's probably not a problem in
 788	// practice because using a repository will be done from user interaction
 789	// or in a context where a single instance of git-bug is already guaranteed
 790	// (say, a server with the web UI running). But still, that might be nice to
 791	// have a mutex or something to guard that.
 792
 793	// Todo: this will fail if somehow the filesystem is shared with another
 794	// computer. Should add a configuration that prevent the cleaning of the
 795	// lock file
 796
 797	f, err := os.Open(lockPath)
 798
 799	if err != nil && !os.IsNotExist(err) {
 800		return err
 801	}
 802
 803	if err == nil {
 804		// lock file already exist
 805		buf, err := ioutil.ReadAll(io.LimitReader(f, 10))
 806		if err != nil {
 807			return err
 808		}
 809		if len(buf) == 10 {
 810			return fmt.Errorf("the lock file should be < 10 bytes")
 811		}
 812
 813		pid, err := strconv.Atoi(string(buf))
 814		if err != nil {
 815			return err
 816		}
 817
 818		if process.IsRunning(pid) {
 819			return fmt.Errorf("the repository you want to access is already locked by the process pid %d", pid)
 820		}
 821
 822		// The lock file is just laying there after a crash, clean it
 823
 824		fmt.Println("A lock file is present but the corresponding process is not, removing it.")
 825		err = f.Close()
 826		if err != nil {
 827			return err
 828		}
 829
 830		err = os.Remove(lockPath)
 831		if err != nil {
 832			return err
 833		}
 834	}
 835
 836	return nil
 837}
 838
 839// ResolveIdentityExcerpt retrieve a IdentityExcerpt matching the exact given id
 840func (c *RepoCache) ResolveIdentityExcerpt(id entity.Id) (*IdentityExcerpt, error) {
 841	c.muIdentity.RLock()
 842	defer c.muIdentity.RUnlock()
 843
 844	e, ok := c.identitiesExcerpts[id]
 845	if !ok {
 846		return nil, identity.ErrIdentityNotExist
 847	}
 848
 849	return e, nil
 850}
 851
 852// ResolveIdentity retrieve an identity matching the exact given id
 853func (c *RepoCache) ResolveIdentity(id entity.Id) (*IdentityCache, error) {
 854	c.muIdentity.RLock()
 855	cached, ok := c.identities[id]
 856	c.muIdentity.RUnlock()
 857	if ok {
 858		return cached, nil
 859	}
 860
 861	i, err := identity.ReadLocal(c.repo, id)
 862	if err != nil {
 863		return nil, err
 864	}
 865
 866	cached = NewIdentityCache(c, i)
 867
 868	c.muIdentity.Lock()
 869	c.identities[id] = cached
 870	c.muIdentity.Unlock()
 871
 872	return cached, nil
 873}
 874
 875// ResolveIdentityExcerptPrefix retrieve a IdentityExcerpt matching an id prefix.
 876// It fails if multiple identities match.
 877func (c *RepoCache) ResolveIdentityExcerptPrefix(prefix string) (*IdentityExcerpt, error) {
 878	return c.ResolveIdentityExcerptMatcher(func(excerpt *IdentityExcerpt) bool {
 879		return excerpt.Id.HasPrefix(prefix)
 880	})
 881}
 882
 883// ResolveIdentityPrefix retrieve an Identity matching an id prefix.
 884// It fails if multiple identities match.
 885func (c *RepoCache) ResolveIdentityPrefix(prefix string) (*IdentityCache, error) {
 886	return c.ResolveIdentityMatcher(func(excerpt *IdentityExcerpt) bool {
 887		return excerpt.Id.HasPrefix(prefix)
 888	})
 889}
 890
 891// ResolveIdentityImmutableMetadata retrieve an Identity that has the exact given metadata on
 892// one of it's version. If multiple version have the same key, the first defined take precedence.
 893func (c *RepoCache) ResolveIdentityImmutableMetadata(key string, value string) (*IdentityCache, error) {
 894	return c.ResolveIdentityMatcher(func(excerpt *IdentityExcerpt) bool {
 895		return excerpt.ImmutableMetadata[key] == value
 896	})
 897}
 898
 899func (c *RepoCache) ResolveIdentityExcerptMatcher(f func(*IdentityExcerpt) bool) (*IdentityExcerpt, error) {
 900	id, err := c.resolveIdentityMatcher(f)
 901	if err != nil {
 902		return nil, err
 903	}
 904	return c.ResolveIdentityExcerpt(id)
 905}
 906
 907func (c *RepoCache) ResolveIdentityMatcher(f func(*IdentityExcerpt) bool) (*IdentityCache, error) {
 908	id, err := c.resolveIdentityMatcher(f)
 909	if err != nil {
 910		return nil, err
 911	}
 912	return c.ResolveIdentity(id)
 913}
 914
 915func (c *RepoCache) resolveIdentityMatcher(f func(*IdentityExcerpt) bool) (entity.Id, error) {
 916	c.muIdentity.RLock()
 917	defer c.muIdentity.RUnlock()
 918
 919	// preallocate but empty
 920	matching := make([]entity.Id, 0, 5)
 921
 922	for _, excerpt := range c.identitiesExcerpts {
 923		if f(excerpt) {
 924			matching = append(matching, excerpt.Id)
 925		}
 926	}
 927
 928	if len(matching) > 1 {
 929		return entity.UnsetId, identity.NewErrMultipleMatch(matching)
 930	}
 931
 932	if len(matching) == 0 {
 933		return entity.UnsetId, identity.ErrIdentityNotExist
 934	}
 935
 936	return matching[0], nil
 937}
 938
 939// AllIdentityIds return all known identity ids
 940func (c *RepoCache) AllIdentityIds() []entity.Id {
 941	c.muIdentity.RLock()
 942	defer c.muIdentity.RUnlock()
 943
 944	result := make([]entity.Id, len(c.identitiesExcerpts))
 945
 946	i := 0
 947	for _, excerpt := range c.identitiesExcerpts {
 948		result[i] = excerpt.Id
 949		i++
 950	}
 951
 952	return result
 953}
 954
 955func (c *RepoCache) SetUserIdentity(i *IdentityCache) error {
 956	err := identity.SetUserIdentity(c.repo, i.Identity)
 957	if err != nil {
 958		return err
 959	}
 960
 961	c.muIdentity.RLock()
 962	defer c.muIdentity.RUnlock()
 963
 964	// Make sure that everything is fine
 965	if _, ok := c.identities[i.Id()]; !ok {
 966		panic("SetUserIdentity while the identity is not from the cache, something is wrong")
 967	}
 968
 969	c.userIdentityId = i.Id()
 970
 971	return nil
 972}
 973
 974func (c *RepoCache) GetUserIdentity() (*IdentityCache, error) {
 975	if c.userIdentityId != "" {
 976		i, ok := c.identities[c.userIdentityId]
 977		if ok {
 978			return i, nil
 979		}
 980	}
 981
 982	c.muIdentity.Lock()
 983	defer c.muIdentity.Unlock()
 984
 985	i, err := identity.GetUserIdentity(c.repo)
 986	if err != nil {
 987		return nil, err
 988	}
 989
 990	cached := NewIdentityCache(c, i)
 991	c.identities[i.Id()] = cached
 992	c.userIdentityId = i.Id()
 993
 994	return cached, nil
 995}
 996
 997func (c *RepoCache) GetUserIdentityExcerpt() (*IdentityExcerpt, error) {
 998	if c.userIdentityId == "" {
 999		id, err := identity.GetUserIdentityId(c.repo)
1000		if err != nil {
1001			return nil, err
1002		}
1003		c.userIdentityId = id
1004	}
1005
1006	c.muIdentity.RLock()
1007	defer c.muIdentity.RUnlock()
1008
1009	excerpt, ok := c.identitiesExcerpts[c.userIdentityId]
1010	if !ok {
1011		return nil, fmt.Errorf("cache: missing identity excerpt %v", c.userIdentityId)
1012	}
1013	return excerpt, nil
1014}
1015
1016func (c *RepoCache) IsUserIdentitySet() (bool, error) {
1017	return identity.IsUserIdentitySet(c.repo)
1018}
1019
1020func (c *RepoCache) NewIdentityFromGitUser() (*IdentityCache, error) {
1021	return c.NewIdentityFromGitUserRaw(nil)
1022}
1023
1024func (c *RepoCache) NewIdentityFromGitUserRaw(metadata map[string]string) (*IdentityCache, error) {
1025	i, err := identity.NewFromGitUser(c.repo)
1026	if err != nil {
1027		return nil, err
1028	}
1029	return c.finishIdentity(i, metadata)
1030}
1031
1032// NewIdentity create a new identity
1033// The new identity is written in the repository (commit)
1034func (c *RepoCache) NewIdentity(name string, email string) (*IdentityCache, error) {
1035	return c.NewIdentityRaw(name, email, "", "", nil)
1036}
1037
1038// NewIdentityFull create a new identity
1039// The new identity is written in the repository (commit)
1040func (c *RepoCache) NewIdentityFull(name string, email string, login string, avatarUrl string) (*IdentityCache, error) {
1041	return c.NewIdentityRaw(name, email, login, avatarUrl, nil)
1042}
1043
1044func (c *RepoCache) NewIdentityRaw(name string, email string, login string, avatarUrl string, metadata map[string]string) (*IdentityCache, error) {
1045	i := identity.NewIdentityFull(name, email, login, avatarUrl)
1046	return c.finishIdentity(i, metadata)
1047}
1048
1049func (c *RepoCache) finishIdentity(i *identity.Identity, metadata map[string]string) (*IdentityCache, error) {
1050	for key, value := range metadata {
1051		i.SetMetadata(key, value)
1052	}
1053
1054	err := i.Commit(c.repo)
1055	if err != nil {
1056		return nil, err
1057	}
1058
1059	c.muIdentity.Lock()
1060	if _, has := c.identities[i.Id()]; has {
1061		return nil, fmt.Errorf("identity %s already exist in the cache", i.Id())
1062	}
1063
1064	cached := NewIdentityCache(c, i)
1065	c.identities[i.Id()] = cached
1066	c.muIdentity.Unlock()
1067
1068	// force the write of the excerpt
1069	err = c.identityUpdated(i.Id())
1070	if err != nil {
1071		return nil, err
1072	}
1073
1074	return cached, nil
1075}