repo_cache.go

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