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