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