aboutsummaryrefslogtreecommitdiffstats
path: root/repository/repo.go
blob: 2347427720a014ed1960342ed9c0c30413c3aa55 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
// Package repository contains helper methods for working with a Git repo.
package repository

import (
	"errors"
	"io"

	"github.com/ProtonMail/go-crypto/openpgp"
	"github.com/go-git/go-billy/v5"

	"github.com/git-bug/git-bug/util/lamport"
)

var (
	// ErrNotARepo is the error returned when the git repo root can't be found
	ErrNotARepo = errors.New("not a git repository")
	// ErrClockNotExist is the error returned when a clock can't be found
	ErrClockNotExist = errors.New("clock doesn't exist")
	// ErrNotFound is the error returned when a git object can't be found
	ErrNotFound = errors.New("ref not found")
)

// Repo represents a source code repository.
type Repo interface {
	RepoConfig
	RepoKeyring
	RepoCommon
	RepoStorage
	RepoIndex
	RepoData

	Close() error
}

type RepoCommonStorage interface {
	RepoCommon
	RepoStorage
}

// ClockedRepo is a Repo that also has Lamport clocks
type ClockedRepo interface {
	Repo
	RepoClock
}

// RepoConfig access the configuration of a repository
type RepoConfig interface {
	// LocalConfig give access to the repository scoped configuration
	LocalConfig() Config

	// GlobalConfig give access to the global scoped configuration
	GlobalConfig() Config

	// AnyConfig give access to a merged local/global configuration
	AnyConfig() ConfigRead
}

// RepoKeyring give access to a user-wide storage for secrets
type RepoKeyring interface {
	// Keyring give access to a user-wide storage for secrets
	Keyring() Keyring
}

// RepoCommon represent the common function we want all repos to implement
type RepoCommon interface {
	// GetUserName returns the name the user has used to configure git
	GetUserName() (string, error)

	// GetUserEmail returns the email address that the user has used to configure git.
	GetUserEmail() (string, error)

	// GetCoreEditor returns the name of the editor that the user has used to configure git.
	GetCoreEditor() (string, error)

	// GetRemotes returns the configured remotes repositories.
	GetRemotes() (map[string]string, error)
}

type LocalStorage interface {
	billy.Filesystem
	RemoveAll(path string) error
}

// RepoStorage give access to the filesystem
type RepoStorage interface {
	// LocalStorage return a billy.Filesystem giving access to $RepoPath/.git/git-bug
	LocalStorage() LocalStorage
}

// RepoIndex gives access to full-text search indexes
type RepoIndex interface {
	GetIndex(name string) (Index, error)
}

// Index is a full-text search index
type Index interface {
	// IndexOne indexes one document, for the given ID. If the document already exist,
	// it replaces it.
	IndexOne(id string, texts []string) error

	// IndexBatch start a batch indexing. The returned indexer function is used the same
	// way as IndexOne, and the closer function complete the batch insertion.
	IndexBatch() (indexer func(id string, texts []string) error, closer func() error)

	// Search returns the list of IDs matching the given terms.
	Search(terms []string) (ids []string, err error)

	// DocCount returns the number of document in the index.
	DocCount() (uint64, error)

	// Remove delete one document in the index.
	Remove(id string) error

	// Clear empty the index.
	Clear() error

	// Close closes the index and make sure everything is safely written. After this call
	// the index can't be used anymore.
	Close() error
}

type Commit struct {
	Hash       Hash
	Parents    []Hash    // hashes of the parents, if any
	TreeHash   Hash      // hash of the git Tree
	SignedData io.Reader // if signed, reader for the signed data (likely, the serialized commit)
	Signature  io.Reader // if signed, reader for the (non-armored) signature
}

