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