feat(diff): share one window policy for MR, commit and comment reads
FileWindow gains Full, a single file read with no line cap and the byte guard only. windowLimits is the one policy for MergeRequests.Get, Browse.GetCommit and checkLine: windowed reads collapse generated files, and a line of a file that can be loaded can be commented on.
- commit
- 2a79463da35da5c4da914e77d4cce55cbb69ea6f
- author
- brice · · 2026-10-20 07:37 UTC
- committer
- brice · · 2026-10-20 09:12 UTC
- parent
- b1b81df
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
13 files · +229 -37
M internal/adapters/rpc/git.go +1 -1
| @@ -180,7 +180,7 @@ func (s *gitService) GetCommit(ctx context.Context, req *connect.Request[pb.GetC | |||
| 180 | 180 | if err != nil { | |
| 181 | 181 | return nil, connectError(ctx, err) | |
| 182 | 182 | } | |
| 183 | - | detail, err := s.browse.GetCommit(ctx, caller(ctx), name, rev) | |
| 183 | + | detail, err := s.browse.GetCommit(ctx, caller(ctx), name, rev, ports.GetCommitOptions{}) | |
| 184 | 184 | if err != nil { | |
| 185 | 185 | return nil, connectError(ctx, err) | |
| 186 | 186 | } | |
| @@ -180,7 +180,7 @@ func (s *gitService) GetCommit(ctx context.Context, req *connect.Request[pb.GetC | |||||
| 180 | if err != nil { | 180 | if err != nil { | ||
| 181 | return nil, connectError(ctx, err) | 181 | return nil, connectError(ctx, err) | ||
| 182 | } | 182 | } | ||
| 183 | - | detail, err := s.browse.GetCommit(ctx, caller(ctx), name, rev) | 183 | + | detail, err := s.browse.GetCommit(ctx, caller(ctx), name, rev, ports.GetCommitOptions{}) |
| 184 | if err != nil { | 184 | if err != nil { | ||
| 185 | return nil, connectError(ctx, err) | 185 | return nil, connectError(ctx, err) | ||
| 186 | } | 186 | } | ||
M internal/adapters/rpc/git_test.go +1 -1
| @@ -157,7 +157,7 @@ func TestGitServiceGetCommitAndResolveRevision(t *testing.T) { | |||
| 157 | 157 | t.Run("GetCommit not found", func(t *testing.T) { | |
| 158 | 158 | t.Parallel() | |
| 159 | 159 | browse := mocks.NewBrowseService(t) | |
| 160 | - | browse.EXPECT().GetCommit(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything).Return(domain.CommitDetail{}, domain.ErrNotFound).Once() | |
| 160 | + | browse.EXPECT().GetCommit(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything, mock.Anything).Return(domain.CommitDetail{}, domain.ErrNotFound).Once() | |
| 161 | 161 | _, err := (&gitService{browse}).GetCommit(ownerCtx(), connect.NewRequest(&pb.GetCommitRequest{Repo: "demo", Revision: "main"})) | |
| 162 | 162 | requireCode(t, connect.CodeNotFound, err) | |
| 163 | 163 | }) | |
| @@ -157,7 +157,7 @@ func TestGitServiceGetCommitAndResolveRevision(t *testing.T) { | |||||
| 157 | t.Run("GetCommit not found", func(t *testing.T) { | 157 | t.Run("GetCommit not found", func(t *testing.T) { | ||
| 158 | t.Parallel() | 158 | t.Parallel() | ||
| 159 | browse := mocks.NewBrowseService(t) | 159 | browse := mocks.NewBrowseService(t) | ||
| 160 | - | browse.EXPECT().GetCommit(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything).Return(domain.CommitDetail{}, domain.ErrNotFound).Once() | 160 | + | browse.EXPECT().GetCommit(mock.Anything, domain.Owner(), mustRepoName(t, "demo"), mock.Anything, mock.Anything).Return(domain.CommitDetail{}, domain.ErrNotFound).Once() |
| 161 | _, err := (&gitService{browse}).GetCommit(ownerCtx(), connect.NewRequest(&pb.GetCommitRequest{Repo: "demo", Revision: "main"})) | 161 | _, err := (&gitService{browse}).GetCommit(ownerCtx(), connect.NewRequest(&pb.GetCommitRequest{Repo: "demo", Revision: "main"})) | ||
| 162 | requireCode(t, connect.CodeNotFound, err) | 162 | requireCode(t, connect.CodeNotFound, err) | ||
| 163 | }) | 163 | }) | ||
M internal/adapters/web/browse.go +2 -2
| @@ -45,12 +45,12 @@ func (s *Server) getTree(ctx context.Context, repo domain.RepoName, rev, path st | |||
| 45 | 45 | } | |
| 46 | 46 | ||
| 47 | 47 | // getCommit reads a commit with its diff. | |
| 48 | - | func (s *Server) getCommit(ctx context.Context, repo domain.RepoName, rev string) (domain.CommitDetail, error) { | |
| 48 | + | func (s *Server) getCommit(ctx context.Context, repo domain.RepoName, rev string, opts ports.GetCommitOptions) (domain.CommitDetail, error) { | |
| 49 | 49 | r, err := domain.ParseRevision(rev) | |
| 50 | 50 | if err != nil { | |
| 51 | 51 | return domain.CommitDetail{}, err | |
| 52 | 52 | } | |
| 53 | - | return s.cfg.Browse.GetCommit(ctx, principal(ctx), repo, r) | |
| 53 | + | return s.cfg.Browse.GetCommit(ctx, principal(ctx), repo, r, opts) | |
| 54 | 54 | } | |
| 55 | 55 | ||
| 56 | 56 | // openBlobAt opens the file path of the commit rev. The caller closes the | |
| @@ -45,12 +45,12 @@ func (s *Server) getTree(ctx context.Context, repo domain.RepoName, rev, path st | |||||
| 45 | } | 45 | } | ||
| 46 | 46 | ||||
| 47 | // getCommit reads a commit with its diff. | 47 | // getCommit reads a commit with its diff. | ||
| 48 | - | func (s *Server) getCommit(ctx context.Context, repo domain.RepoName, rev string) (domain.CommitDetail, error) { | 48 | + | func (s *Server) getCommit(ctx context.Context, repo domain.RepoName, rev string, opts ports.GetCommitOptions) (domain.CommitDetail, error) { |
| 49 | r, err := domain.ParseRevision(rev) | 49 | r, err := domain.ParseRevision(rev) | ||
| 50 | if err != nil { | 50 | if err != nil { | ||
| 51 | return domain.CommitDetail{}, err | 51 | return domain.CommitDetail{}, err | ||
| 52 | } | 52 | } | ||
| 53 | - | return s.cfg.Browse.GetCommit(ctx, principal(ctx), repo, r) | 53 | + | return s.cfg.Browse.GetCommit(ctx, principal(ctx), repo, r, opts) |
| 54 | } | 54 | } | ||
| 55 | 55 | ||||
| 56 | // openBlobAt opens the file path of the commit rev. The caller closes the | 56 | // openBlobAt opens the file path of the commit rev. The caller closes the | ||
M internal/adapters/web/commit.go +2 -1
| @@ -12,6 +12,7 @@ import ( | |||
| 12 | 12 | "github.com/a-h/templ" | |
| 13 | 13 | ||
| 14 | 14 | "bnema.dev/ermite/internal/domain" | |
| 15 | + | "bnema.dev/ermite/internal/ports" | |
| 15 | 16 | ) | |
| 16 | 17 | ||
| 17 | 18 | // bothViewsLines is the largest diff, in lines, that is sent in both the | |
| @@ -105,7 +106,7 @@ func (s *Server) commit(ctx context.Context, r *http.Request) (templ.Component, | |||
| 105 | 106 | if err != nil { | |
| 106 | 107 | return nil, 0, err | |
| 107 | 108 | } | |
| 108 | - | res, err := s.getCommit(ctx, d.repo.Name, sha) | |
| 109 | + | res, err := s.getCommit(ctx, d.repo.Name, sha, ports.GetCommitOptions{}) | |
| 109 | 110 | if err != nil { | |
| 110 | 111 | return nil, 0, err | |
| 111 | 112 | } | |
| @@ -12,6 +12,7 @@ import ( | |||||
| 12 | "github.com/a-h/templ" | 12 | "github.com/a-h/templ" | ||
| 13 | 13 | ||||
| 14 | "bnema.dev/ermite/internal/domain" | 14 | "bnema.dev/ermite/internal/domain" | ||
| 15 | + | "bnema.dev/ermite/internal/ports" | |||
| 15 | ) | 16 | ) | ||
| 16 | 17 | ||||
| 17 | // bothViewsLines is the largest diff, in lines, that is sent in both the | 18 | // bothViewsLines is the largest diff, in lines, that is sent in both the | ||
| @@ -105,7 +106,7 @@ func (s *Server) commit(ctx context.Context, r *http.Request) (templ.Component, | |||||
| 105 | if err != nil { | 106 | if err != nil { | ||
| 106 | return nil, 0, err | 107 | return nil, 0, err | ||
| 107 | } | 108 | } | ||
| 108 | - | res, err := s.getCommit(ctx, d.repo.Name, sha) | 109 | + | res, err := s.getCommit(ctx, d.repo.Name, sha, ports.GetCommitOptions{}) |
| 109 | if err != nil { | 110 | if err != nil { | ||
| 110 | return nil, 0, err | 111 | return nil, 0, err | ||
| 111 | } | 112 | } | ||
M internal/adapters/web/compare.go +1 -1
| @@ -76,7 +76,7 @@ func (s *Server) compare(ctx context.Context, r *http.Request) (templ.Component, | |||
| 76 | 76 | ||
| 77 | 77 | // The exact diff: one commit on top of the tip of the target. | |
| 78 | 78 | if exact && !behind && len(ahead) == 1 { | |
| 79 | - | res, err := s.getCommit(ctx, d.repo.Name, ahead[0].ID.String()) | |
| 79 | + | res, err := s.getCommit(ctx, d.repo.Name, ahead[0].ID.String(), ports.GetCommitOptions{}) | |
| 80 | 80 | if err != nil { | |
| 81 | 81 | return nil, 0, err | |
| 82 | 82 | } | |
| @@ -76,7 +76,7 @@ func (s *Server) compare(ctx context.Context, r *http.Request) (templ.Component, | |||||
| 76 | 76 | ||||
| 77 | // The exact diff: one commit on top of the tip of the target. | 77 | // The exact diff: one commit on top of the tip of the target. | ||
| 78 | if exact && !behind && len(ahead) == 1 { | 78 | if exact && !behind && len(ahead) == 1 { | ||
| 79 | - | res, err := s.getCommit(ctx, d.repo.Name, ahead[0].ID.String()) | 79 | + | res, err := s.getCommit(ctx, d.repo.Name, ahead[0].ID.String(), ports.GetCommitOptions{}) |
| 80 | if err != nil { | 80 | if err != nil { | ||
| 81 | return nil, 0, err | 81 | return nil, 0, err | ||
| 82 | } | 82 | } | ||
M internal/ports/dto.go +20 -3
| @@ -298,8 +298,10 @@ type GetMergeRequestOptions struct { | |||
| 298 | 298 | SinceLastRead bool | |
| 299 | 299 | // Files, when set, lists every changed file, without the file-count | |
| 300 | 300 | // limit, and reads the hunks of the files in the window only, each within | |
| 301 | - | // the per-file and the total limits of domain.DefaultDiffLimits. Unset, | |
| 302 | - | // the diff is bounded by domain.DefaultDiffLimits (the API contract). | |
| 301 | + | // the per-file and the total limits of domain.DefaultDiffLimits; generated | |
| 302 | + | // files are listed collapsed (domain.OmitGenerated). With Full, the single | |
| 303 | + | // file is read without the line limits. Unset, the diff is bounded by | |
| 304 | + | // domain.DefaultDiffLimits and generated files are read (the API contract). | |
| 303 | 305 | Files *FileWindow | |
| 304 | 306 | // Base and Head, when not zero, are the diff the caller shows: Get | |
| 305 | 307 | // returns domain.ErrConflict when the request's diff base or head differ | |
| @@ -310,7 +312,22 @@ type GetMergeRequestOptions struct { | |||
| 310 | 312 | } | |
| 311 | 313 | ||
| 312 | 314 | // FileWindow is the range [From, To) of files whose hunks are read. | |
| 313 | - | type FileWindow struct{ From, To int } | |
| 315 | + | // | |
| 316 | + | // Full asks for the one file From read in full: no line limit applies, only | |
| 317 | + | // the byte guard (domain.OneFileDiffLimits). It requires To == From+1; a | |
| 318 | + | // window with Full and any other To is domain.ErrInvalid. | |
| 319 | + | type FileWindow struct { | |
| 320 | + | From, To int | |
| 321 | + | Full bool | |
| 322 | + | } | |
| 323 | + | ||
| 324 | + | // GetCommitOptions say which diff BrowseService.GetCommit reads. | |
| 325 | + | type GetCommitOptions struct { | |
| 326 | + | // Files, when set, reads the hunks of a window of the files, as | |
| 327 | + | // GetMergeRequestOptions.Files does. Unset (the zero value), the diff is | |
| 328 | + | // bounded by domain.DefaultDiffLimits. | |
| 329 | + | Files *FileWindow | |
| 330 | + | } | |
| 314 | 331 | ||
| 315 | 332 | // AddCommentParams describes the comment to add. | |
| 316 | 333 | type AddCommentParams struct { | |
| @@ -298,8 +298,10 @@ type GetMergeRequestOptions struct { | |||||
| 298 | SinceLastRead bool | 298 | SinceLastRead bool | ||
| 299 | // Files, when set, lists every changed file, without the file-count | 299 | // Files, when set, lists every changed file, without the file-count | ||
| 300 | // limit, and reads the hunks of the files in the window only, each within | 300 | // limit, and reads the hunks of the files in the window only, each within | ||
| 301 | - | // the per-file and the total limits of domain.DefaultDiffLimits. Unset, | 301 | + | // the per-file and the total limits of domain.DefaultDiffLimits; generated |
| 302 | - | // the diff is bounded by domain.DefaultDiffLimits (the API contract). | 302 | + | // files are listed collapsed (domain.OmitGenerated). With Full, the single |
| 303 | + | // file is read without the line limits. Unset, the diff is bounded by | |||
| 304 | + | // domain.DefaultDiffLimits and generated files are read (the API contract). | |||
| 303 | Files *FileWindow | 305 | Files *FileWindow | ||
| 304 | // Base and Head, when not zero, are the diff the caller shows: Get | 306 | // Base and Head, when not zero, are the diff the caller shows: Get | ||
| 305 | // returns domain.ErrConflict when the request's diff base or head differ | 307 | // returns domain.ErrConflict when the request's diff base or head differ | ||
| @@ -310,7 +312,22 @@ type GetMergeRequestOptions struct { | |||||
| 310 | } | 312 | } | ||
| 311 | 313 | ||||
| 312 | // FileWindow is the range [From, To) of files whose hunks are read. | 314 | // FileWindow is the range [From, To) of files whose hunks are read. | ||
| 313 | - | type FileWindow struct{ From, To int } | 315 | + | // |
| 316 | + | // Full asks for the one file From read in full: no line limit applies, only | |||
| 317 | + | // the byte guard (domain.OneFileDiffLimits). It requires To == From+1; a | |||
| 318 | + | // window with Full and any other To is domain.ErrInvalid. | |||
| 319 | + | type FileWindow struct { | |||
| 320 | + | From, To int | |||
| 321 | + | Full bool | |||
| 322 | + | } | |||
| 323 | + | ||||
| 324 | + | // GetCommitOptions say which diff BrowseService.GetCommit reads. | |||
| 325 | + | type GetCommitOptions struct { | |||
| 326 | + | // Files, when set, reads the hunks of a window of the files, as | |||
| 327 | + | // GetMergeRequestOptions.Files does. Unset (the zero value), the diff is | |||
| 328 | + | // bounded by domain.DefaultDiffLimits. | |||
| 329 | + | Files *FileWindow | |||
| 330 | + | } | |||
| 314 | 331 | ||||
| 315 | // AddCommentParams describes the comment to add. | 332 | // AddCommentParams describes the comment to add. | ||
| 316 | type AddCommentParams struct { | 333 | type AddCommentParams struct { | ||
M internal/ports/mocks/BrowseService.go +18 -12
| @@ -51,8 +51,8 @@ func (_m *BrowseService) EXPECT() *BrowseService_Expecter { | |||
| 51 | 51 | } | |
| 52 | 52 | ||
| 53 | 53 | // GetCommit provides a mock function for the type BrowseService | |
| 54 | - | func (_mock *BrowseService) GetCommit(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision) (domain.CommitDetail, error) { | |
| 55 | - | ret := _mock.Called(ctx, p, repo, rev) | |
| 54 | + | func (_mock *BrowseService) GetCommit(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, opts ports.GetCommitOptions) (domain.CommitDetail, error) { | |
| 55 | + | ret := _mock.Called(ctx, p, repo, rev, opts) | |
| 56 | 56 | ||
| 57 | 57 | if len(ret) == 0 { | |
| 58 | 58 | panic("no return value specified for GetCommit") | |
| @@ -60,16 +60,16 @@ func (_mock *BrowseService) GetCommit(ctx context.Context, p domain.Principal, r | |||
| 60 | 60 | ||
| 61 | 61 | var r0 domain.CommitDetail | |
| 62 | 62 | var r1 error | |
| 63 | - | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, domain.Revision) (domain.CommitDetail, error)); ok { | |
| 64 | - | return returnFunc(ctx, p, repo, rev) | |
| 63 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, domain.Revision, ports.GetCommitOptions) (domain.CommitDetail, error)); ok { | |
| 64 | + | return returnFunc(ctx, p, repo, rev, opts) | |
| 65 | 65 | } | |
| 66 | - | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, domain.Revision) domain.CommitDetail); ok { | |
| 67 | - | r0 = returnFunc(ctx, p, repo, rev) | |
| 66 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, domain.Revision, ports.GetCommitOptions) domain.CommitDetail); ok { | |
| 67 | + | r0 = returnFunc(ctx, p, repo, rev, opts) | |
| 68 | 68 | } else { | |
| 69 | 69 | r0 = ret.Get(0).(domain.CommitDetail) | |
| 70 | 70 | } | |
| 71 | - | if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, domain.Revision) error); ok { | |
| 72 | - | r1 = returnFunc(ctx, p, repo, rev) | |
| 71 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, domain.Revision, ports.GetCommitOptions) error); ok { | |
| 72 | + | r1 = returnFunc(ctx, p, repo, rev, opts) | |
| 73 | 73 | } else { | |
| 74 | 74 | r1 = ret.Error(1) | |
| 75 | 75 | } | |
| @@ -86,11 +86,12 @@ type BrowseService_GetCommit_Call struct { | |||
| 86 | 86 | // - p domain.Principal | |
| 87 | 87 | // - repo domain.RepoName | |
| 88 | 88 | // - rev domain.Revision | |
| 89 | - | func (_e *BrowseService_Expecter) GetCommit(ctx any, p any, repo any, rev any) *BrowseService_GetCommit_Call { | |
| 90 | - | return &BrowseService_GetCommit_Call{Call: _e.mock.On("GetCommit", ctx, p, repo, rev)} | |
| 89 | + | // - opts ports.GetCommitOptions | |
| 90 | + | func (_e *BrowseService_Expecter) GetCommit(ctx any, p any, repo any, rev any, opts any) *BrowseService_GetCommit_Call { | |
| 91 | + | return &BrowseService_GetCommit_Call{Call: _e.mock.On("GetCommit", ctx, p, repo, rev, opts)} | |
| 91 | 92 | } | |
| 92 | 93 | ||
| 93 | - | func (_c *BrowseService_GetCommit_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision)) *BrowseService_GetCommit_Call { | |
| 94 | + | func (_c *BrowseService_GetCommit_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, opts ports.GetCommitOptions)) *BrowseService_GetCommit_Call { | |
| 94 | 95 | _c.Call.Run(func(args mock.Arguments) { | |
| 95 | 96 | var arg0 context.Context | |
| 96 | 97 | if args[0] != nil { | |
| @@ -108,11 +109,16 @@ func (_c *BrowseService_GetCommit_Call) Run(run func(ctx context.Context, p doma | |||
| 108 | 109 | if args[3] != nil { | |
| 109 | 110 | arg3 = args[3].(domain.Revision) | |
| 110 | 111 | } | |
| 112 | + | var arg4 ports.GetCommitOptions | |
| 113 | + | if args[4] != nil { | |
| 114 | + | arg4 = args[4].(ports.GetCommitOptions) | |
| 115 | + | } | |
| 111 | 116 | run( | |
| 112 | 117 | arg0, | |
| 113 | 118 | arg1, | |
| 114 | 119 | arg2, | |
| 115 | 120 | arg3, | |
| 121 | + | arg4, | |
| 116 | 122 | ) | |
| 117 | 123 | }) | |
| 118 | 124 | return _c | |
| @@ -123,7 +129,7 @@ func (_c *BrowseService_GetCommit_Call) Return(commitDetail domain.CommitDetail, | |||
| 123 | 129 | return _c | |
| 124 | 130 | } | |
| 125 | 131 | ||
| 126 | - | func (_c *BrowseService_GetCommit_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision) (domain.CommitDetail, error)) *BrowseService_GetCommit_Call { | |
| 132 | + | func (_c *BrowseService_GetCommit_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, opts ports.GetCommitOptions) (domain.CommitDetail, error)) *BrowseService_GetCommit_Call { | |
| 127 | 133 | _c.Call.Return(run) | |
| 128 | 134 | return _c | |
| 129 | 135 | } | |
| @@ -51,8 +51,8 @@ func (_m *BrowseService) EXPECT() *BrowseService_Expecter { | |||||
| 51 | } | 51 | } | ||
| 52 | 52 | ||||
| 53 | // GetCommit provides a mock function for the type BrowseService | 53 | // GetCommit provides a mock function for the type BrowseService | ||
| 54 | - | func (_mock *BrowseService) GetCommit(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision) (domain.CommitDetail, error) { | 54 | + | func (_mock *BrowseService) GetCommit(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, opts ports.GetCommitOptions) (domain.CommitDetail, error) { |
| 55 | - | ret := _mock.Called(ctx, p, repo, rev) | 55 | + | ret := _mock.Called(ctx, p, repo, rev, opts) |
| 56 | 56 | ||||
| 57 | if len(ret) == 0 { | 57 | if len(ret) == 0 { | ||
| 58 | panic("no return value specified for GetCommit") | 58 | panic("no return value specified for GetCommit") | ||
| @@ -60,16 +60,16 @@ func (_mock *BrowseService) GetCommit(ctx context.Context, p domain.Principal, r | |||||
| 60 | 60 | ||||
| 61 | var r0 domain.CommitDetail | 61 | var r0 domain.CommitDetail | ||
| 62 | var r1 error | 62 | var r1 error | ||
| 63 | - | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, domain.Revision) (domain.CommitDetail, error)); ok { | 63 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, domain.Revision, ports.GetCommitOptions) (domain.CommitDetail, error)); ok { |
| 64 | - | return returnFunc(ctx, p, repo, rev) | 64 | + | return returnFunc(ctx, p, repo, rev, opts) |
| 65 | } | 65 | } | ||
| 66 | - | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, domain.Revision) domain.CommitDetail); ok { | 66 | + | if returnFunc, ok := ret.Get(0).(func(context.Context, domain.Principal, domain.RepoName, domain.Revision, ports.GetCommitOptions) domain.CommitDetail); ok { |
| 67 | - | r0 = returnFunc(ctx, p, repo, rev) | 67 | + | r0 = returnFunc(ctx, p, repo, rev, opts) |
| 68 | } else { | 68 | } else { | ||
| 69 | r0 = ret.Get(0).(domain.CommitDetail) | 69 | r0 = ret.Get(0).(domain.CommitDetail) | ||
| 70 | } | 70 | } | ||
| 71 | - | if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, domain.Revision) error); ok { | 71 | + | if returnFunc, ok := ret.Get(1).(func(context.Context, domain.Principal, domain.RepoName, domain.Revision, ports.GetCommitOptions) error); ok { |
| 72 | - | r1 = returnFunc(ctx, p, repo, rev) | 72 | + | r1 = returnFunc(ctx, p, repo, rev, opts) |
| 73 | } else { | 73 | } else { | ||
| 74 | r1 = ret.Error(1) | 74 | r1 = ret.Error(1) | ||
| 75 | } | 75 | } | ||
| @@ -86,11 +86,12 @@ type BrowseService_GetCommit_Call struct { | |||||
| 86 | // - p domain.Principal | 86 | // - p domain.Principal | ||
| 87 | // - repo domain.RepoName | 87 | // - repo domain.RepoName | ||
| 88 | // - rev domain.Revision | 88 | // - rev domain.Revision | ||
| 89 | - | func (_e *BrowseService_Expecter) GetCommit(ctx any, p any, repo any, rev any) *BrowseService_GetCommit_Call { | 89 | + | // - opts ports.GetCommitOptions |
| 90 | - | return &BrowseService_GetCommit_Call{Call: _e.mock.On("GetCommit", ctx, p, repo, rev)} | 90 | + | func (_e *BrowseService_Expecter) GetCommit(ctx any, p any, repo any, rev any, opts any) *BrowseService_GetCommit_Call { |
| 91 | + | return &BrowseService_GetCommit_Call{Call: _e.mock.On("GetCommit", ctx, p, repo, rev, opts)} | |||
| 91 | } | 92 | } | ||
| 92 | 93 | ||||
| 93 | - | func (_c *BrowseService_GetCommit_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision)) *BrowseService_GetCommit_Call { | 94 | + | func (_c *BrowseService_GetCommit_Call) Run(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, opts ports.GetCommitOptions)) *BrowseService_GetCommit_Call { |
| 94 | _c.Call.Run(func(args mock.Arguments) { | 95 | _c.Call.Run(func(args mock.Arguments) { | ||
| 95 | var arg0 context.Context | 96 | var arg0 context.Context | ||
| 96 | if args[0] != nil { | 97 | if args[0] != nil { | ||
| @@ -108,11 +109,16 @@ func (_c *BrowseService_GetCommit_Call) Run(run func(ctx context.Context, p doma | |||||
| 108 | if args[3] != nil { | 109 | if args[3] != nil { | ||
| 109 | arg3 = args[3].(domain.Revision) | 110 | arg3 = args[3].(domain.Revision) | ||
| 110 | } | 111 | } | ||
| 112 | + | var arg4 ports.GetCommitOptions | |||
| 113 | + | if args[4] != nil { | |||
| 114 | + | arg4 = args[4].(ports.GetCommitOptions) | |||
| 115 | + | } | |||
| 111 | run( | 116 | run( | ||
| 112 | arg0, | 117 | arg0, | ||
| 113 | arg1, | 118 | arg1, | ||
| 114 | arg2, | 119 | arg2, | ||
| 115 | arg3, | 120 | arg3, | ||
| 121 | + | arg4, | |||
| 116 | ) | 122 | ) | ||
| 117 | }) | 123 | }) | ||
| 118 | return _c | 124 | return _c | ||
| @@ -123,7 +129,7 @@ func (_c *BrowseService_GetCommit_Call) Return(commitDetail domain.CommitDetail, | |||||
| 123 | return _c | 129 | return _c | ||
| 124 | } | 130 | } | ||
| 125 | 131 | ||||
| 126 | - | func (_c *BrowseService_GetCommit_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision) (domain.CommitDetail, error)) *BrowseService_GetCommit_Call { | 132 | + | func (_c *BrowseService_GetCommit_Call) RunAndReturn(run func(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, opts ports.GetCommitOptions) (domain.CommitDetail, error)) *BrowseService_GetCommit_Call { |
| 127 | _c.Call.Return(run) | 133 | _c.Call.Return(run) | ||
| 128 | return _c | 134 | return _c | ||
| 129 | } | 135 | } | ||
M internal/ports/services.go +1 -1
| @@ -95,7 +95,7 @@ type BrowseService interface { | |||
| 95 | 95 | ListPaths(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision) (domain.PathList, error) | |
| 96 | 96 | OpenBlob(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, path domain.TreePath) (domain.BlobInfo, io.ReadCloser, error) | |
| 97 | 97 | ListCommits(ctx context.Context, p domain.Principal, repo domain.RepoName, params ListCommitsParams) (HistoryPage, error) | |
| 98 | - | GetCommit(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision) (domain.CommitDetail, error) | |
| 98 | + | GetCommit(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, opts GetCommitOptions) (domain.CommitDetail, error) | |
| 99 | 99 | // RepoStats counts the commits of the branch whose head is given, and | |
| 100 | 100 | // names the latest tag. | |
| 101 | 101 | RepoStats(ctx context.Context, p domain.Principal, repo domain.RepoName, head domain.ObjectID, now time.Time) (RepoStats, error) | |
| @@ -95,7 +95,7 @@ type BrowseService interface { | |||||
| 95 | ListPaths(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision) (domain.PathList, error) | 95 | ListPaths(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision) (domain.PathList, error) | ||
| 96 | OpenBlob(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, path domain.TreePath) (domain.BlobInfo, io.ReadCloser, error) | 96 | OpenBlob(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, path domain.TreePath) (domain.BlobInfo, io.ReadCloser, error) | ||
| 97 | ListCommits(ctx context.Context, p domain.Principal, repo domain.RepoName, params ListCommitsParams) (HistoryPage, error) | 97 | ListCommits(ctx context.Context, p domain.Principal, repo domain.RepoName, params ListCommitsParams) (HistoryPage, error) | ||
| 98 | - | GetCommit(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision) (domain.CommitDetail, error) | 98 | + | GetCommit(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, opts GetCommitOptions) (domain.CommitDetail, error) |
| 99 | // RepoStats counts the commits of the branch whose head is given, and | 99 | // RepoStats counts the commits of the branch whose head is given, and | ||
| 100 | // names the latest tag. | 100 | // names the latest tag. | ||
| 101 | RepoStats(ctx context.Context, p domain.Principal, repo domain.RepoName, head domain.ObjectID, now time.Time) (RepoStats, error) | 101 | RepoStats(ctx context.Context, p domain.Principal, repo domain.RepoName, head domain.ObjectID, now time.Time) (RepoStats, error) | ||
M internal/usecase/browse.go +8 -3
| @@ -336,12 +336,17 @@ func decodeCommitToken(tok string, path domain.TreePath) (domain.ObjectID, int, | |||
| 336 | 336 | } | |
| 337 | 337 | ||
| 338 | 338 | // GetCommit returns a commit with its statistics and its diff against the | |
| 339 | - | // first parent, within the default diff limits. | |
| 340 | - | func (b *Browse) GetCommit(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision) (domain.CommitDetail, error) { | |
| 339 | + | // first parent, within the default diff limits or, with opts.Files, the limits | |
| 340 | + | // of that file window. An invalid window gives domain.ErrInvalid. | |
| 341 | + | func (b *Browse) GetCommit(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, opts ports.GetCommitOptions) (domain.CommitDetail, error) { | |
| 342 | + | limits, err := windowLimits(opts.Files) | |
| 343 | + | if err != nil { | |
| 344 | + | return domain.CommitDetail{}, err | |
| 345 | + | } | |
| 341 | 346 | if _, err := b.authorize(ctx, p, repo); err != nil { | |
| 342 | 347 | return domain.CommitDetail{}, err | |
| 343 | 348 | } | |
| 344 | - | d, err := b.reader.GetCommit(ctx, repo, rev, domain.DefaultDiffLimits()) | |
| 349 | + | d, err := b.reader.GetCommit(ctx, repo, rev, limits) | |
| 345 | 350 | if err != nil { | |
| 346 | 351 | return domain.CommitDetail{}, err | |
| 347 | 352 | } | |
| @@ -336,12 +336,17 @@ func decodeCommitToken(tok string, path domain.TreePath) (domain.ObjectID, int, | |||||
| 336 | } | 336 | } | ||
| 337 | 337 | ||||
| 338 | // GetCommit returns a commit with its statistics and its diff against the | 338 | // GetCommit returns a commit with its statistics and its diff against the | ||
| 339 | - | // first parent, within the default diff limits. | 339 | + | // first parent, within the default diff limits or, with opts.Files, the limits |
| 340 | - | func (b *Browse) GetCommit(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision) (domain.CommitDetail, error) { | 340 | + | // of that file window. An invalid window gives domain.ErrInvalid. |
| 341 | + | func (b *Browse) GetCommit(ctx context.Context, p domain.Principal, repo domain.RepoName, rev domain.Revision, opts ports.GetCommitOptions) (domain.CommitDetail, error) { | |||
| 342 | + | limits, err := windowLimits(opts.Files) | |||
| 343 | + | if err != nil { | |||
| 344 | + | return domain.CommitDetail{}, err | |||
| 345 | + | } | |||
| 341 | if _, err := b.authorize(ctx, p, repo); err != nil { | 346 | if _, err := b.authorize(ctx, p, repo); err != nil { | ||
| 342 | return domain.CommitDetail{}, err | 347 | return domain.CommitDetail{}, err | ||
| 343 | } | 348 | } | ||
| 344 | - | d, err := b.reader.GetCommit(ctx, repo, rev, domain.DefaultDiffLimits()) | 349 | + | d, err := b.reader.GetCommit(ctx, repo, rev, limits) |
| 345 | if err != nil { | 350 | if err != nil { | ||
| 346 | return domain.CommitDetail{}, err | 351 | return domain.CommitDetail{}, err | ||
| 347 | } | 352 | } | ||
M internal/usecase/browse_test.go +4 -4
| @@ -104,7 +104,7 @@ func browseOps() []browseOp { | |||
| 104 | 104 | r.EXPECT().ListCommits(mock.Anything, mock.Anything, mock.Anything).Return(ports.CommitPage{}, nil) | |
| 105 | 105 | }}, | |
| 106 | 106 | {"GetCommit", func(b *usecase.Browse, ctx context.Context, p domain.Principal, repo domain.RepoName) error { | |
| 107 | - | _, err := b.GetCommit(ctx, p, repo, domain.Revision{}) | |
| 107 | + | _, err := b.GetCommit(ctx, p, repo, domain.Revision{}, ports.GetCommitOptions{}) | |
| 108 | 108 | return err | |
| 109 | 109 | }, func(r *mocks.RepoReader) { | |
| 110 | 110 | r.EXPECT().GetCommit(mock.Anything, mock.Anything, mock.Anything, domain.DefaultDiffLimits()).Return(domain.CommitDetail{}, nil) | |
| @@ -629,7 +629,7 @@ func TestBrowseVerifiesCommits(t *testing.T) { | |||
| 629 | 629 | Return(domain.CommitDetail{Commit: c, Stats: domain.DiffStats{FilesChanged: 2}}, nil) | |
| 630 | 630 | v.EXPECT().VerifyCommits(ctx, pub.Name, []domain.Commit{c}). | |
| 631 | 631 | Return(map[domain.ObjectID]domain.CommitVerification{oid("a"): verified}) | |
| 632 | - | d, err := f.browse.GetCommit(ctx, domain.Anonymous(), pub.Name, domain.Revision{}) | |
| 632 | + | d, err := f.browse.GetCommit(ctx, domain.Anonymous(), pub.Name, domain.Revision{}, ports.GetCommitOptions{}) | |
| 633 | 633 | require.NoError(t, err) | |
| 634 | 634 | assert.Equal(t, verified, d.Commit.Verification) | |
| 635 | 635 | assert.Equal(t, 2, d.Stats.FilesChanged) | |
| @@ -642,7 +642,7 @@ func TestBrowseVerifiesCommits(t *testing.T) { | |||
| 642 | 642 | _, err := f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name, ports.ListCommitsParams{}) | |
| 643 | 643 | require.NoError(t, err) | |
| 644 | 644 | f.reader.EXPECT().GetCommit(ctx, pub.Name, mock.Anything, mock.Anything).Return(domain.CommitDetail{}, domain.ErrNotFound) | |
| 645 | - | _, err = f.browse.GetCommit(ctx, domain.Anonymous(), pub.Name, domain.Revision{}) | |
| 645 | + | _, err = f.browse.GetCommit(ctx, domain.Anonymous(), pub.Name, domain.Revision{}, ports.GetCommitOptions{}) | |
| 646 | 646 | assert.ErrorIs(t, err, domain.ErrNotFound) | |
| 647 | 647 | }) | |
| 648 | 648 | ||
| @@ -657,7 +657,7 @@ func TestBrowseVerifiesCommits(t *testing.T) { | |||
| 657 | 657 | page, err := f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name, ports.ListCommitsParams{}) | |
| 658 | 658 | require.NoError(t, err) | |
| 659 | 659 | assert.Equal(t, domain.CommitVerification{}, page.Commits[0].Verification) | |
| 660 | - | d, err := f.browse.GetCommit(ctx, domain.Anonymous(), pub.Name, domain.Revision{}) | |
| 660 | + | d, err := f.browse.GetCommit(ctx, domain.Anonymous(), pub.Name, domain.Revision{}, ports.GetCommitOptions{}) | |
| 661 | 661 | require.NoError(t, err) | |
| 662 | 662 | assert.Equal(t, domain.CommitVerification{}, d.Commit.Verification) | |
| 663 | 663 | }) | |
| @@ -104,7 +104,7 @@ func browseOps() []browseOp { | |||||
| 104 | r.EXPECT().ListCommits(mock.Anything, mock.Anything, mock.Anything).Return(ports.CommitPage{}, nil) | 104 | r.EXPECT().ListCommits(mock.Anything, mock.Anything, mock.Anything).Return(ports.CommitPage{}, nil) | ||
| 105 | }}, | 105 | }}, | ||
| 106 | {"GetCommit", func(b *usecase.Browse, ctx context.Context, p domain.Principal, repo domain.RepoName) error { | 106 | {"GetCommit", func(b *usecase.Browse, ctx context.Context, p domain.Principal, repo domain.RepoName) error { | ||
| 107 | - | _, err := b.GetCommit(ctx, p, repo, domain.Revision{}) | 107 | + | _, err := b.GetCommit(ctx, p, repo, domain.Revision{}, ports.GetCommitOptions{}) |
| 108 | return err | 108 | return err | ||
| 109 | }, func(r *mocks.RepoReader) { | 109 | }, func(r *mocks.RepoReader) { | ||
| 110 | r.EXPECT().GetCommit(mock.Anything, mock.Anything, mock.Anything, domain.DefaultDiffLimits()).Return(domain.CommitDetail{}, nil) | 110 | r.EXPECT().GetCommit(mock.Anything, mock.Anything, mock.Anything, domain.DefaultDiffLimits()).Return(domain.CommitDetail{}, nil) | ||
| @@ -629,7 +629,7 @@ func TestBrowseVerifiesCommits(t *testing.T) { | |||||
| 629 | Return(domain.CommitDetail{Commit: c, Stats: domain.DiffStats{FilesChanged: 2}}, nil) | 629 | Return(domain.CommitDetail{Commit: c, Stats: domain.DiffStats{FilesChanged: 2}}, nil) | ||
| 630 | v.EXPECT().VerifyCommits(ctx, pub.Name, []domain.Commit{c}). | 630 | v.EXPECT().VerifyCommits(ctx, pub.Name, []domain.Commit{c}). | ||
| 631 | Return(map[domain.ObjectID]domain.CommitVerification{oid("a"): verified}) | 631 | Return(map[domain.ObjectID]domain.CommitVerification{oid("a"): verified}) | ||
| 632 | - | d, err := f.browse.GetCommit(ctx, domain.Anonymous(), pub.Name, domain.Revision{}) | 632 | + | d, err := f.browse.GetCommit(ctx, domain.Anonymous(), pub.Name, domain.Revision{}, ports.GetCommitOptions{}) |
| 633 | require.NoError(t, err) | 633 | require.NoError(t, err) | ||
| 634 | assert.Equal(t, verified, d.Commit.Verification) | 634 | assert.Equal(t, verified, d.Commit.Verification) | ||
| 635 | assert.Equal(t, 2, d.Stats.FilesChanged) | 635 | assert.Equal(t, 2, d.Stats.FilesChanged) | ||
| @@ -642,7 +642,7 @@ func TestBrowseVerifiesCommits(t *testing.T) { | |||||
| 642 | _, err := f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name, ports.ListCommitsParams{}) | 642 | _, err := f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name, ports.ListCommitsParams{}) | ||
| 643 | require.NoError(t, err) | 643 | require.NoError(t, err) | ||
| 644 | f.reader.EXPECT().GetCommit(ctx, pub.Name, mock.Anything, mock.Anything).Return(domain.CommitDetail{}, domain.ErrNotFound) | 644 | f.reader.EXPECT().GetCommit(ctx, pub.Name, mock.Anything, mock.Anything).Return(domain.CommitDetail{}, domain.ErrNotFound) | ||
| 645 | - | _, err = f.browse.GetCommit(ctx, domain.Anonymous(), pub.Name, domain.Revision{}) | 645 | + | _, err = f.browse.GetCommit(ctx, domain.Anonymous(), pub.Name, domain.Revision{}, ports.GetCommitOptions{}) |
| 646 | assert.ErrorIs(t, err, domain.ErrNotFound) | 646 | assert.ErrorIs(t, err, domain.ErrNotFound) | ||
| 647 | }) | 647 | }) | ||
| 648 | 648 | ||||
| @@ -657,7 +657,7 @@ func TestBrowseVerifiesCommits(t *testing.T) { | |||||
| 657 | page, err := f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name, ports.ListCommitsParams{}) | 657 | page, err := f.browse.ListCommits(ctx, domain.Anonymous(), pub.Name, ports.ListCommitsParams{}) | ||
| 658 | require.NoError(t, err) | 658 | require.NoError(t, err) | ||
| 659 | assert.Equal(t, domain.CommitVerification{}, page.Commits[0].Verification) | 659 | assert.Equal(t, domain.CommitVerification{}, page.Commits[0].Verification) | ||
| 660 | - | d, err := f.browse.GetCommit(ctx, domain.Anonymous(), pub.Name, domain.Revision{}) | 660 | + | d, err := f.browse.GetCommit(ctx, domain.Anonymous(), pub.Name, domain.Revision{}, ports.GetCommitOptions{}) |
| 661 | require.NoError(t, err) | 661 | require.NoError(t, err) | ||
| 662 | assert.Equal(t, domain.CommitVerification{}, d.Commit.Verification) | 662 | assert.Equal(t, domain.CommitVerification{}, d.Commit.Verification) | ||
| 663 | }) | 663 | }) | ||
M internal/usecase/export_test.go +3 -0
| @@ -217,3 +217,6 @@ func (s *Mergeability) RetryDueForTest(repo domain.RepoName) (time.Time, bool) { | |||
| 217 | 217 | func (s *Mergeability) PopDueForTest(repo domain.RepoName, due time.Time) []domain.RepoName { | |
| 218 | 218 | return s.popDue(retryItem{key: repo, due: due}) | |
| 219 | 219 | } | |
| 220 | + | ||
| 221 | + | // WindowLimits exposes windowLimits to the tests. | |
| 222 | + | var WindowLimits = windowLimits | |
| @@ -217,3 +217,6 @@ func (s *Mergeability) RetryDueForTest(repo domain.RepoName) (time.Time, bool) { | |||||
| 217 | func (s *Mergeability) PopDueForTest(repo domain.RepoName, due time.Time) []domain.RepoName { | 217 | func (s *Mergeability) PopDueForTest(repo domain.RepoName, due time.Time) []domain.RepoName { | ||
| 218 | return s.popDue(retryItem{key: repo, due: due}) | 218 | return s.popDue(retryItem{key: repo, due: due}) | ||
| 219 | } | 219 | } | ||
| 220 | + | ||||
| 221 | + | // WindowLimits exposes windowLimits to the tests. | |||
| 222 | + | var WindowLimits = windowLimits | |||
M internal/usecase/mergerequests.go +27 -8
| @@ -493,6 +493,26 @@ func (m *MergeRequests) get(ctx context.Context, p domain.Principal, name domain | |||
| 493 | 493 | return repo, mr, nil | |
| 494 | 494 | } | |
| 495 | 495 | ||
| 496 | + | // windowLimits is the diff limits of a read: nil gives | |
| 497 | + | // domain.DefaultDiffLimits (the API contract, generated files read); a window | |
| 498 | + | // lists every file (MaxFiles 0), reads From..To and skips generated files; a | |
| 499 | + | // Full window reads the one file From with domain.OneFileDiffLimits. An invalid | |
| 500 | + | // window (negative, To < From, Full without To == From+1) is domain.ErrInvalid. | |
| 501 | + | func windowLimits(w *ports.FileWindow) (domain.DiffLimits, error) { | |
| 502 | + | if w == nil { | |
| 503 | + | return domain.DefaultDiffLimits(), nil | |
| 504 | + | } | |
| 505 | + | if w.From < 0 || w.To < 0 || (w.To != 0 && w.To < w.From) || (w.Full && w.To != w.From+1) { | |
| 506 | + | return domain.DiffLimits{}, fmt.Errorf("%w: file window [%d, %d) full=%t", domain.ErrInvalid, w.From, w.To, w.Full) | |
| 507 | + | } | |
| 508 | + | if w.Full { | |
| 509 | + | return domain.OneFileDiffLimits(w.From), nil | |
| 510 | + | } | |
| 511 | + | l := domain.DefaultDiffLimits() | |
| 512 | + | l.MaxFiles, l.From, l.To, l.SkipGenerated = 0, w.From, w.To, true | |
| 513 | + | return l, nil | |
| 514 | + | } | |
| 515 | + | ||
| 496 | 516 | // Get returns a merge request with its commits, its diff, its comments and the | |
| 497 | 517 | // read position. The diff compares the merge base of the target and the head | |
| 498 | 518 | // with the head ("target...head"), within domain.DefaultDiffLimits. With | |
| @@ -507,12 +527,9 @@ func (m *MergeRequests) get(ctx context.Context, p domain.Principal, name domain | |||
| 507 | 527 | // diff is no longer the one the caller shows (before any diff is read); | |
| 508 | 528 | // opts.DiffOnly skips the commits. An invalid window gives domain.ErrInvalid. | |
| 509 | 529 | func (m *MergeRequests) Get(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, opts ports.GetMergeRequestOptions) (ports.MergeRequestDetail, error) { | |
| 510 | - | limits := domain.DefaultDiffLimits() | |
| 511 | - | if w := opts.Files; w != nil { | |
| 512 | - | if w.From < 0 || w.To < 0 || (w.To != 0 && w.To < w.From) { | |
| 513 | - | return ports.MergeRequestDetail{}, fmt.Errorf("%w: file window [%d, %d)", domain.ErrInvalid, w.From, w.To) | |
| 514 | - | } | |
| 515 | - | limits.MaxFiles, limits.From, limits.To = 0, w.From, w.To | |
| 530 | + | limits, err := windowLimits(opts.Files) | |
| 531 | + | if err != nil { | |
| 532 | + | return ports.MergeRequestDetail{}, err | |
| 516 | 533 | } | |
| 517 | 534 | _, mr, err := m.get(ctx, p, name, number) | |
| 518 | 535 | if err != nil { | |
| @@ -773,8 +790,10 @@ func (m *MergeRequests) checkLine(ctx context.Context, mr domain.MergeRequest, p | |||
| 773 | 790 | } | |
| 774 | 791 | idx := slices.IndexFunc(diff.Files, func(f domain.FileDiff) bool { return f.CommentPath() == params.Path }) | |
| 775 | 792 | if idx >= 0 { | |
| 776 | - | one := domain.DefaultDiffLimits() | |
| 777 | - | one.MaxFiles, one.From, one.To = 0, idx, idx+1 | |
| 793 | + | one, err := windowLimits(&ports.FileWindow{From: idx, To: idx + 1, Full: true}) | |
| 794 | + | if err != nil { | |
| 795 | + | return err | |
| 796 | + | } | |
| 778 | 797 | if _, diff, err = m.git.DiffCommits(ctx, mr.Repo, base, mr.HeadID, one); err != nil { | |
| 779 | 798 | return err | |
| 780 | 799 | } | |
| @@ -493,6 +493,26 @@ func (m *MergeRequests) get(ctx context.Context, p domain.Principal, name domain | |||||
| 493 | return repo, mr, nil | 493 | return repo, mr, nil | ||
| 494 | } | 494 | } | ||
| 495 | 495 | ||||
| 496 | + | // windowLimits is the diff limits of a read: nil gives | |||
| 497 | + | // domain.DefaultDiffLimits (the API contract, generated files read); a window | |||
| 498 | + | // lists every file (MaxFiles 0), reads From..To and skips generated files; a | |||
| 499 | + | // Full window reads the one file From with domain.OneFileDiffLimits. An invalid | |||
| 500 | + | // window (negative, To < From, Full without To == From+1) is domain.ErrInvalid. | |||
| 501 | + | func windowLimits(w *ports.FileWindow) (domain.DiffLimits, error) { | |||
| 502 | + | if w == nil { | |||
| 503 | + | return domain.DefaultDiffLimits(), nil | |||
| 504 | + | } | |||
| 505 | + | if w.From < 0 || w.To < 0 || (w.To != 0 && w.To < w.From) || (w.Full && w.To != w.From+1) { | |||
| 506 | + | return domain.DiffLimits{}, fmt.Errorf("%w: file window [%d, %d) full=%t", domain.ErrInvalid, w.From, w.To, w.Full) | |||
| 507 | + | } | |||
| 508 | + | if w.Full { | |||
| 509 | + | return domain.OneFileDiffLimits(w.From), nil | |||
| 510 | + | } | |||
| 511 | + | l := domain.DefaultDiffLimits() | |||
| 512 | + | l.MaxFiles, l.From, l.To, l.SkipGenerated = 0, w.From, w.To, true | |||
| 513 | + | return l, nil | |||
| 514 | + | } | |||
| 515 | + | ||||
| 496 | // Get returns a merge request with its commits, its diff, its comments and the | 516 | // Get returns a merge request with its commits, its diff, its comments and the | ||
| 497 | // read position. The diff compares the merge base of the target and the head | 517 | // read position. The diff compares the merge base of the target and the head | ||
| 498 | // with the head ("target...head"), within domain.DefaultDiffLimits. With | 518 | // with the head ("target...head"), within domain.DefaultDiffLimits. With | ||
| @@ -507,12 +527,9 @@ func (m *MergeRequests) get(ctx context.Context, p domain.Principal, name domain | |||||
| 507 | // diff is no longer the one the caller shows (before any diff is read); | 527 | // diff is no longer the one the caller shows (before any diff is read); | ||
| 508 | // opts.DiffOnly skips the commits. An invalid window gives domain.ErrInvalid. | 528 | // opts.DiffOnly skips the commits. An invalid window gives domain.ErrInvalid. | ||
| 509 | func (m *MergeRequests) Get(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, opts ports.GetMergeRequestOptions) (ports.MergeRequestDetail, error) { | 529 | func (m *MergeRequests) Get(ctx context.Context, p domain.Principal, name domain.RepoName, number int64, opts ports.GetMergeRequestOptions) (ports.MergeRequestDetail, error) { | ||
| 510 | - | limits := domain.DefaultDiffLimits() | 530 | + | limits, err := windowLimits(opts.Files) |
| 511 | - | if w := opts.Files; w != nil { | 531 | + | if err != nil { |
| 512 | - | if w.From < 0 || w.To < 0 || (w.To != 0 && w.To < w.From) { | 532 | + | return ports.MergeRequestDetail{}, err |
| 513 | - | return ports.MergeRequestDetail{}, fmt.Errorf("%w: file window [%d, %d)", domain.ErrInvalid, w.From, w.To) | |||
| 514 | - | } | |||
| 515 | - | limits.MaxFiles, limits.From, limits.To = 0, w.From, w.To | |||
| 516 | } | 533 | } | ||
| 517 | _, mr, err := m.get(ctx, p, name, number) | 534 | _, mr, err := m.get(ctx, p, name, number) | ||
| 518 | if err != nil { | 535 | if err != nil { | ||
| @@ -773,8 +790,10 @@ func (m *MergeRequests) checkLine(ctx context.Context, mr domain.MergeRequest, p | |||||
| 773 | } | 790 | } | ||
| 774 | idx := slices.IndexFunc(diff.Files, func(f domain.FileDiff) bool { return f.CommentPath() == params.Path }) | 791 | idx := slices.IndexFunc(diff.Files, func(f domain.FileDiff) bool { return f.CommentPath() == params.Path }) | ||
| 775 | if idx >= 0 { | 792 | if idx >= 0 { | ||
| 776 | - | one := domain.DefaultDiffLimits() | 793 | + | one, err := windowLimits(&ports.FileWindow{From: idx, To: idx + 1, Full: true}) |
| 777 | - | one.MaxFiles, one.From, one.To = 0, idx, idx+1 | 794 | + | if err != nil { |
| 795 | + | return err | |||
| 796 | + | } | |||
| 778 | if _, diff, err = m.git.DiffCommits(ctx, mr.Repo, base, mr.HeadID, one); err != nil { | 797 | if _, diff, err = m.git.DiffCommits(ctx, mr.Repo, base, mr.HeadID, one); err != nil { | ||
| 779 | return err | 798 | return err | ||
| 780 | } | 799 | } | ||
A internal/usecase/window_test.go +141 -0
| @@ -0,0 +1,141 @@ | |||
| 1 | + | // SPDX-License-Identifier: AGPL-3.0-or-later | |
| 2 | + | ||
| 3 | + | package usecase_test | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "fmt" | |
| 7 | + | "strings" | |
| 8 | + | "testing" | |
| 9 | + | ||
| 10 | + | "github.com/stretchr/testify/assert" | |
| 11 | + | "github.com/stretchr/testify/require" | |
| 12 | + | ||
| 13 | + | "bnema.dev/ermite/internal/domain" | |
| 14 | + | "bnema.dev/ermite/internal/ports" | |
| 15 | + | "bnema.dev/ermite/internal/usecase" | |
| 16 | + | ) | |
| 17 | + | ||
| 18 | + | func TestWindowLimits(t *testing.T) { | |
| 19 | + | def := domain.DefaultDiffLimits() | |
| 20 | + | win := def | |
| 21 | + | win.MaxFiles, win.From, win.To, win.SkipGenerated = 0, 2, 5, true | |
| 22 | + | open := win | |
| 23 | + | open.From, open.To = 7, 0 | |
| 24 | + | ||
| 25 | + | tests := []struct { | |
| 26 | + | name string | |
| 27 | + | w *ports.FileWindow | |
| 28 | + | want domain.DiffLimits | |
| 29 | + | bad bool | |
| 30 | + | }{ | |
| 31 | + | {name: "nil is the default", want: def}, | |
| 32 | + | {name: "a window", w: &ports.FileWindow{From: 2, To: 5}, want: win}, | |
| 33 | + | {name: "an open window", w: &ports.FileWindow{From: 7}, want: open}, | |
| 34 | + | {name: "full", w: &ports.FileWindow{From: 3, To: 4, Full: true}, want: domain.OneFileDiffLimits(3)}, | |
| 35 | + | {name: "negative from", w: &ports.FileWindow{From: -1}, bad: true}, | |
| 36 | + | {name: "negative to", w: &ports.FileWindow{To: -1}, bad: true}, | |
| 37 | + | {name: "to before from", w: &ports.FileWindow{From: 5, To: 3}, bad: true}, | |
| 38 | + | {name: "full without to", w: &ports.FileWindow{From: 3, Full: true}, bad: true}, | |
| 39 | + | {name: "full of two files", w: &ports.FileWindow{From: 3, To: 5, Full: true}, bad: true}, | |
| 40 | + | {name: "full of no file", w: &ports.FileWindow{From: 3, To: 3, Full: true}, bad: true}, | |
| 41 | + | } | |
| 42 | + | for _, tt := range tests { | |
| 43 | + | t.Run(tt.name, func(t *testing.T) { | |
| 44 | + | got, err := usecase.WindowLimits(tt.w) | |
| 45 | + | if tt.bad { | |
| 46 | + | require.ErrorIs(t, err, domain.ErrInvalid) | |
| 47 | + | return | |
| 48 | + | } | |
| 49 | + | require.NoError(t, err) | |
| 50 | + | assert.Equal(t, tt.want, got) | |
| 51 | + | }) | |
| 52 | + | } | |
| 53 | + | } | |
| 54 | + | ||
| 55 | + | func TestGetMergeRequestFullAndGenerated(t *testing.T) { | |
| 56 | + | r := newMRRig(t) | |
| 57 | + | r.seed() | |
| 58 | + | r.sh("checkout", "-q", "-B", "feat", "main") | |
| 59 | + | r.commit(author, "a.txt", "one\n", "a") | |
| 60 | + | r.commit(author, "go.sum", "x y z\n", "sum") | |
| 61 | + | r.push("feat") | |
| 62 | + | n := r.open("feat").Number | |
| 63 | + | ||
| 64 | + | t.Run("full with a wider window is invalid", func(t *testing.T) { | |
| 65 | + | _, err := r.mrs.Get(r.ctx, reader, r.repo, n, ports.GetMergeRequestOptions{Files: &ports.FileWindow{From: 0, To: 2, Full: true}}) | |
| 66 | + | require.ErrorIs(t, err, domain.ErrInvalid) | |
| 67 | + | }) | |
| 68 | + | ||
| 69 | + | t.Run("a windowed read collapses a generated file, a nil read does not", func(t *testing.T) { | |
| 70 | + | d, err := r.mrs.Get(r.ctx, reader, r.repo, n, ports.GetMergeRequestOptions{Files: &ports.FileWindow{From: 0, To: 10}}) | |
| 71 | + | require.NoError(t, err) | |
| 72 | + | byPath := map[string]domain.FileDiff{} | |
| 73 | + | for _, f := range d.Diff.Files { | |
| 74 | + | byPath[f.NewPath] = f | |
| 75 | + | } | |
| 76 | + | assert.Equal(t, domain.OmitGenerated, byPath["go.sum"].Omitted) | |
| 77 | + | assert.Empty(t, byPath["go.sum"].Hunks) | |
| 78 | + | assert.Equal(t, domain.OmitNone, byPath["a.txt"].Omitted) | |
| 79 | + | assert.NotEmpty(t, byPath["a.txt"].Hunks) | |
| 80 | + | ||
| 81 | + | d, err = r.mrs.Get(r.ctx, reader, r.repo, n, ports.GetMergeRequestOptions{}) | |
| 82 | + | require.NoError(t, err) | |
| 83 | + | for _, f := range d.Diff.Files { | |
| 84 | + | assert.Equal(t, domain.OmitNone, f.Omitted, f.NewPath) | |
| 85 | + | assert.NotEmpty(t, f.Hunks, f.NewPath) | |
| 86 | + | } | |
| 87 | + | }) | |
| 88 | + | } | |
| 89 | + | ||
| 90 | + | func bigContent(n int) string { | |
| 91 | + | var b strings.Builder | |
| 92 | + | for i := range n { | |
| 93 | + | fmt.Fprintf(&b, "line %d\n", i) | |
| 94 | + | } | |
| 95 | + | return b.String() | |
| 96 | + | } | |
| 97 | + | ||
| 98 | + | func TestCheckLineBeyondFileLimit(t *testing.T) { | |
| 99 | + | r := newMRRig(t) | |
| 100 | + | r.seed() | |
| 101 | + | r.sh("checkout", "-q", "-B", "feat", "main") | |
| 102 | + | r.commit(author, "big.txt", bigContent(3000), "big") | |
| 103 | + | r.push("feat") | |
| 104 | + | n := r.open("feat").Number | |
| 105 | + | ||
| 106 | + | add := func(line int) error { | |
| 107 | + | _, err := r.mrs.AddComment(r.ctx, mrW, r.repo, n, ports.AddCommentParams{Path: "big.txt", Line: line, Side: domain.SideNew, Text: "c"}) | |
| 108 | + | return err | |
| 109 | + | } | |
| 110 | + | require.NoError(t, add(1)) | |
| 111 | + | require.NoError(t, add(2500)) | |
| 112 | + | require.NoError(t, add(3000)) | |
| 113 | + | require.ErrorIs(t, add(3001), domain.ErrInvalid) | |
| 114 | + | } | |
| 115 | + | ||
| 116 | + | func TestBrowseGetCommitFullWindow(t *testing.T) { | |
| 117 | + | r := newMRRig(t) | |
| 118 | + | r.seed() | |
| 119 | + | r.commit(author, "a-big.txt", bigContent(3000), "big") | |
| 120 | + | id := r.head() | |
| 121 | + | r.push("main") | |
| 122 | + | b := usecase.NewBrowse(r.store, r.git) | |
| 123 | + | rev := domain.RevisionOf(id) | |
| 124 | + | ||
| 125 | + | d, err := b.GetCommit(r.ctx, root, r.repo, rev, ports.GetCommitOptions{}) | |
| 126 | + | require.NoError(t, err) | |
| 127 | + | require.Len(t, d.Diff.Files, 1) | |
| 128 | + | assert.Equal(t, domain.OmitLarge, d.Diff.Files[0].Omitted) | |
| 129 | + | assert.Empty(t, d.Diff.Files[0].Hunks) | |
| 130 | + | ||
| 131 | + | d, err = b.GetCommit(r.ctx, root, r.repo, rev, ports.GetCommitOptions{Files: &ports.FileWindow{From: 0, To: 1, Full: true}}) | |
| 132 | + | require.NoError(t, err) | |
| 133 | + | require.Len(t, d.Diff.Files, 1) | |
| 134 | + | f := d.Diff.Files[0] | |
| 135 | + | assert.Equal(t, domain.OmitNone, f.Omitted) | |
| 136 | + | require.NotEmpty(t, f.Hunks) | |
| 137 | + | assert.Equal(t, 3000, f.Additions) | |
| 138 | + | ||
| 139 | + | _, err = b.GetCommit(r.ctx, root, r.repo, rev, ports.GetCommitOptions{Files: &ports.FileWindow{From: 0, To: 2, Full: true}}) | |
| 140 | + | require.ErrorIs(t, err, domain.ErrInvalid) | |
| 141 | + | } | |
| @@ -0,0 +1,141 @@ | |||||
| 1 | + | // SPDX-License-Identifier: AGPL-3.0-or-later | |||
| 2 | + | ||||
| 3 | + | package usecase_test | |||
| 4 | + | ||||
| 5 | + | import ( | |||
| 6 | + | "fmt" | |||
| 7 | + | "strings" | |||
| 8 | + | "testing" | |||
| 9 | + | ||||
| 10 | + | "github.com/stretchr/testify/assert" | |||
| 11 | + | "github.com/stretchr/testify/require" | |||
| 12 | + | ||||
| 13 | + | "bnema.dev/ermite/internal/domain" | |||
| 14 | + | "bnema.dev/ermite/internal/ports" | |||
| 15 | + | "bnema.dev/ermite/internal/usecase" | |||
| 16 | + | ) | |||
| 17 | + | ||||
| 18 | + | func TestWindowLimits(t *testing.T) { | |||
| 19 | + | def := domain.DefaultDiffLimits() | |||
| 20 | + | win := def | |||
| 21 | + | win.MaxFiles, win.From, win.To, win.SkipGenerated = 0, 2, 5, true | |||
| 22 | + | open := win | |||
| 23 | + | open.From, open.To = 7, 0 | |||
| 24 | + | ||||
| 25 | + | tests := []struct { | |||
| 26 | + | name string | |||
| 27 | + | w *ports.FileWindow | |||
| 28 | + | want domain.DiffLimits | |||
| 29 | + | bad bool | |||
| 30 | + | }{ | |||
| 31 | + | {name: "nil is the default", want: def}, | |||
| 32 | + | {name: "a window", w: &ports.FileWindow{From: 2, To: 5}, want: win}, | |||
| 33 | + | {name: "an open window", w: &ports.FileWindow{From: 7}, want: open}, | |||
| 34 | + | {name: "full", w: &ports.FileWindow{From: 3, To: 4, Full: true}, want: domain.OneFileDiffLimits(3)}, | |||
| 35 | + | {name: "negative from", w: &ports.FileWindow{From: -1}, bad: true}, | |||
| 36 | + | {name: "negative to", w: &ports.FileWindow{To: -1}, bad: true}, | |||
| 37 | + | {name: "to before from", w: &ports.FileWindow{From: 5, To: 3}, bad: true}, | |||
| 38 | + | {name: "full without to", w: &ports.FileWindow{From: 3, Full: true}, bad: true}, | |||
| 39 | + | {name: "full of two files", w: &ports.FileWindow{From: 3, To: 5, Full: true}, bad: true}, | |||
| 40 | + | {name: "full of no file", w: &ports.FileWindow{From: 3, To: 3, Full: true}, bad: true}, | |||
| 41 | + | } | |||
| 42 | + | for _, tt := range tests { | |||
| 43 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 44 | + | got, err := usecase.WindowLimits(tt.w) | |||
| 45 | + | if tt.bad { | |||
| 46 | + | require.ErrorIs(t, err, domain.ErrInvalid) | |||
| 47 | + | return | |||
| 48 | + | } | |||
| 49 | + | require.NoError(t, err) | |||
| 50 | + | assert.Equal(t, tt.want, got) | |||
| 51 | + | }) | |||
| 52 | + | } | |||
| 53 | + | } | |||
| 54 | + | ||||
| 55 | + | func TestGetMergeRequestFullAndGenerated(t *testing.T) { | |||
| 56 | + | r := newMRRig(t) | |||
| 57 | + | r.seed() | |||
| 58 | + | r.sh("checkout", "-q", "-B", "feat", "main") | |||
| 59 | + | r.commit(author, "a.txt", "one\n", "a") | |||
| 60 | + | r.commit(author, "go.sum", "x y z\n", "sum") | |||
| 61 | + | r.push("feat") | |||
| 62 | + | n := r.open("feat").Number | |||
| 63 | + | ||||
| 64 | + | t.Run("full with a wider window is invalid", func(t *testing.T) { | |||
| 65 | + | _, err := r.mrs.Get(r.ctx, reader, r.repo, n, ports.GetMergeRequestOptions{Files: &ports.FileWindow{From: 0, To: 2, Full: true}}) | |||
| 66 | + | require.ErrorIs(t, err, domain.ErrInvalid) | |||
| 67 | + | }) | |||
| 68 | + | ||||
| 69 | + | t.Run("a windowed read collapses a generated file, a nil read does not", func(t *testing.T) { | |||
| 70 | + | d, err := r.mrs.Get(r.ctx, reader, r.repo, n, ports.GetMergeRequestOptions{Files: &ports.FileWindow{From: 0, To: 10}}) | |||
| 71 | + | require.NoError(t, err) | |||
| 72 | + | byPath := map[string]domain.FileDiff{} | |||
| 73 | + | for _, f := range d.Diff.Files { | |||
| 74 | + | byPath[f.NewPath] = f | |||
| 75 | + | } | |||
| 76 | + | assert.Equal(t, domain.OmitGenerated, byPath["go.sum"].Omitted) | |||
| 77 | + | assert.Empty(t, byPath["go.sum"].Hunks) | |||
| 78 | + | assert.Equal(t, domain.OmitNone, byPath["a.txt"].Omitted) | |||
| 79 | + | assert.NotEmpty(t, byPath["a.txt"].Hunks) | |||
| 80 | + | ||||
| 81 | + | d, err = r.mrs.Get(r.ctx, reader, r.repo, n, ports.GetMergeRequestOptions{}) | |||
| 82 | + | require.NoError(t, err) | |||
| 83 | + | for _, f := range d.Diff.Files { | |||
| 84 | + | assert.Equal(t, domain.OmitNone, f.Omitted, f.NewPath) | |||
| 85 | + | assert.NotEmpty(t, f.Hunks, f.NewPath) | |||
| 86 | + | } | |||
| 87 | + | }) | |||
| 88 | + | } | |||
| 89 | + | ||||
| 90 | + | func bigContent(n int) string { | |||
| 91 | + | var b strings.Builder | |||
| 92 | + | for i := range n { | |||
| 93 | + | fmt.Fprintf(&b, "line %d\n", i) | |||
| 94 | + | } | |||
| 95 | + | return b.String() | |||
| 96 | + | } | |||
| 97 | + | ||||
| 98 | + | func TestCheckLineBeyondFileLimit(t *testing.T) { | |||
| 99 | + | r := newMRRig(t) | |||
| 100 | + | r.seed() | |||
| 101 | + | r.sh("checkout", "-q", "-B", "feat", "main") | |||
| 102 | + | r.commit(author, "big.txt", bigContent(3000), "big") | |||
| 103 | + | r.push("feat") | |||
| 104 | + | n := r.open("feat").Number | |||
| 105 | + | ||||
| 106 | + | add := func(line int) error { | |||
| 107 | + | _, err := r.mrs.AddComment(r.ctx, mrW, r.repo, n, ports.AddCommentParams{Path: "big.txt", Line: line, Side: domain.SideNew, Text: "c"}) | |||
| 108 | + | return err | |||
| 109 | + | } | |||
| 110 | + | require.NoError(t, add(1)) | |||
| 111 | + | require.NoError(t, add(2500)) | |||
| 112 | + | require.NoError(t, add(3000)) | |||
| 113 | + | require.ErrorIs(t, add(3001), domain.ErrInvalid) | |||
| 114 | + | } | |||
| 115 | + | ||||
| 116 | + | func TestBrowseGetCommitFullWindow(t *testing.T) { | |||
| 117 | + | r := newMRRig(t) | |||
| 118 | + | r.seed() | |||
| 119 | + | r.commit(author, "a-big.txt", bigContent(3000), "big") | |||
| 120 | + | id := r.head() | |||
| 121 | + | r.push("main") | |||
| 122 | + | b := usecase.NewBrowse(r.store, r.git) | |||
| 123 | + | rev := domain.RevisionOf(id) | |||
| 124 | + | ||||
| 125 | + | d, err := b.GetCommit(r.ctx, root, r.repo, rev, ports.GetCommitOptions{}) | |||
| 126 | + | require.NoError(t, err) | |||
| 127 | + | require.Len(t, d.Diff.Files, 1) | |||
| 128 | + | assert.Equal(t, domain.OmitLarge, d.Diff.Files[0].Omitted) | |||
| 129 | + | assert.Empty(t, d.Diff.Files[0].Hunks) | |||
| 130 | + | ||||
| 131 | + | d, err = b.GetCommit(r.ctx, root, r.repo, rev, ports.GetCommitOptions{Files: &ports.FileWindow{From: 0, To: 1, Full: true}}) | |||
| 132 | + | require.NoError(t, err) | |||
| 133 | + | require.Len(t, d.Diff.Files, 1) | |||
| 134 | + | f := d.Diff.Files[0] | |||
| 135 | + | assert.Equal(t, domain.OmitNone, f.Omitted) | |||
| 136 | + | require.NotEmpty(t, f.Hunks) | |||
| 137 | + | assert.Equal(t, 3000, f.Additions) | |||
| 138 | + | ||||
| 139 | + | _, err = b.GetCommit(r.ctx, root, r.repo, rev, ports.GetCommitOptions{Files: &ports.FileWindow{From: 0, To: 2, Full: true}}) | |||
| 140 | + | require.ErrorIs(t, err, domain.ErrInvalid) | |||
| 141 | + | } | |||