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