initial work on raw diff (#129)

jobatzil/rename
Enver Bisevac 2022-12-22 14:30:31 +01:00 committed by GitHub
parent 43c7e94e06
commit 22b0595476
15 changed files with 647 additions and 1 deletions

1
.gitignore vendored
View File

@ -10,3 +10,4 @@ release
gitness
.idea
coverage.out
gitness.session.sql

25
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,25 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Harness",
"type": "go",
"request": "launch",
"mode": "auto",
"buildFlags": "-tags=harness",
"program": "${workspaceFolder}",
"args": ["server", "./.harness.env"]
},
{
"name": "Standalone",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}",
"args": ["server", "./.local.env"]
}
]
}

11
.vscode/settings.json vendored
View File

@ -1,3 +1,12 @@
{
"go.testTags": "sqlite_fts5"
"go.testTags": "sqlite_fts5",
"sqltools.useNodeRuntime": true,
"sqltools.connections": [
{
"previewLimit": 50,
"driver": "SQLite",
"name": "gitness",
"database": "${workspaceFolder:gitness}/database.sqlite3"
}
]
}

View File

@ -22,6 +22,7 @@ type Client struct {
refService rpc.ReferenceServiceClient
httpService rpc.SmartHTTPServiceClient
commitFilesService rpc.CommitFilesServiceClient
diffService rpc.DiffServiceClient
}
func New(remoteAddr string) (*Client, error) {
@ -50,5 +51,6 @@ func New(remoteAddr string) (*Client, error) {
refService: rpc.NewReferenceServiceClient(conn),
httpService: rpc.NewSmartHTTPServiceClient(conn),
commitFilesService: rpc.NewCommitFilesServiceClient(conn),
diffService: rpc.NewDiffServiceClient(conn),
}, nil
}

43
gitrpc/diff.go Normal file
View File

@ -0,0 +1,43 @@
// Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package gitrpc
import (
"context"
"fmt"
"io"
"github.com/harness/gitness/gitrpc/internal/streamio"
"github.com/harness/gitness/gitrpc/rpc"
)
type RawDiffRequest struct {
RepoID string
LeftCommitID string
RightCommitID string
}
func (c *Client) RawDiff(ctx context.Context, in *RawDiffRequest, w io.Writer) error {
diff, err := c.diffService.RawDiff(ctx, &rpc.RawDiffRequest{
RepoId: in.RepoID,
LeftCommitId: in.LeftCommitID,
RightCommitId: in.RightCommitID,
})
if err != nil {
return err
}
reader := streamio.NewReader(func() ([]byte, error) {
var resp *rpc.RawDiffResponse
resp, err = diff.Recv()
return resp.GetData(), err
})
if _, err = io.Copy(w, reader); err != nil {
return fmt.Errorf("copy rpc data: %w", err)
}
return nil
}

View File

@ -33,4 +33,9 @@ type Interface interface {
*/
GetInfoRefs(ctx context.Context, w io.Writer, params *InfoRefsParams) error
ServicePack(ctx context.Context, w io.Writer, params *ServicePackParams) error
/*
* Diff services
*/
RawDiff(ctx context.Context, in *RawDiffRequest, w io.Writer) error
}

View File

@ -0,0 +1,71 @@
// Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package service
import (
"fmt"
"os"
"time"
"github.com/harness/gitness/gitrpc/internal/streamio"
"github.com/harness/gitness/gitrpc/internal/types"
"github.com/harness/gitness/gitrpc/rpc"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
)
type DiffService struct {
rpc.UnimplementedDiffServiceServer
adapter GitAdapter
reposRoot string
}
func NewDiffService(adapter GitAdapter, reposRoot string) (*DiffService, error) {
return &DiffService{
adapter: adapter,
reposRoot: reposRoot,
}, nil
}
func (s DiffService) RawDiff(req *rpc.RawDiffRequest, stream rpc.DiffService_RawDiffServer) error {
err := validateDiffRequest(req)
if err != nil {
return err
}
ctx := stream.Context()
sw := streamio.NewWriter(func(p []byte) error {
return stream.Send(&rpc.RawDiffResponse{Data: p})
})
repoPath := getFullPathForRepo(s.reposRoot, req.GetRepoId())
cmd := git.NewCommand(ctx, "diff", "--full-index", req.LeftCommitId, req.RightCommitId)
cmd.SetDescription(fmt.Sprintf("GetDiffRange [repo_path: %s]", repoPath))
return cmd.Run(&git.RunOpts{
Timeout: time.Duration(setting.Git.Timeout.Default) * time.Second,
Dir: repoPath,
Stderr: os.Stderr,
Stdout: sw,
})
}
type requestWithLeftRightCommitIds interface {
GetLeftCommitId() string
GetRightCommitId() string
}
func validateDiffRequest(in requestWithLeftRightCommitIds) error {
if in.GetLeftCommitId() == "" {
return types.ErrEmptyLeftCommitID
}
if in.GetRightCommitId() == "" {
return types.ErrEmptyRightCommitID
}
return nil
}