// RepoData give access to the git data storage
type RepoData interface {
	// FetchRefs fetch git refs matching a directory prefix to a remote
	// Ex: prefix="foo" will fetch any remote refs matching "refs/foo/*" locally.
	// The equivalent git refspec would be "refs/foo/*:refs/remotes/<remote>/foo/*"
	FetchRefs(remote string, prefixes ...string) (string, error)

	// PushRefs push git refs matching a directory prefix to a remote
	// Ex: prefix="foo" will push any local refs matching "refs/foo/*" to the remote.
	// The equivalent git refspec would be "refs/foo/*:refs/foo/*"
	//
	// Additionally, PushRefs will update the local references in refs/remotes/<remote>/foo to match
	// the remote state.
	PushRefs(remote string, prefixes ...string) (string, error)

	// StoreData will store arbitrary data and return the corresponding hash
	StoreData(data []byte) (Hash, error)

	// ReadData will attempt to read arbitrary data from the given hash
	// Returns ErrNotFound if not found.
	ReadData(hash Hash) ([]byte, error)

	// StoreTree will store a mapping key-->Hash as a Git tree
	StoreTree(mapping []TreeEntry) (Hash, error)

	// ReadTree will return the list of entries in a Git tree
	// The given hash could be from either a commit or a tree
	// Returns ErrNotFound if not found.
	ReadTree(hash Hash) ([]TreeEntry, error)

	// StoreCommit will store a Git commit with the given Git tree
	StoreCommit(treeHash Hash, parents ...Hash) (Hash, error)

	// StoreSignedCommit will store a Git commit with the given Git tree. If signKey is not nil, the commit
	// will be signed accordingly.
	StoreSignedCommit(treeHash Hash, signKey *openpgp.Entity, parents ...Hash) (Hash, error)

	// ReadCommit read a Git commit and returns some of its characteristic
	// Returns ErrNotFound if not found.
	ReadCommit(hash Hash) (Commit, error)

	// ResolveRef returns the hash of the target commit of the given ref
	// Returns ErrNotFound if not found.
	ResolveRef(ref string) (Hash, error)

	// UpdateRef will create or update a Git reference
	UpdateRef(ref string, hash Hash) error

	// RemoveRef will remove a Git reference
	// RemoveRef is idempotent.
	RemoveRef(ref string) error

	// ListRefs will return a list of Git ref matching the given refspec
	ListRefs(refPrefix string) ([]string, error)

	// RefExist will check if a reference exist in Git
	RefExist(ref string) (bool, error)

	// CopyRef will create a new reference with the same value as another one
	// Returns ErrNotFound if not found.
	CopyRef(source string, dest string) error

	// ListCommits will return the list of tree hashes of a ref, in chronological order
	ListCommits(ref string) ([]Hash, error)
}

// RepoClock give access to Lamport clocks
type RepoClock interface {
	// AllClocks return all the known clocks
	AllClocks() (map[string]lamport.Clock, error)

	// GetOrCreateClock return a Lamport clock stored in the Repo.
	// If the clock doesn't exist, it's created.
	GetOrCreateClock(name string) (lamport.Clock, error)

	// Increment is equivalent to c = GetOrCreateClock(name) + c.Increment()
	Increment(name string) (lamport.Time, error)

	// Witness is equivalent to c = GetOrCreateClock(name) + c.Witness(time)
	Witness(name string, time lamport.Time) error
}

// ClockLoader hold which logical clock need to exist for an entity and
// how to create them if they don't.
type ClockLoader struct {
	// Clocks hold the name of all the clocks this loader deal with.
	// Those clocks will be checked when the repo load. If not present or broken,
	// Witnesser will be used to create them.
	Clocks []string
	// Witnesser is a function that will initialize the clocks of a repo
	// from scratch
	Witnesser func(repo ClockedRepo) error
}

// TestedRepo is an extended ClockedRepo with function for testing only
type TestedRepo interface {
	ClockedRepo
	repoTest
}

// repoTest give access to test only functions
type repoTest interface {
	// AddRemote add a new remote to the repository
	AddRemote(name string, url string) error

	// GetLocalRemote return the URL to use to add this repo as a local remote
	GetLocalRemote() string

	// EraseFromDisk delete this repository entirely from the disk
	EraseFromDisk() error
}