feat(domain): add update/delete events, request-scoped principal and gitcli branch helpers
RepoUpdated/RepoDeleted events, ErrInvalid/ErrPreconditionFailed, the principal carried in ctx, GitRepos.HasBranch/HeadBranch, and Trash drops the cat-file process of the moved directory.
- commit
- 435618a674c26b75ab493ff007cc18c6de988969
- author
- brice · · 2026-10-10 09:41 UTC
- parent
- 6e542e5
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
7 files · +258 -0
M internal/adapters/gitcli/catfile.go +9 -0
| @@ -273,6 +273,15 @@ func (p *catPool) startLocked(ctx context.Context, dir string) (*catProc, error) | |||
| 273 | 273 | return cp, nil | |
| 274 | 274 | } | |
| 275 | 275 | ||
| 276 | + | // forget kills the process of the repository in dir, if any. | |
| 277 | + | func (p *catPool) forget(dir string) { | |
| 278 | + | p.mu.Lock() | |
| 279 | + | defer p.mu.Unlock() | |
| 280 | + | if cp := p.procs[dir]; cp != nil { | |
| 281 | + | p.dropLocked(cp) | |
| 282 | + | } | |
| 283 | + | } | |
| 284 | + | ||
| 276 | 285 | // close kills every process and waits, until ctx ends, for them to be | |
| 277 | 286 | // collected. Later requests fail with ErrClosed. | |
| 278 | 287 | func (p *catPool) close(ctx context.Context) error { | |
| @@ -273,6 +273,15 @@ func (p *catPool) startLocked(ctx context.Context, dir string) (*catProc, error) | |||||
| 273 | return cp, nil | 273 | return cp, nil | ||
| 274 | } | 274 | } | ||
| 275 | 275 | ||||
| 276 | + | // forget kills the process of the repository in dir, if any. | |||
| 277 | + | func (p *catPool) forget(dir string) { | |||
| 278 | + | p.mu.Lock() | |||
| 279 | + | defer p.mu.Unlock() | |||
| 280 | + | if cp := p.procs[dir]; cp != nil { | |||
| 281 | + | p.dropLocked(cp) | |||
| 282 | + | } | |||
| 283 | + | } | |||
| 284 | + | ||||
| 276 | // close kills every process and waits, until ctx ends, for them to be | 285 | // close kills every process and waits, until ctx ends, for them to be | ||
| 277 | // collected. Later requests fail with ErrClosed. | 286 | // collected. Later requests fail with ErrClosed. | ||
| 278 | func (p *catPool) close(ctx context.Context) error { | 287 | func (p *catPool) close(ctx context.Context) error { | ||
M internal/adapters/gitcli/repos.go +52 -0
| @@ -10,6 +10,7 @@ import ( | |||
| 10 | 10 | "fmt" | |
| 11 | 11 | "io/fs" | |
| 12 | 12 | "os" | |
| 13 | + | "os/exec" | |
| 13 | 14 | "path/filepath" | |
| 14 | 15 | "strings" | |
| 15 | 16 | "time" | |
| @@ -164,6 +165,9 @@ func (g *Git) Trash(ctx context.Context, name domain.RepoName) error { | |||
| 164 | 165 | if err != nil { | |
| 165 | 166 | return fmt.Errorf("trash repository %q: %w", name, err) | |
| 166 | 167 | } | |
| 168 | + | // A cat-file process of the old directory must not serve a repository | |
| 169 | + | // created later under the same name. | |
| 170 | + | g.cat.forget(src) | |
| 167 | 171 | if err := syncDir(g.reposDir()); err != nil { | |
| 168 | 172 | return fmt.Errorf("trash repository %q: %w", name, err) | |
| 169 | 173 | } | |
| @@ -253,6 +257,54 @@ func (g *Git) DefaultBranch(ctx context.Context, name domain.RepoName) (string, | |||
| 253 | 257 | return branch, strings.TrimSpace(out) == ref, nil | |
| 254 | 258 | } | |
| 255 | 259 | ||
| 260 | + | // HasBranch checks refs/heads/<branch> with `git for-each-ref` after | |
| 261 | + | // `git check-ref-format`, which rejects the glob characters for-each-ref | |
| 262 | + | // would otherwise interpret. | |
| 263 | + | // | |
| 264 | + | //hexcheck:ignore suspicious-business-logic-in-adapter port method name only | |
| 265 | + | func (g *Git) HasBranch(ctx context.Context, name domain.RepoName, branch string) (bool, error) { | |
| 266 | + | dir, err := g.repoDir(name) | |
| 267 | + | if err != nil { | |
| 268 | + | return false, err | |
| 269 | + | } | |
| 270 | + | ref := headsPrefix + branch | |
| 271 | + | if branch == "" || strings.HasPrefix(branch, "-") { | |
| 272 | + | return false, nil | |
| 273 | + | } | |
| 274 | + | if _, err := g.run(ctx, "check-ref-format", ref); err != nil { | |
| 275 | + | var exit *exec.ExitError | |
| 276 | + | if errors.As(err, &exit) { | |
| 277 | + | return false, nil | |
| 278 | + | } | |
| 279 | + | return false, err | |
| 280 | + | } | |
| 281 | + | out, err := g.run(ctx, "-C", dir, "for-each-ref", "--format=%(refname)", ref) | |
| 282 | + | if err != nil { | |
| 283 | + | return false, fmt.Errorf("look up %s in %q: %w", ref, name, err) | |
| 284 | + | } | |
| 285 | + | return strings.TrimSpace(out) == ref, nil | |
| 286 | + | } | |
| 287 | + | ||
| 288 | + | // HeadBranch reads the HEAD file of the bare repository (files ref backend). | |
| 289 | + | func (g *Git) HeadBranch(_ context.Context, name domain.RepoName) (string, error) { | |
| 290 | + | dir, err := g.repoDir(name) | |
| 291 | + | if err != nil { | |
| 292 | + | return "", err | |
| 293 | + | } | |
| 294 | + | raw, err := os.ReadFile(filepath.Join(dir, "HEAD")) | |
| 295 | + | if err != nil { | |
| 296 | + | if errors.Is(err, fs.ErrNotExist) { | |
| 297 | + | return "", domain.ErrNotFound | |
| 298 | + | } | |
| 299 | + | return "", fmt.Errorf("read HEAD of %q: %w", name, err) | |
| 300 | + | } | |
| 301 | + | ref, ok := strings.CutPrefix(strings.TrimSpace(string(raw)), "ref: ") | |
| 302 | + | if !ok { | |
| 303 | + | return "", nil // detached | |
| 304 | + | } | |
| 305 | + | return strings.TrimPrefix(ref, headsPrefix), nil | |
| 306 | + | } | |
| 307 | + | ||
| 256 | 308 | // SetDefaultBranch runs `git symbolic-ref HEAD refs/heads/<branch>`. | |
| 257 | 309 | func (g *Git) SetDefaultBranch(ctx context.Context, name domain.RepoName, branch string) error { | |
| 258 | 310 | dir, err := g.repoDir(name) | |
| @@ -10,6 +10,7 @@ import ( | |||||
| 10 | "fmt" | 10 | "fmt" | ||
| 11 | "io/fs" | 11 | "io/fs" | ||
| 12 | "os" | 12 | "os" | ||
| 13 | + | "os/exec" | |||
| 13 | "path/filepath" | 14 | "path/filepath" | ||
| 14 | "strings" | 15 | "strings" | ||
| 15 | "time" | 16 | "time" | ||
| @@ -164,6 +165,9 @@ func (g *Git) Trash(ctx context.Context, name domain.RepoName) error { | |||||
| 164 | if err != nil { | 165 | if err != nil { | ||
| 165 | return fmt.Errorf("trash repository %q: %w", name, err) | 166 | return fmt.Errorf("trash repository %q: %w", name, err) | ||
| 166 | } | 167 | } | ||
| 168 | + | // A cat-file process of the old directory must not serve a repository | |||
| 169 | + | // created later under the same name. | |||
| 170 | + | g.cat.forget(src) | |||
| 167 | if err := syncDir(g.reposDir()); err != nil { | 171 | if err := syncDir(g.reposDir()); err != nil { | ||
| 168 | return fmt.Errorf("trash repository %q: %w", name, err) | 172 | return fmt.Errorf("trash repository %q: %w", name, err) | ||
| 169 | } | 173 | } | ||
| @@ -253,6 +257,54 @@ func (g *Git) DefaultBranch(ctx context.Context, name domain.RepoName) (string, | |||||
| 253 | return branch, strings.TrimSpace(out) == ref, nil | 257 | return branch, strings.TrimSpace(out) == ref, nil | ||
| 254 | } | 258 | } | ||
| 255 | 259 | ||||
| 260 | + | // HasBranch checks refs/heads/<branch> with `git for-each-ref` after | |||
| 261 | + | // `git check-ref-format`, which rejects the glob characters for-each-ref | |||
| 262 | + | // would otherwise interpret. | |||
| 263 | + | // | |||
| 264 | + | //hexcheck:ignore suspicious-business-logic-in-adapter port method name only | |||
| 265 | + | func (g *Git) HasBranch(ctx context.Context, name domain.RepoName, branch string) (bool, error) { | |||
| 266 | + | dir, err := g.repoDir(name) | |||
| 267 | + | if err != nil { | |||
| 268 | + | return false, err | |||
| 269 | + | } | |||
| 270 | + | ref := headsPrefix + branch | |||
| 271 | + | if branch == "" || strings.HasPrefix(branch, "-") { | |||
| 272 | + | return false, nil | |||
| 273 | + | } | |||
| 274 | + | if _, err := g.run(ctx, "check-ref-format", ref); err != nil { | |||
| 275 | + | var exit *exec.ExitError | |||
| 276 | + | if errors.As(err, &exit) { | |||
| 277 | + | return false, nil | |||
| 278 | + | } | |||
| 279 | + | return false, err | |||
| 280 | + | } | |||
| 281 | + | out, err := g.run(ctx, "-C", dir, "for-each-ref", "--format=%(refname)", ref) | |||
| 282 | + | if err != nil { | |||
| 283 | + | return false, fmt.Errorf("look up %s in %q: %w", ref, name, err) | |||
| 284 | + | } | |||
| 285 | + | return strings.TrimSpace(out) == ref, nil | |||
| 286 | + | } | |||
| 287 | + | ||||
| 288 | + | // HeadBranch reads the HEAD file of the bare repository (files ref backend). | |||
| 289 | + | func (g *Git) HeadBranch(_ context.Context, name domain.RepoName) (string, error) { | |||
| 290 | + | dir, err := g.repoDir(name) | |||
| 291 | + | if err != nil { | |||
| 292 | + | return "", err | |||
| 293 | + | } | |||
| 294 | + | raw, err := os.ReadFile(filepath.Join(dir, "HEAD")) | |||
| 295 | + | if err != nil { | |||
| 296 | + | if errors.Is(err, fs.ErrNotExist) { | |||
| 297 | + | return "", domain.ErrNotFound | |||
| 298 | + | } | |||
| 299 | + | return "", fmt.Errorf("read HEAD of %q: %w", name, err) | |||
| 300 | + | } | |||
| 301 | + | ref, ok := strings.CutPrefix(strings.TrimSpace(string(raw)), "ref: ") | |||
| 302 | + | if !ok { | |||
| 303 | + | return "", nil // detached | |||
| 304 | + | } | |||
| 305 | + | return strings.TrimPrefix(ref, headsPrefix), nil | |||
| 306 | + | } | |||
| 307 | + | ||||
| 256 | // SetDefaultBranch runs `git symbolic-ref HEAD refs/heads/<branch>`. | 308 | // SetDefaultBranch runs `git symbolic-ref HEAD refs/heads/<branch>`. | ||
| 257 | func (g *Git) SetDefaultBranch(ctx context.Context, name domain.RepoName, branch string) error { | 309 | func (g *Git) SetDefaultBranch(ctx context.Context, name domain.RepoName, branch string) error { | ||
| 258 | dir, err := g.repoDir(name) | 310 | dir, err := g.repoDir(name) | ||
M internal/domain/errors.go +7 -0
| @@ -26,4 +26,11 @@ var ( | |||
| 26 | 26 | ErrRejected = errors.New("rejected") | |
| 27 | 27 | // ErrConflict means a concurrent change prevented the operation. | |
| 28 | 28 | ErrConflict = errors.New("conflict") | |
| 29 | + | // ErrInvalid means an argument of a use case is not acceptable and no | |
| 30 | + | // more specific error says why: an empty update, an unknown topic, a | |
| 31 | + | // description that breaks the rules. | |
| 32 | + | ErrInvalid = errors.New("invalid argument") | |
| 33 | + | // ErrPreconditionFailed means the state of the forge does not allow the | |
| 34 | + | // operation, for example a default branch that does not exist. | |
| 35 | + | ErrPreconditionFailed = errors.New("failed precondition") | |
| 29 | 36 | ) | |
| @@ -26,4 +26,11 @@ var ( | |||||
| 26 | ErrRejected = errors.New("rejected") | 26 | ErrRejected = errors.New("rejected") | ||
| 27 | // ErrConflict means a concurrent change prevented the operation. | 27 | // ErrConflict means a concurrent change prevented the operation. | ||
| 28 | ErrConflict = errors.New("conflict") | 28 | ErrConflict = errors.New("conflict") | ||
| 29 | + | // ErrInvalid means an argument of a use case is not acceptable and no | |||
| 30 | + | // more specific error says why: an empty update, an unknown topic, a | |||
| 31 | + | // description that breaks the rules. | |||
| 32 | + | ErrInvalid = errors.New("invalid argument") | |||
| 33 | + | // ErrPreconditionFailed means the state of the forge does not allow the | |||
| 34 | + | // operation, for example a default branch that does not exist. | |||
| 35 | + | ErrPreconditionFailed = errors.New("failed precondition") | |||
| 29 | ) | 36 | ) | ||
M internal/domain/event.go +21 -0
| @@ -60,6 +60,27 @@ type RepoCreated struct { | |||
| 60 | 60 | // EventKind implements Event. | |
| 61 | 61 | func (RepoCreated) EventKind() string { return "repo.created" } | |
| 62 | 62 | ||
| 63 | + | // RepoUpdated is published when the metadata of a repository, or its default | |
| 64 | + | // branch, changed. The new state is read from the repository. | |
| 65 | + | type RepoUpdated struct { | |
| 66 | + | Repo RepoName | |
| 67 | + | At time.Time | |
| 68 | + | } | |
| 69 | + | ||
| 70 | + | // EventKind implements Event. | |
| 71 | + | func (RepoUpdated) EventKind() string { return "repo.updated" } | |
| 72 | + | ||
| 73 | + | // RepoDeleted is published after a repository was deleted. It records the | |
| 74 | + | // visibility the repository had, which decides who may be told. | |
| 75 | + | type RepoDeleted struct { | |
| 76 | + | Repo RepoName | |
| 77 | + | Visibility Visibility | |
| 78 | + | At time.Time | |
| 79 | + | } | |
| 80 | + | ||
| 81 | + | // EventKind implements Event. | |
| 82 | + | func (RepoDeleted) EventKind() string { return "repo.deleted" } | |
| 83 | + | ||
| 63 | 84 | // PushEvent is published after a push has been accepted. | |
| 64 | 85 | type PushEvent struct { | |
| 65 | 86 | Repo RepoName | |
| @@ -60,6 +60,27 @@ type RepoCreated struct { | |||||
| 60 | // EventKind implements Event. | 60 | // EventKind implements Event. | ||
| 61 | func (RepoCreated) EventKind() string { return "repo.created" } | 61 | func (RepoCreated) EventKind() string { return "repo.created" } | ||
| 62 | 62 | ||||
| 63 | + | // RepoUpdated is published when the metadata of a repository, or its default | |||
| 64 | + | // branch, changed. The new state is read from the repository. | |||
| 65 | + | type RepoUpdated struct { | |||
| 66 | + | Repo RepoName | |||
| 67 | + | At time.Time | |||
| 68 | + | } | |||
| 69 | + | ||||
| 70 | + | // EventKind implements Event. | |||
| 71 | + | func (RepoUpdated) EventKind() string { return "repo.updated" } | |||
| 72 | + | ||||
| 73 | + | // RepoDeleted is published after a repository was deleted. It records the | |||
| 74 | + | // visibility the repository had, which decides who may be told. | |||
| 75 | + | type RepoDeleted struct { | |||
| 76 | + | Repo RepoName | |||
| 77 | + | Visibility Visibility | |||
| 78 | + | At time.Time | |||
| 79 | + | } | |||
| 80 | + | ||||
| 81 | + | // EventKind implements Event. | |||
| 82 | + | func (RepoDeleted) EventKind() string { return "repo.deleted" } | |||
| 83 | + | ||||
| 63 | // PushEvent is published after a push has been accepted. | 84 | // PushEvent is published after a push has been accepted. | ||
| 64 | type PushEvent struct { | 85 | type PushEvent struct { | ||
| 65 | Repo RepoName | 86 | Repo RepoName | ||
A internal/domain/principalctx.go +22 -0
| @@ -0,0 +1,22 @@ | |||
| 1 | + | // SPDX-License-Identifier: AGPL-3.0-or-later | |
| 2 | + | ||
| 3 | + | package domain | |
| 4 | + | ||
| 5 | + | import "context" | |
| 6 | + | ||
| 7 | + | type principalKey struct{} | |
| 8 | + | ||
| 9 | + | // ContextWithPrincipal returns a context that carries p as the identity of the | |
| 10 | + | // caller. The API adapter sets it after authenticating a request; the web | |
| 11 | + | // sets it for the calls it makes in process (anonymous, or the owner of a | |
| 12 | + | // session). Authorization stays in the use cases. | |
| 13 | + | func ContextWithPrincipal(ctx context.Context, p Principal) context.Context { | |
| 14 | + | return context.WithValue(ctx, principalKey{}, p) | |
| 15 | + | } | |
| 16 | + | ||
| 17 | + | // PrincipalFromContext returns the principal ContextWithPrincipal stored. | |
| 18 | + | // ok is false when the context carries none. | |
| 19 | + | func PrincipalFromContext(ctx context.Context) (p Principal, ok bool) { | |
| 20 | + | p, ok = ctx.Value(principalKey{}).(Principal) | |
| 21 | + | return p, ok | |
| 22 | + | } | |
| @@ -0,0 +1,22 @@ | |||||
| 1 | + | // SPDX-License-Identifier: AGPL-3.0-or-later | |||
| 2 | + | ||||
| 3 | + | package domain | |||
| 4 | + | ||||
| 5 | + | import "context" | |||
| 6 | + | ||||
| 7 | + | type principalKey struct{} | |||
| 8 | + | ||||
| 9 | + | // ContextWithPrincipal returns a context that carries p as the identity of the | |||
| 10 | + | // caller. The API adapter sets it after authenticating a request; the web | |||
| 11 | + | // sets it for the calls it makes in process (anonymous, or the owner of a | |||
| 12 | + | // session). Authorization stays in the use cases. | |||
| 13 | + | func ContextWithPrincipal(ctx context.Context, p Principal) context.Context { | |||
| 14 | + | return context.WithValue(ctx, principalKey{}, p) | |||
| 15 | + | } | |||
| 16 | + | ||||
| 17 | + | // PrincipalFromContext returns the principal ContextWithPrincipal stored. | |||
| 18 | + | // ok is false when the context carries none. | |||
| 19 | + | func PrincipalFromContext(ctx context.Context) (p Principal, ok bool) { | |||
| 20 | + | p, ok = ctx.Value(principalKey{}).(Principal) | |||
| 21 | + | return p, ok | |||
| 22 | + | } | |||
M internal/ports/mocks/GitRepos.go +138 -0
| @@ -177,6 +177,144 @@ func (_c *GitRepos_Discard_Call) RunAndReturn(run func(ctx context.Context, id p | |||
| 177 | 177 | return _c | |
| 178 | 178 | } | |
| 179 | 179 | ||
| 180 | + | // HasBranch provides a mock function for the type GitRepos | |
| 181 | + | func (_mock *GitRepos) HasBranch(ctx context.Context, name domain.RepoName, branch string) (bool, error) { | |
| 182 | + | ret := _mock.Called(ctx, name, branch) | |
| 183 | + | ||
| 184 | + | if len(ret) == 0 { | |
| 185 | + | panic("no return value specified for HasBranch") | |
| 186 | + | } | |
| 187 | + | ||
| 188 | + | var r0 bool | |
| 189 | + | var r1 error | |
| 190 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, string) (bool, error)); ok { | |
| 191 | + | return returnFunc(ctx, name, branch) | |
| 192 | + | } | |
| 193 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, string) bool); ok { | |
| 194 | + | r0 = returnFunc(ctx, name, branch) | |
| 195 | + | } else { | |
| 196 | + | r0 = ret.Get(0).(bool) | |
| 197 | + | } | |
| 198 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, domain.RepoName, string) error); ok { | |
| 199 | + | r1 = returnFunc(ctx, name, branch) | |
| 200 | + | } else { | |
| 201 | + | r1 = ret.Error(1) | |
| 202 | + | } | |
| 203 | + | return r0, r1 | |
| 204 | + | } | |
| 205 | + | ||
| 206 | + | // GitRepos_HasBranch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HasBranch' | |
| 207 | + | type GitRepos_HasBranch_Call struct { | |
| 208 | + | *mock.Call | |
| 209 | + | } | |
| 210 | + | ||
| 211 | + | // HasBranch is a helper method to define mock.On call | |
| 212 | + | // - ctx context.Context | |
| 213 | + | // - name domain.RepoName | |
| 214 | + | // - branch string | |
| 215 | + | func (_e *GitRepos_Expecter) HasBranch(ctx any, name any, branch any) *GitRepos_HasBranch_Call { | |
| 216 | + | return &GitRepos_HasBranch_Call{Call: _e.mock.On("HasBranch", ctx, name, branch)} | |
| 217 | + | } | |
| 218 | + | ||
| 219 | + | func (_c *GitRepos_HasBranch_Call) Run(run func(ctx context.Context, name domain.RepoName, branch string)) *GitRepos_HasBranch_Call { | |
| 220 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 221 | + | var arg0 context.Context | |
| 222 | + | if args[0] != nil { | |
| 223 | + | arg0 = args[0].(context.Context) | |
| 224 | + | } | |
| 225 | + | var arg1 domain.RepoName | |
| 226 | + | if args[1] != nil { | |
| 227 | + | arg1 = args[1].(domain.RepoName) | |
| 228 | + | } | |
| 229 | + | var arg2 string | |
| 230 | + | if args[2] != nil { | |
| 231 | + | arg2 = args[2].(string) | |
| 232 | + | } | |
| 233 | + | run( | |
| 234 | + | arg0, | |
| 235 | + | arg1, | |
| 236 | + | arg2, | |
| 237 | + | ) | |
| 238 | + | }) | |
| 239 | + | return _c | |
| 240 | + | } | |
| 241 | + | ||
| 242 | + | func (_c *GitRepos_HasBranch_Call) Return(b bool, err error) *GitRepos_HasBranch_Call { | |
| 243 | + | _c.Call.Return(b, err) | |
| 244 | + | return _c | |
| 245 | + | } | |
| 246 | + | ||
| 247 | + | func (_c *GitRepos_HasBranch_Call) RunAndReturn(run func(ctx context.Context, name domain.RepoName, branch string) (bool, error)) *GitRepos_HasBranch_Call { | |
| 248 | + | _c.Call.Return(run) | |
| 249 | + | return _c | |
| 250 | + | } | |
| 251 | + | ||
| 252 | + | // HeadBranch provides a mock function for the type GitRepos | |
| 253 | + | func (_mock *GitRepos) HeadBranch(ctx context.Context, name domain.RepoName) (string, error) { | |
| 254 | + | ret := _mock.Called(ctx, name) | |
| 255 | + | ||
| 256 | + | if len(ret) == 0 { | |
| 257 | + | panic("no return value specified for HeadBranch") | |
| 258 | + | } | |
| 259 | + | ||
| 260 | + | var r0 string | |
| 261 | + | var r1 error | |
| 262 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName) (string, error)); ok { | |
| 263 | + | return returnFunc(ctx, name) | |
| 264 | + | } | |
| 265 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName) string); ok { | |
| 266 | + | r0 = returnFunc(ctx, name) | |
| 267 | + | } else { | |
| 268 | + | r0 = ret.Get(0).(string) | |
| 269 | + | } | |
| 270 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, domain.RepoName) error); ok { | |
| 271 | + | r1 = returnFunc(ctx, name) | |
| 272 | + | } else { | |
| 273 | + | r1 = ret.Error(1) | |
| 274 | + | } | |
| 275 | + | return r0, r1 | |
| 276 | + | } | |
| 277 | + | ||
| 278 | + | // GitRepos_HeadBranch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HeadBranch' | |
| 279 | + | type GitRepos_HeadBranch_Call struct { | |
| 280 | + | *mock.Call | |
| 281 | + | } | |
| 282 | + | ||
| 283 | + | // HeadBranch is a helper method to define mock.On call | |
| 284 | + | // - ctx context.Context | |
| 285 | + | // - name domain.RepoName | |
| 286 | + | func (_e *GitRepos_Expecter) HeadBranch(ctx any, name any) *GitRepos_HeadBranch_Call { | |
| 287 | + | return &GitRepos_HeadBranch_Call{Call: _e.mock.On("HeadBranch", ctx, name)} | |
| 288 | + | } | |
| 289 | + | ||
| 290 | + | func (_c *GitRepos_HeadBranch_Call) Run(run func(ctx context.Context, name domain.RepoName)) *GitRepos_HeadBranch_Call { | |
| 291 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 292 | + | var arg0 context.Context | |
| 293 | + | if args[0] != nil { | |
| 294 | + | arg0 = args[0].(context.Context) | |
| 295 | + | } | |
| 296 | + | var arg1 domain.RepoName | |
| 297 | + | if args[1] != nil { | |
| 298 | + | arg1 = args[1].(domain.RepoName) | |
| 299 | + | } | |
| 300 | + | run( | |
| 301 | + | arg0, | |
| 302 | + | arg1, | |
| 303 | + | ) | |
| 304 | + | }) | |
| 305 | + | return _c | |
| 306 | + | } | |
| 307 | + | ||
| 308 | + | func (_c *GitRepos_HeadBranch_Call) Return(s string, err error) *GitRepos_HeadBranch_Call { | |
| 309 | + | _c.Call.Return(s, err) | |
| 310 | + | return _c | |
| 311 | + | } | |
| 312 | + | ||
| 313 | + | func (_c *GitRepos_HeadBranch_Call) RunAndReturn(run func(ctx context.Context, name domain.RepoName) (string, error)) *GitRepos_HeadBranch_Call { | |
| 314 | + | _c.Call.Return(run) | |
| 315 | + | return _c | |
| 316 | + | } | |
| 317 | + | ||
| 180 | 318 | // List provides a mock function for the type GitRepos | |
| 181 | 319 | func (_mock *GitRepos) List(ctx context.Context) (ports.RepoDirs, error) { | |
| 182 | 320 | ret := _mock.Called(ctx) | |
| @@ -177,6 +177,144 @@ func (_c *GitRepos_Discard_Call) RunAndReturn(run func(ctx context.Context, id p | |||||
| 177 | return _c | 177 | return _c | ||
| 178 | } | 178 | } | ||
| 179 | 179 | ||||
| 180 | + | // HasBranch provides a mock function for the type GitRepos | |||
| 181 | + | func (_mock *GitRepos) HasBranch(ctx context.Context, name domain.RepoName, branch string) (bool, error) { | |||
| 182 | + | ret := _mock.Called(ctx, name, branch) | |||
| 183 | + | ||||
| 184 | + | if len(ret) == 0 { | |||
| 185 | + | panic("no return value specified for HasBranch") | |||
| 186 | + | } | |||
| 187 | + | ||||
| 188 | + | var r0 bool | |||
| 189 | + | var r1 error | |||
| 190 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, string) (bool, error)); ok { | |||
| 191 | + | return returnFunc(ctx, name, branch) | |||
| 192 | + | } | |||
| 193 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName, string) bool); ok { | |||
| 194 | + | r0 = returnFunc(ctx, name, branch) | |||
| 195 | + | } else { | |||
| 196 | + | r0 = ret.Get(0).(bool) | |||
| 197 | + | } | |||
| 198 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, domain.RepoName, string) error); ok { | |||
| 199 | + | r1 = returnFunc(ctx, name, branch) | |||
| 200 | + | } else { | |||
| 201 | + | r1 = ret.Error(1) | |||
| 202 | + | } | |||
| 203 | + | return r0, r1 | |||
| 204 | + | } | |||
| 205 | + | ||||
| 206 | + | // GitRepos_HasBranch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HasBranch' | |||
| 207 | + | type GitRepos_HasBranch_Call struct { | |||
| 208 | + | *mock.Call | |||
| 209 | + | } | |||
| 210 | + | ||||
| 211 | + | // HasBranch is a helper method to define mock.On call | |||
| 212 | + | // - ctx context.Context | |||
| 213 | + | // - name domain.RepoName | |||
| 214 | + | // - branch string | |||
| 215 | + | func (_e *GitRepos_Expecter) HasBranch(ctx any, name any, branch any) *GitRepos_HasBranch_Call { | |||
| 216 | + | return &GitRepos_HasBranch_Call{Call: _e.mock.On("HasBranch", ctx, name, branch)} | |||
| 217 | + | } | |||
| 218 | + | ||||
| 219 | + | func (_c *GitRepos_HasBranch_Call) Run(run func(ctx context.Context, name domain.RepoName, branch string)) *GitRepos_HasBranch_Call { | |||
| 220 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 221 | + | var arg0 context.Context | |||
| 222 | + | if args[0] != nil { | |||
| 223 | + | arg0 = args[0].(context.Context) | |||
| 224 | + | } | |||
| 225 | + | var arg1 domain.RepoName | |||
| 226 | + | if args[1] != nil { | |||
| 227 | + | arg1 = args[1].(domain.RepoName) | |||
| 228 | + | } | |||
| 229 | + | var arg2 string | |||
| 230 | + | if args[2] != nil { | |||
| 231 | + | arg2 = args[2].(string) | |||
| 232 | + | } | |||
| 233 | + | run( | |||
| 234 | + | arg0, | |||
| 235 | + | arg1, | |||
| 236 | + | arg2, | |||
| 237 | + | ) | |||
| 238 | + | }) | |||
| 239 | + | return _c | |||
| 240 | + | } | |||
| 241 | + | ||||
| 242 | + | func (_c *GitRepos_HasBranch_Call) Return(b bool, err error) *GitRepos_HasBranch_Call { | |||
| 243 | + | _c.Call.Return(b, err) | |||
| 244 | + | return _c | |||
| 245 | + | } | |||
| 246 | + | ||||
| 247 | + | func (_c *GitRepos_HasBranch_Call) RunAndReturn(run func(ctx context.Context, name domain.RepoName, branch string) (bool, error)) *GitRepos_HasBranch_Call { | |||
| 248 | + | _c.Call.Return(run) | |||
| 249 | + | return _c | |||
| 250 | + | } | |||
| 251 | + | ||||
| 252 | + | // HeadBranch provides a mock function for the type GitRepos | |||
| 253 | + | func (_mock *GitRepos) HeadBranch(ctx context.Context, name domain.RepoName) (string, error) { | |||
| 254 | + | ret := _mock.Called(ctx, name) | |||
| 255 | + | ||||
| 256 | + | if len(ret) == 0 { | |||
| 257 | + | panic("no return value specified for HeadBranch") | |||
| 258 | + | } | |||
| 259 | + | ||||
| 260 | + | var r0 string | |||
| 261 | + | var r1 error | |||
| 262 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName) (string, error)); ok { | |||
| 263 | + | return returnFunc(ctx, name) | |||
| 264 | + | } | |||
| 265 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.RepoName) string); ok { | |||
| 266 | + | r0 = returnFunc(ctx, name) | |||
| 267 | + | } else { | |||
| 268 | + | r0 = ret.Get(0).(string) | |||
| 269 | + | } | |||
| 270 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, domain.RepoName) error); ok { | |||
| 271 | + | r1 = returnFunc(ctx, name) | |||
| 272 | + | } else { | |||
| 273 | + | r1 = ret.Error(1) | |||
| 274 | + | } | |||
| 275 | + | return r0, r1 | |||
| 276 | + | } | |||
| 277 | + | ||||
| 278 | + | // GitRepos_HeadBranch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'HeadBranch' | |||
| 279 | + | type GitRepos_HeadBranch_Call struct { | |||
| 280 | + | *mock.Call | |||
| 281 | + | } | |||
| 282 | + | ||||
| 283 | + | // HeadBranch is a helper method to define mock.On call | |||
| 284 | + | // - ctx context.Context | |||
| 285 | + | // - name domain.RepoName | |||
| 286 | + | func (_e *GitRepos_Expecter) HeadBranch(ctx any, name any) *GitRepos_HeadBranch_Call { | |||
| 287 | + | return &GitRepos_HeadBranch_Call{Call: _e.mock.On("HeadBranch", ctx, name)} | |||
| 288 | + | } | |||
| 289 | + | ||||
| 290 | + | func (_c *GitRepos_HeadBranch_Call) Run(run func(ctx context.Context, name domain.RepoName)) *GitRepos_HeadBranch_Call { | |||
| 291 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 292 | + | var arg0 context.Context | |||
| 293 | + | if args[0] != nil { | |||
| 294 | + | arg0 = args[0].(context.Context) | |||
| 295 | + | } | |||
| 296 | + | var arg1 domain.RepoName | |||
| 297 | + | if args[1] != nil { | |||
| 298 | + | arg1 = args[1].(domain.RepoName) | |||
| 299 | + | } | |||
| 300 | + | run( | |||
| 301 | + | arg0, | |||
| 302 | + | arg1, | |||
| 303 | + | ) | |||
| 304 | + | }) | |||
| 305 | + | return _c | |||
| 306 | + | } | |||
| 307 | + | ||||
| 308 | + | func (_c *GitRepos_HeadBranch_Call) Return(s string, err error) *GitRepos_HeadBranch_Call { | |||
| 309 | + | _c.Call.Return(s, err) | |||
| 310 | + | return _c | |||
| 311 | + | } | |||
| 312 | + | ||||
| 313 | + | func (_c *GitRepos_HeadBranch_Call) RunAndReturn(run func(ctx context.Context, name domain.RepoName) (string, error)) *GitRepos_HeadBranch_Call { | |||
| 314 | + | _c.Call.Return(run) | |||
| 315 | + | return _c | |||
| 316 | + | } | |||
| 317 | + | ||||
| 180 | // List provides a mock function for the type GitRepos | 318 | // List provides a mock function for the type GitRepos | ||
| 181 | func (_mock *GitRepos) List(ctx context.Context) (ports.RepoDirs, error) { | 319 | func (_mock *GitRepos) List(ctx context.Context) (ports.RepoDirs, error) { | ||
| 182 | ret := _mock.Called(ctx) | 320 | ret := _mock.Called(ctx) | ||
M internal/ports/ports.go +9 -0
| @@ -65,6 +65,15 @@ type GitRepos interface { | |||
| 65 | 65 | // and whether that branch exists. It returns domain.ErrNotFound if there | |
| 66 | 66 | // is no such repository. | |
| 67 | 67 | DefaultBranch(ctx context.Context, name domain.RepoName) (branch string, exists bool, err error) | |
| 68 | + | // HasBranch reports whether refs/heads/<branch> exists. A name that | |
| 69 | + | // cannot be a branch does not exist. It returns domain.ErrNotFound if | |
| 70 | + | // there is no such repository. | |
| 71 | + | HasBranch(ctx context.Context, name domain.RepoName, branch string) (bool, error) | |
| 72 | + | // HeadBranch returns the short name of the branch HEAD points to, whether | |
| 73 | + | // or not that branch exists, and "" when HEAD is detached. It reads no | |
| 74 | + | // refs, so it is cheap enough for a listing. It returns | |
| 75 | + | // domain.ErrNotFound if there is no such repository. | |
| 76 | + | HeadBranch(ctx context.Context, name domain.RepoName) (string, error) | |
| 68 | 77 | // SetDefaultBranch points HEAD to the branch, which need not exist yet. | |
| 69 | 78 | SetDefaultBranch(ctx context.Context, name domain.RepoName, branch string) error | |
| 70 | 79 | // Maintain refreshes the commit-graph of the repository. | |
| @@ -65,6 +65,15 @@ type GitRepos interface { | |||||
| 65 | // and whether that branch exists. It returns domain.ErrNotFound if there | 65 | // and whether that branch exists. It returns domain.ErrNotFound if there | ||
| 66 | // is no such repository. | 66 | // is no such repository. | ||
| 67 | DefaultBranch(ctx context.Context, name domain.RepoName) (branch string, exists bool, err error) | 67 | DefaultBranch(ctx context.Context, name domain.RepoName) (branch string, exists bool, err error) | ||
| 68 | + | // HasBranch reports whether refs/heads/<branch> exists. A name that | |||
| 69 | + | // cannot be a branch does not exist. It returns domain.ErrNotFound if | |||
| 70 | + | // there is no such repository. | |||
| 71 | + | HasBranch(ctx context.Context, name domain.RepoName, branch string) (bool, error) | |||
| 72 | + | // HeadBranch returns the short name of the branch HEAD points to, whether | |||
| 73 | + | // or not that branch exists, and "" when HEAD is detached. It reads no | |||
| 74 | + | // refs, so it is cheap enough for a listing. It returns | |||
| 75 | + | // domain.ErrNotFound if there is no such repository. | |||
| 76 | + | HeadBranch(ctx context.Context, name domain.RepoName) (string, error) | |||
| 68 | // SetDefaultBranch points HEAD to the branch, which need not exist yet. | 77 | // SetDefaultBranch points HEAD to the branch, which need not exist yet. | ||
| 69 | SetDefaultBranch(ctx context.Context, name domain.RepoName, branch string) error | 78 | SetDefaultBranch(ctx context.Context, name domain.RepoName, branch string) error | ||
| 70 | // Maintain refreshes the commit-graph of the repository. | 79 | // Maintain refreshes the commit-graph of the repository. | ||