View File

@ -16,4 +16,6 @@ var (
ErrActionListEmpty = errors.New("no commit actions to perform on repository")
ErrHeaderCannotBeEmpty = errors.New("header field cannot be empty")
ErrSHADoesNotMatch = errors.New("sha does not match")
ErrEmptyLeftCommitID = errors.New("empty LeftCommitId")
ErrEmptyRightCommitID = errors.New("empty RightCommitId")
)

20
gitrpc/proto/diff.proto Normal file
View File

@ -0,0 +1,20 @@
syntax = "proto3";
package rpc;
option go_package = "github.com/harness/gitness/gitrpc/rpc";
// DiffService is a service which provides RPCs to inspect differences
// introduced between a set of commits.
service DiffService {
rpc RawDiff(RawDiffRequest) returns (stream RawDiffResponse) {}
}
message RawDiffRequest {
string repo_id = 1;
string left_commit_id = 2;
string right_commit_id = 3;
}
message RawDiffResponse {
bytes data = 1;
}

233
gitrpc/rpc/diff.pb.go Normal file
View File

@ -0,0 +1,233 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc v3.21.9
// source: diff.proto
package rpc
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type RawDiffRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
RepoId string `protobuf:"bytes,1,opt,name=repo_id,json=repoId,proto3" json:"repo_id,omitempty"`
LeftCommitId string `protobuf:"bytes,2,opt,name=left_commit_id,json=leftCommitId,proto3" json:"left_commit_id,omitempty"`
RightCommitId string `protobuf:"bytes,3,opt,name=right_commit_id,json=rightCommitId,proto3" json:"right_commit_id,omitempty"`
}
func (x *RawDiffRequest) Reset() {
*x = RawDiffRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_diff_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *RawDiffRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RawDiffRequest) ProtoMessage() {}
func (x *RawDiffRequest) ProtoReflect() protoreflect.Message {
mi := &file_diff_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RawDiffRequest.ProtoReflect.Descriptor instead.
func (*RawDiffRequest) Descriptor() ([]byte, []int) {
return file_diff_proto_rawDescGZIP(), []int{0}
}
func (x *RawDiffRequest) GetRepoId() string {
if x != nil {
return x.RepoId
}
return ""
}
func (x *RawDiffRequest) GetLeftCommitId() string {
if x != nil {
return x.LeftCommitId
}
return ""
}
func (x *RawDiffRequest) GetRightCommitId() string {
if x != nil {
return x.RightCommitId
}
return ""
}
type RawDiffResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
}
func (x *RawDiffResponse) Reset() {
*x = RawDiffResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_diff_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *RawDiffResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RawDiffResponse) ProtoMessage() {}
func (x *RawDiffResponse) ProtoReflect() protoreflect.Message {
mi := &file_diff_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RawDiffResponse.ProtoReflect.Descriptor instead.
func (*RawDiffResponse) Descriptor() ([]byte, []int) {
return file_diff_proto_rawDescGZIP(), []int{1}
}
func (x *RawDiffResponse) GetData() []byte {
if x != nil {
return x.Data
}
return nil
}
var File_diff_proto protoreflect.FileDescriptor
var file_diff_proto_rawDesc = []byte{
0x0a, 0x0a, 0x64, 0x69, 0x66, 0x66, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x72, 0x70,
0x63, 0x22, 0x77, 0x0a, 0x0e, 0x52, 0x61, 0x77, 0x44, 0x69, 0x66, 0x66, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x12, 0x17, 0x0a, 0x07, 0x72, 0x65, 0x70, 0x6f, 0x5f, 0x69, 0x64, 0x18, 0x01,
0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x70, 0x6f, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e,
0x6c, 0x65, 0x66, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6c, 0x65, 0x66, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74,
0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x72, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x6d,
0x69, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x69, 0x67,
0x68, 0x74, 0x43, 0x6f, 0x6d, 0x6d, 0x69, 0x74, 0x49, 0x64, 0x22, 0x25, 0x0a, 0x0f, 0x52, 0x61,
0x77, 0x44, 0x69, 0x66, 0x66, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a,
0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74,
0x61, 0x32, 0x47, 0x0a, 0x0b, 0x44, 0x69, 0x66, 0x66, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65,
0x12, 0x38, 0x0a, 0x07, 0x52, 0x61, 0x77, 0x44, 0x69, 0x66, 0x66, 0x12, 0x13, 0x2e, 0x72, 0x70,
0x63, 0x2e, 0x52, 0x61, 0x77, 0x44, 0x69, 0x66, 0x66, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x14, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x61, 0x77, 0x44, 0x69, 0x66, 0x66, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x42, 0x27, 0x5a, 0x25, 0x67, 0x69,
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x72, 0x6e, 0x65, 0x73, 0x73,
0x2f, 0x67, 0x69, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x2f, 0x67, 0x69, 0x74, 0x72, 0x70, 0x63, 0x2f,
0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_diff_proto_rawDescOnce sync.Once
file_diff_proto_rawDescData = file_diff_proto_rawDesc
)
func file_diff_proto_rawDescGZIP() []byte {
file_diff_proto_rawDescOnce.Do(func() {
file_diff_proto_rawDescData = protoimpl.X.CompressGZIP(file_diff_proto_rawDescData)
})
return file_diff_proto_rawDescData
}
var file_diff_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_diff_proto_goTypes = []interface{}{
(*RawDiffRequest)(nil), // 0: rpc.RawDiffRequest
(*RawDiffResponse)(nil), // 1: rpc.RawDiffResponse
}
var file_diff_proto_depIdxs = []int32{
0, // 0: rpc.DiffService.RawDiff:input_type -> rpc.RawDiffRequest
1, // 1: rpc.DiffService.RawDiff:output_type -> rpc.RawDiffResponse
1, // [1:2] is the sub-list for method output_type
0, // [0:1] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_diff_proto_init() }
func file_diff_proto_init() {
if File_diff_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_diff_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RawDiffRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_diff_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RawDiffResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_diff_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_diff_proto_goTypes,
DependencyIndexes: file_diff_proto_depIdxs,
MessageInfos: file_diff_proto_msgTypes,
}.Build()
File_diff_proto = out.File
file_diff_proto_rawDesc = nil
file_diff_proto_goTypes = nil
file_diff_proto_depIdxs = nil
}

132
gitrpc/rpc/diff_grpc.pb.go Normal file
View File

@ -0,0 +1,132 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc v3.21.9
// source: diff.proto
package rpc
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
// DiffServiceClient is the client API for DiffService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type DiffServiceClient interface {
RawDiff(ctx context.Context, in *RawDiffRequest, opts ...grpc.CallOption) (DiffService_RawDiffClient, error)
}
type diffServiceClient struct {
cc grpc.ClientConnInterface
}
func NewDiffServiceClient(cc grpc.ClientConnInterface) DiffServiceClient {
return &diffServiceClient{cc}
}
func (c *diffServiceClient) RawDiff(ctx context.Context, in *RawDiffRequest, opts ...grpc.CallOption) (DiffService_RawDiffClient, error) {
stream, err := c.cc.NewStream(ctx, &DiffService_ServiceDesc.Streams[0], "/rpc.DiffService/RawDiff", opts...)
if err != nil {
return nil, err
}
x := &diffServiceRawDiffClient{stream}
if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err
}
if err := x.ClientStream.CloseSend(); err != nil {
return nil, err
}
return x, nil
}
type DiffService_RawDiffClient interface {
Recv() (*RawDiffResponse, error)
grpc.ClientStream
}
type diffServiceRawDiffClient struct {
grpc.ClientStream
}
func (x *diffServiceRawDiffClient) Recv() (*RawDiffResponse, error) {
m := new(RawDiffResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// DiffServiceServer is the server API for DiffService service.
// All implementations must embed UnimplementedDiffServiceServer
// for forward compatibility
type DiffServiceServer interface {
RawDiff(*RawDiffRequest, DiffService_RawDiffServer) error
mustEmbedUnimplementedDiffServiceServer()
}
// UnimplementedDiffServiceServer must be embedded to have forward compatible implementations.
type UnimplementedDiffServiceServer struct {
}
func (UnimplementedDiffServiceServer) RawDiff(*RawDiffRequest, DiffService_RawDiffServer) error {
return status.Errorf(codes.Unimplemented, "method RawDiff not implemented")
}
func (UnimplementedDiffServiceServer) mustEmbedUnimplementedDiffServiceServer() {}
// UnsafeDiffServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to DiffServiceServer will
// result in compilation errors.
type UnsafeDiffServiceServer interface {
mustEmbedUnimplementedDiffServiceServer()
}
func RegisterDiffServiceServer(s grpc.ServiceRegistrar, srv DiffServiceServer) {
s.RegisterService(&DiffService_ServiceDesc, srv)
}
func _DiffService_RawDiff_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(RawDiffRequest)
if err := stream.RecvMsg(m); err != nil {
return err
}
return srv.(DiffServiceServer).RawDiff(m, &diffServiceRawDiffServer{stream})
}
type DiffService_RawDiffServer interface {
Send(*RawDiffResponse) error
grpc.ServerStream
}
type diffServiceRawDiffServer struct {
grpc.ServerStream
}
func (x *diffServiceRawDiffServer) Send(m *RawDiffResponse) error {
return x.ServerStream.SendMsg(m)
}
// DiffService_ServiceDesc is the grpc.ServiceDesc for DiffService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var DiffService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "rpc.DiffService",
HandlerType: (*DiffServiceServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "RawDiff",
Handler: _DiffService_RawDiff_Handler,
ServerStreams: true,
},
},
Metadata: "diff.proto",
}

