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