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