View File

@ -89,12 +89,17 @@ func NewServer(config Config, eventsSystem *events.System) (*Server, error) {
if err != nil {
return nil, err
}
diffService, err := service.NewDiffService(adapter, reposRoot)
if err != nil {
return nil, err
}
// register services
rpc.RegisterRepositoryServiceServer(s, repoService)
rpc.RegisterReferenceServiceServer(s, refService)
rpc.RegisterSmartHTTPServiceServer(s, httpService)
rpc.RegisterCommitFilesServiceServer(s, commitFilesService)
rpc.RegisterDiffServiceServer(s, diffService)
return &Server{
Server: s,

View File

@ -0,0 +1,60 @@
// Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package repo
import (
"context"
"io"
"strings"
"github.com/harness/gitness/gitrpc"
apiauth "github.com/harness/gitness/internal/api/auth"
"github.com/harness/gitness/internal/auth"
"github.com/harness/gitness/types/enum"
)
func (c *Controller) RawDiff(
ctx context.Context,
session *auth.Session,
repoRef string,
path string,
w io.Writer,
) error {
repo, err := c.repoStore.FindRepoFromRef(ctx, repoRef)
if err != nil {
return err
}
if err = apiauth.CheckRepo(ctx, c.authorizer, session, repo, enum.PermissionRepoView, false); err != nil {
return err
}
info := parseDiffPath(path)
return c.gitRPCClient.RawDiff(ctx, &gitrpc.RawDiffRequest{
RepoID: repo.GitUID,
LeftCommitID: info.Left,
RightCommitID: info.Right,
}, w)
}
type CompareInfo struct {
Left string
Right string
}
func parseDiffPath(path string) CompareInfo {
infos := strings.SplitN(path, "...", 2)
if len(infos) != 2 {
infos = strings.SplitN(path, "..", 2)
}
if len(infos) != 2 {
return CompareInfo{}
}
return CompareInfo{
Left: infos[0],
Right: infos[1],
}
}

View File

@ -0,0 +1,33 @@
// Copyright 2022 Harness Inc. All rights reserved.
// Use of this source code is governed by the Polyform Free Trial License
// that can be found in the LICENSE.md file for this repository.
package repo
import (
"net/http"
"github.com/harness/gitness/internal/api/controller/repo"
"github.com/harness/gitness/internal/api/render"
"github.com/harness/gitness/internal/api/request"
)
// HandleRawDiff how diff between two commits, branches or tags.
func HandleRawDiff(repoCtrl *repo.Controller) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
session, _ := request.AuthSessionFrom(ctx)
repoRef, err := request.GetRepoRefFromPath(r)
if err != nil {
render.TranslatedUserError(w, err)
return
}
path := request.GetOptionalRemainderFromPath(r)
if err = repoCtrl.RawDiff(ctx, session, repoRef, path, w); err != nil {
render.TranslatedUserError(w, err)
return
}
}
}

View File

@ -190,6 +190,11 @@ func setupRepos(r chi.Router, repoCtrl *repo.Controller, pullreqCtrl *pullreq.Co
})
})
// diffs
r.Route("/compare", func(r chi.Router) {
r.Get("/*", handlerrepo.HandleRawDiff(repoCtrl))
})
setupPullReq(r, pullreqCtrl)
})
})