diff --git a/appview/pages/pages.go b/appview/pages/pages.go index 2c6e2da3..c56b1ff0 100644 --- a/appview/pages/pages.go +++ b/appview/pages/pages.go @@ -1464,6 +1464,10 @@ type PullInterdiffParams struct { ErrorMsg string } +type PullDiffFragmentParams struct { + Diff string +} + func (p *Pages) PullDiff(w io.Writer, params PullDiffParams) error { panic("unimplemented") } @@ -1472,6 +1476,10 @@ func (p *Pages) PullInterdiff(w io.Writer, params PullInterdiffParams) error { panic("unimplemented") } +func (p *Pages) PullDiffFragment(w io.Writer, params PullDiffFragmentParams) error { + return p.executePlain("repo/pulls/fragments/diff", w, params) +} + func (p *Pages) RepoSinglePull(w io.Writer, params RepoSinglePullParams) error { params.Active = "pulls" return p.executeRepo("repo/pulls/pull", w, params) diff --git a/appview/pages/templates/repo/pulls/fragments/diff.html b/appview/pages/templates/repo/pulls/fragments/diff.html new file mode 100644 index 00000000..035758da --- /dev/null +++ b/appview/pages/templates/repo/pulls/fragments/diff.html @@ -0,0 +1,5 @@ +{{ define "repo/pulls/fragments/diff" }} +
+
{{ .Diff }}
+
+{{ end }} diff --git a/appview/pulls/pull2.go b/appview/pulls/pull2.go index 589aa0be..d970eb0c 100644 --- a/appview/pulls/pull2.go +++ b/appview/pulls/pull2.go @@ -2,9 +2,12 @@ package pulls import ( "context" + "errors" "fmt" + "io" "net/http" "strconv" + "strings" "github.com/bluesky-social/indigo/atproto/syntax" "github.com/bluesky-social/indigo/lex/util" @@ -198,50 +201,157 @@ func (s *Pulls) PullDiff(w http.ResponseWriter, r *http.Request) { params.Commits = commits } +// fileDiff pairs a streamed FileDiff with the line content of its base/head blobs. +type fileDiff struct { + diff *gitmirrorv1.FileDiff + baseLines []string // lines of the base (lhs) blob; nil for binary/submodule/absent + headLines []string // lines of the head (rhs) blob; nil for binary/submodule/absent +} + // htmx fragment. render diff between commits func (s *Pulls) PullDiffFragment(w http.ResponseWriter, r *http.Request) { var ( - repoRaw = r.URL.Query().Get("repo") + // repoRaw = r.URL.Query().Get("repo") base = r.URL.Query().Get("base") // base commit ID head = r.URL.Query().Get("head") // head commit ID unified = r.URL.Query().Get("view") == "unified" ) - repo, err := syntax.ParseDID(repoRaw) + // repo, err := syntax.ParseDID(repoRaw) + // if err != nil { + // http.Error(w, fmt.Sprintf("invalid repo DID: %q", repoRaw), http.StatusBadRequest) + // return + // } + repo, err := s.repoResolver.Resolve(r) if err != nil { - http.Error(w, fmt.Sprintf("invalid repo DID: %q", repoRaw), http.StatusBadRequest) - return + panic("unimplemented") } + l := s.logger ctx := r.Context() + var params pages.PullDiffFragmentParams + defer func() { + s.pages.PullDiffFragment(w, params) + }() + + // a. drain the diff stream into one fileDiff per changed file. stream, err := s.gitmirror.Diff(ctx, &gitmirrorv1.DiffRequest{ - Repo: repo.String(), + Repo: repo.RepoDid, Base: base, Head: head, }) if err != nil { - panic("unimplemented") + panic(err) + } + var files []*fileDiff + for { + fd, err := stream.Recv() + if errors.Is(err, io.EOF) { + break + } + if err != nil { + panic(err) + } + files = append(files, &fileDiff{diff: fd}) + } + + // b. fetch each file's base/head blob in parallel and split into lines. + g, gctx := errgroup.WithContext(ctx) + for i, f := range files { + l.Debug("diff", "file", i, "diff", f.diff) + g.Go(func() error { + lhs, rhs := f.diff.GetLhsSrc(), f.diff.GetRhsSrc() + // Binary/submodule files have no line content to fetch. + if isBinaryOrSubmodule(lhs) || isBinaryOrSubmodule(rhs) { + return nil + } + baseBlob, err := s.getBlob(gctx, repo.RepoDid, lhs.GetOid()) + if err != nil { + return err + } + headBlob, err := s.getBlob(gctx, repo.RepoDid, rhs.GetOid()) + if err != nil { + return err + } + f.baseLines = splitLines(baseBlob) + f.headLines = splitLines(headBlob) + return nil + }) + } + if err := g.Wait(); err != nil { + panic(err) } - diff, err := stream.Recv() + // TODO: implement split view + _ = unified + + // TODO: implement context lines + for _, f := range files { + params.Diff += f.diff.RhsSrc.Path + "\n\n" + for _, hunk := range f.diff.Hunks { + params.Diff += "@@@\n" + for _, line := range hunk.Lines { + if line.Lhs != nil { + params.Diff += fmt.Sprintf("%d\t\t - %s\n", *line.Lhs+1, f.baseLines[*line.Lhs]) + } + } + for _, line := range hunk.Lines { + if line.Rhs != nil { + params.Diff += fmt.Sprintf("\t%d\t + %s\n", *line.Rhs+1, f.headLines[*line.Rhs]) + } + } + } + } +} + +// getBlob streams a blob's bytes from gitmirror by OID and concatenates them. +// A null (all-zero) oid — the absent side of an addition/deletion — yields nil. +func (s *Pulls) getBlob(ctx context.Context, repo, oid string) ([]byte, error) { + if isNullOid(oid) { + return nil, nil + } + stream, err := s.gitmirror.GetBlob(ctx, &gitmirrorv1.GetBlobRequest{Repo: repo, Oid: oid}) if err != nil { - panic("unimplemented") + return nil, err } + var buf []byte + for { + chunk, err := stream.Recv() + if errors.Is(err, io.EOF) { + break + } + if err != nil { + return nil, err + } + buf = append(buf, chunk.GetData()...) + } + return buf, nil +} - _ = diff +func isBinaryOrSubmodule(fc *gitmirrorv1.FileContent) bool { + return fc != nil && (fc.GetIsBinary() || fc.GetIsSubmodule()) +} - // // 1. fetch each blob in parallel - // baseBlob, err := s.gitmirror.GetBlob(ctx, ...) - // headBlob, err := s.gitmirror.GetBlob(ctx, ...) +// isNullOid reports whether oid is empty or the all-zero object id. +func isNullOid(oid string) bool { + if oid == "" { + return true + } + for _, c := range oid { + if c != '0' { + return false + } + } + return true +} - // 2. render diff by merging diff & blob content - if unified { - // render unified diff - // var lines []string - } else { - // render split diff - // var leftLines, rightLines []string +// splitLines splits blob bytes into lines, dropping the trailing empty element +// produced by a final newline. +func splitLines(b []byte) []string { + if len(b) == 0 { + return nil } + return strings.Split(strings.TrimSuffix(string(b), "\n"), "\n") } // htmx fragment. render interdiff between changes diff --git a/appview/pulls/pulls.go b/appview/pulls/pulls.go index e9dac606..7ab22574 100644 --- a/appview/pulls/pulls.go +++ b/appview/pulls/pulls.go @@ -24,10 +24,15 @@ import ( "tangled.org/core/patchutil" indigoxrpc "github.com/bluesky-social/indigo/xrpc" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" ) const ApplicationGzip = "application/gzip" +// TODO: move to config. gitmirror serves h2c; talk to it over plaintext for now. +const gitmirrorHost = "gitmirror:9000" + type Pulls struct { oauth *oauth.OAuth repoResolver *reporesolver.RepoResolver @@ -57,6 +62,10 @@ func New( indexer *pulls_indexer.Indexer, logger *slog.Logger, ) *Pulls { + conn, err := grpc.NewClient(gitmirrorHost, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + panic(fmt.Sprintf("dial gitmirror: %v", err)) + } return &Pulls{ oauth: oauth, repoResolver: repoResolver, @@ -70,6 +79,7 @@ func New( logger: logger, indexer: indexer, ogreClient: ogre.NewClient(config.Ogre.Host), + gitmirror: knotmirror.NewGitMirrorServiceClient(conn), } } diff --git a/appview/pulls/router.go b/appview/pulls/router.go index 530d4941..94d8200f 100644 --- a/appview/pulls/router.go +++ b/appview/pulls/router.go @@ -9,6 +9,7 @@ import ( func (s *Pulls) Router(mw *middleware.Middleware) http.Handler { r := chi.NewRouter() + r.Get("/test", s.PullDiffFragment) r.With(middleware.Paginate).Get("/", s.RepoPulls) r.With(middleware.AuthMiddleware(s.oauth)).Route("/new", func(r chi.Router) { r.Get("/", s.NewPull) diff --git a/gitmirror/proto/gen/gitmirror.pb.go b/gitmirror/proto/gen/gitmirror.pb.go index d9eaf289..569923fe 100644 --- a/gitmirror/proto/gen/gitmirror.pb.go +++ b/gitmirror/proto/gen/gitmirror.pb.go @@ -21,6 +21,105 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type GetBlobRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // repo carries the DID as a string. + Repo string `protobuf:"bytes,1,opt,name=repo,proto3" json:"repo,omitempty"` + // oid is the blob object id (hex). + Oid string `protobuf:"bytes,2,opt,name=oid,proto3" json:"oid,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetBlobRequest) Reset() { + *x = GetBlobRequest{} + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetBlobRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetBlobRequest) ProtoMessage() {} + +func (x *GetBlobRequest) ProtoReflect() protoreflect.Message { + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetBlobRequest.ProtoReflect.Descriptor instead. +func (*GetBlobRequest) Descriptor() ([]byte, []int) { + return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{0} +} + +func (x *GetBlobRequest) GetRepo() string { + if x != nil { + return x.Repo + } + return "" +} + +func (x *GetBlobRequest) GetOid() string { + if x != nil { + return x.Oid + } + return "" +} + +// One chunk of a blob's raw bytes; concatenate in arrival order. +type BlobChunk struct { + state protoimpl.MessageState `protogen:"open.v1"` + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BlobChunk) Reset() { + *x = BlobChunk{} + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BlobChunk) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BlobChunk) ProtoMessage() {} + +func (x *BlobChunk) ProtoReflect() protoreflect.Message { + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BlobChunk.ProtoReflect.Descriptor instead. +func (*BlobChunk) Descriptor() ([]byte, []int) { + return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{1} +} + +func (x *BlobChunk) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + type CommitsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` // repo carries the DID as a string; parsing/validation is an impl detail. @@ -32,7 +131,7 @@ type CommitsRequest struct { func (x *CommitsRequest) Reset() { *x = CommitsRequest{} - mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[0] + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -44,7 +143,7 @@ func (x *CommitsRequest) String() string { func (*CommitsRequest) ProtoMessage() {} func (x *CommitsRequest) ProtoReflect() protoreflect.Message { - mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[0] + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[2] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -57,7 +156,7 @@ func (x *CommitsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CommitsRequest.ProtoReflect.Descriptor instead. func (*CommitsRequest) Descriptor() ([]byte, []int) { - return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{0} + return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{2} } func (x *CommitsRequest) GetRepo() string { @@ -82,7 +181,7 @@ type CommitsOptions struct { func (x *CommitsOptions) Reset() { *x = CommitsOptions{} - mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[1] + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -94,7 +193,7 @@ func (x *CommitsOptions) String() string { func (*CommitsOptions) ProtoMessage() {} func (x *CommitsOptions) ProtoReflect() protoreflect.Message { - mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[1] + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[3] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -107,7 +206,7 @@ func (x *CommitsOptions) ProtoReflect() protoreflect.Message { // Deprecated: Use CommitsOptions.ProtoReflect.Descriptor instead. func (*CommitsOptions) Descriptor() ([]byte, []int) { - return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{1} + return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{3} } type CommitsResponse struct { @@ -120,7 +219,7 @@ type CommitsResponse struct { func (x *CommitsResponse) Reset() { *x = CommitsResponse{} - mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[2] + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -132,7 +231,7 @@ func (x *CommitsResponse) String() string { func (*CommitsResponse) ProtoMessage() {} func (x *CommitsResponse) ProtoReflect() protoreflect.Message { - mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[2] + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[4] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -145,7 +244,7 @@ func (x *CommitsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CommitsResponse.ProtoReflect.Descriptor instead. func (*CommitsResponse) Descriptor() ([]byte, []int) { - return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{2} + return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{4} } func (x *CommitsResponse) GetCommits() []string { @@ -168,7 +267,7 @@ type DiffRequest struct { func (x *DiffRequest) Reset() { *x = DiffRequest{} - mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[3] + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -180,7 +279,7 @@ func (x *DiffRequest) String() string { func (*DiffRequest) ProtoMessage() {} func (x *DiffRequest) ProtoReflect() protoreflect.Message { - mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[3] + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[5] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -193,7 +292,7 @@ func (x *DiffRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DiffRequest.ProtoReflect.Descriptor instead. func (*DiffRequest) Descriptor() ([]byte, []int) { - return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{3} + return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{5} } func (x *DiffRequest) GetRepo() string { @@ -232,7 +331,7 @@ type InterdiffRequest struct { func (x *InterdiffRequest) Reset() { *x = InterdiffRequest{} - mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[4] + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -244,7 +343,7 @@ func (x *InterdiffRequest) String() string { func (*InterdiffRequest) ProtoMessage() {} func (x *InterdiffRequest) ProtoReflect() protoreflect.Message { - mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[4] + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -257,7 +356,7 @@ func (x *InterdiffRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use InterdiffRequest.ProtoReflect.Descriptor instead. func (*InterdiffRequest) Descriptor() ([]byte, []int) { - return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{4} + return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{6} } func (x *InterdiffRequest) GetRepo() string { @@ -301,13 +400,14 @@ type FileContent struct { Oid string `protobuf:"bytes,2,opt,name=oid,proto3" json:"oid,omitempty"` Size uint64 `protobuf:"varint,3,opt,name=size,proto3" json:"size,omitempty"` IsBinary bool `protobuf:"varint,4,opt,name=is_binary,json=isBinary,proto3" json:"is_binary,omitempty"` + IsSubmodule bool `protobuf:"varint,5,opt,name=is_submodule,json=isSubmodule,proto3" json:"is_submodule,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } func (x *FileContent) Reset() { *x = FileContent{} - mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[5] + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -319,7 +419,7 @@ func (x *FileContent) String() string { func (*FileContent) ProtoMessage() {} func (x *FileContent) ProtoReflect() protoreflect.Message { - mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[5] + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[7] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -332,7 +432,7 @@ func (x *FileContent) ProtoReflect() protoreflect.Message { // Deprecated: Use FileContent.ProtoReflect.Descriptor instead. func (*FileContent) Descriptor() ([]byte, []int) { - return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{5} + return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{7} } func (x *FileContent) GetPath() string { @@ -363,6 +463,13 @@ func (x *FileContent) GetIsBinary() bool { return false } +func (x *FileContent) GetIsSubmodule() bool { + if x != nil { + return x.IsSubmodule + } + return false +} + // One aligned line pair; a side is absent (None) when unset. type LinePair struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -374,7 +481,7 @@ type LinePair struct { func (x *LinePair) Reset() { *x = LinePair{} - mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[6] + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -386,7 +493,7 @@ func (x *LinePair) String() string { func (*LinePair) ProtoMessage() {} func (x *LinePair) ProtoReflect() protoreflect.Message { - mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[6] + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -399,7 +506,7 @@ func (x *LinePair) ProtoReflect() protoreflect.Message { // Deprecated: Use LinePair.ProtoReflect.Descriptor instead. func (*LinePair) Descriptor() ([]byte, []int) { - return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{6} + return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{8} } func (x *LinePair) GetLhs() uint32 { @@ -428,7 +535,7 @@ type Hunk struct { func (x *Hunk) Reset() { *x = Hunk{} - mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[7] + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -440,7 +547,7 @@ func (x *Hunk) String() string { func (*Hunk) ProtoMessage() {} func (x *Hunk) ProtoReflect() protoreflect.Message { - mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[7] + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -453,7 +560,7 @@ func (x *Hunk) ProtoReflect() protoreflect.Message { // Deprecated: Use Hunk.ProtoReflect.Descriptor instead. func (*Hunk) Descriptor() ([]byte, []int) { - return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{7} + return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{9} } func (x *Hunk) GetNovelLhs() []uint32 { @@ -488,7 +595,7 @@ type ByteChanges struct { func (x *ByteChanges) Reset() { *x = ByteChanges{} - mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[8] + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -500,7 +607,7 @@ func (x *ByteChanges) String() string { func (*ByteChanges) ProtoMessage() {} func (x *ByteChanges) ProtoReflect() protoreflect.Message { - mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[8] + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -513,7 +620,7 @@ func (x *ByteChanges) ProtoReflect() protoreflect.Message { // Deprecated: Use ByteChanges.ProtoReflect.Descriptor instead. func (*ByteChanges) Descriptor() ([]byte, []int) { - return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{8} + return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{10} } func (x *ByteChanges) GetLhs() uint64 { @@ -543,7 +650,7 @@ type FileDiff struct { func (x *FileDiff) Reset() { *x = FileDiff{} - mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[9] + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -555,7 +662,7 @@ func (x *FileDiff) String() string { func (*FileDiff) ProtoMessage() {} func (x *FileDiff) ProtoReflect() protoreflect.Message { - mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[9] + mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -568,7 +675,7 @@ func (x *FileDiff) ProtoReflect() protoreflect.Message { // Deprecated: Use FileDiff.ProtoReflect.Descriptor instead. func (*FileDiff) Descriptor() ([]byte, []int) { - return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{9} + return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{11} } func (x *FileDiff) GetLhsSrc() *FileContent { @@ -610,7 +717,12 @@ var File_gitmirror_v1_gitmirror_proto protoreflect.FileDescriptor const file_gitmirror_v1_gitmirror_proto_rawDesc = "" + "\n" + - "\x1cgitmirror/v1/gitmirror.proto\x12\fgitmirror.v1\"\\\n" + + "\x1cgitmirror/v1/gitmirror.proto\x12\fgitmirror.v1\"6\n" + + "\x0eGetBlobRequest\x12\x12\n" + + "\x04repo\x18\x01 \x01(\tR\x04repo\x12\x10\n" + + "\x03oid\x18\x02 \x01(\tR\x03oid\"\x1f\n" + + "\tBlobChunk\x12\x12\n" + + "\x04data\x18\x01 \x01(\fR\x04data\"\\\n" + "\x0eCommitsRequest\x12\x12\n" + "\x04repo\x18\x01 \x01(\tR\x04repo\x126\n" + "\aoptions\x18\x02 \x01(\v2\x1c.gitmirror.v1.CommitsOptionsR\aoptions\"\x10\n" + @@ -626,12 +738,13 @@ const file_gitmirror_v1_gitmirror_proto_rawDesc = "" + "\tfrom_base\x18\x02 \x01(\tR\bfromBase\x12\x1b\n" + "\tfrom_head\x18\x03 \x01(\tR\bfromHead\x12\x17\n" + "\ato_base\x18\x04 \x01(\tR\x06toBase\x12\x17\n" + - "\ato_head\x18\x05 \x01(\tR\x06toHead\"d\n" + + "\ato_head\x18\x05 \x01(\tR\x06toHead\"\x87\x01\n" + "\vFileContent\x12\x12\n" + "\x04path\x18\x01 \x01(\tR\x04path\x12\x10\n" + "\x03oid\x18\x02 \x01(\tR\x03oid\x12\x12\n" + "\x04size\x18\x03 \x01(\x04R\x04size\x12\x1b\n" + - "\tis_binary\x18\x04 \x01(\bR\bisBinary\"H\n" + + "\tis_binary\x18\x04 \x01(\bR\bisBinary\x12!\n" + + "\fis_submodule\x18\x05 \x01(\bR\visSubmodule\"H\n" + "\bLinePair\x12\x15\n" + "\x03lhs\x18\x01 \x01(\rH\x00R\x03lhs\x88\x01\x01\x12\x15\n" + "\x03rhs\x18\x02 \x01(\rH\x01R\x03rhs\x88\x01\x01B\x06\n" + @@ -650,11 +763,12 @@ const file_gitmirror_v1_gitmirror_proto_rawDesc = "" + "\x05hunks\x18\x03 \x03(\v2\x12.gitmirror.v1.HunkR\x05hunks\x12H\n" + "\x10has_byte_changes\x18\x04 \x01(\v2\x19.gitmirror.v1.ByteChangesH\x00R\x0ehasByteChanges\x88\x01\x01\x122\n" + "\x15has_syntactic_changes\x18\x05 \x01(\bR\x13hasSyntacticChangesB\x13\n" + - "\x11_has_byte_changes2\xde\x01\n" + + "\x11_has_byte_changes2\xa2\x02\n" + "\x10GitMirrorService\x12F\n" + "\aCommits\x12\x1c.gitmirror.v1.CommitsRequest\x1a\x1d.gitmirror.v1.CommitsResponse\x12;\n" + "\x04Diff\x12\x19.gitmirror.v1.DiffRequest\x1a\x16.gitmirror.v1.FileDiff0\x01\x12E\n" + - "\tInterdiff\x12\x1e.gitmirror.v1.InterdiffRequest\x1a\x16.gitmirror.v1.FileDiff0\x01B2Z0tangled.org/core/gitmirror/proto/gen;gitmirrorv1b\x06proto3" + "\tInterdiff\x12\x1e.gitmirror.v1.InterdiffRequest\x1a\x16.gitmirror.v1.FileDiff0\x01\x12B\n" + + "\aGetBlob\x12\x1c.gitmirror.v1.GetBlobRequest\x1a\x17.gitmirror.v1.BlobChunk0\x01B2Z0tangled.org/core/gitmirror/proto/gen;gitmirrorv1b\x06proto3" var ( file_gitmirror_v1_gitmirror_proto_rawDescOnce sync.Once @@ -668,37 +782,41 @@ func file_gitmirror_v1_gitmirror_proto_rawDescGZIP() []byte { return file_gitmirror_v1_gitmirror_proto_rawDescData } -var file_gitmirror_v1_gitmirror_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_gitmirror_v1_gitmirror_proto_msgTypes = make([]protoimpl.MessageInfo, 12) var file_gitmirror_v1_gitmirror_proto_goTypes = []any{ - (*CommitsRequest)(nil), // 0: gitmirror.v1.CommitsRequest - (*CommitsOptions)(nil), // 1: gitmirror.v1.CommitsOptions - (*CommitsResponse)(nil), // 2: gitmirror.v1.CommitsResponse - (*DiffRequest)(nil), // 3: gitmirror.v1.DiffRequest - (*InterdiffRequest)(nil), // 4: gitmirror.v1.InterdiffRequest - (*FileContent)(nil), // 5: gitmirror.v1.FileContent - (*LinePair)(nil), // 6: gitmirror.v1.LinePair - (*Hunk)(nil), // 7: gitmirror.v1.Hunk - (*ByteChanges)(nil), // 8: gitmirror.v1.ByteChanges - (*FileDiff)(nil), // 9: gitmirror.v1.FileDiff + (*GetBlobRequest)(nil), // 0: gitmirror.v1.GetBlobRequest + (*BlobChunk)(nil), // 1: gitmirror.v1.BlobChunk + (*CommitsRequest)(nil), // 2: gitmirror.v1.CommitsRequest + (*CommitsOptions)(nil), // 3: gitmirror.v1.CommitsOptions + (*CommitsResponse)(nil), // 4: gitmirror.v1.CommitsResponse + (*DiffRequest)(nil), // 5: gitmirror.v1.DiffRequest + (*InterdiffRequest)(nil), // 6: gitmirror.v1.InterdiffRequest + (*FileContent)(nil), // 7: gitmirror.v1.FileContent + (*LinePair)(nil), // 8: gitmirror.v1.LinePair + (*Hunk)(nil), // 9: gitmirror.v1.Hunk + (*ByteChanges)(nil), // 10: gitmirror.v1.ByteChanges + (*FileDiff)(nil), // 11: gitmirror.v1.FileDiff } var file_gitmirror_v1_gitmirror_proto_depIdxs = []int32{ - 1, // 0: gitmirror.v1.CommitsRequest.options:type_name -> gitmirror.v1.CommitsOptions - 6, // 1: gitmirror.v1.Hunk.lines:type_name -> gitmirror.v1.LinePair - 5, // 2: gitmirror.v1.FileDiff.lhs_src:type_name -> gitmirror.v1.FileContent - 5, // 3: gitmirror.v1.FileDiff.rhs_src:type_name -> gitmirror.v1.FileContent - 7, // 4: gitmirror.v1.FileDiff.hunks:type_name -> gitmirror.v1.Hunk - 8, // 5: gitmirror.v1.FileDiff.has_byte_changes:type_name -> gitmirror.v1.ByteChanges - 0, // 6: gitmirror.v1.GitMirrorService.Commits:input_type -> gitmirror.v1.CommitsRequest - 3, // 7: gitmirror.v1.GitMirrorService.Diff:input_type -> gitmirror.v1.DiffRequest - 4, // 8: gitmirror.v1.GitMirrorService.Interdiff:input_type -> gitmirror.v1.InterdiffRequest - 2, // 9: gitmirror.v1.GitMirrorService.Commits:output_type -> gitmirror.v1.CommitsResponse - 9, // 10: gitmirror.v1.GitMirrorService.Diff:output_type -> gitmirror.v1.FileDiff - 9, // 11: gitmirror.v1.GitMirrorService.Interdiff:output_type -> gitmirror.v1.FileDiff - 9, // [9:12] is the sub-list for method output_type - 6, // [6:9] is the sub-list for method input_type - 6, // [6:6] is the sub-list for extension type_name - 6, // [6:6] is the sub-list for extension extendee - 0, // [0:6] is the sub-list for field type_name + 3, // 0: gitmirror.v1.CommitsRequest.options:type_name -> gitmirror.v1.CommitsOptions + 8, // 1: gitmirror.v1.Hunk.lines:type_name -> gitmirror.v1.LinePair + 7, // 2: gitmirror.v1.FileDiff.lhs_src:type_name -> gitmirror.v1.FileContent + 7, // 3: gitmirror.v1.FileDiff.rhs_src:type_name -> gitmirror.v1.FileContent + 9, // 4: gitmirror.v1.FileDiff.hunks:type_name -> gitmirror.v1.Hunk + 10, // 5: gitmirror.v1.FileDiff.has_byte_changes:type_name -> gitmirror.v1.ByteChanges + 2, // 6: gitmirror.v1.GitMirrorService.Commits:input_type -> gitmirror.v1.CommitsRequest + 5, // 7: gitmirror.v1.GitMirrorService.Diff:input_type -> gitmirror.v1.DiffRequest + 6, // 8: gitmirror.v1.GitMirrorService.Interdiff:input_type -> gitmirror.v1.InterdiffRequest + 0, // 9: gitmirror.v1.GitMirrorService.GetBlob:input_type -> gitmirror.v1.GetBlobRequest + 4, // 10: gitmirror.v1.GitMirrorService.Commits:output_type -> gitmirror.v1.CommitsResponse + 11, // 11: gitmirror.v1.GitMirrorService.Diff:output_type -> gitmirror.v1.FileDiff + 11, // 12: gitmirror.v1.GitMirrorService.Interdiff:output_type -> gitmirror.v1.FileDiff + 1, // 13: gitmirror.v1.GitMirrorService.GetBlob:output_type -> gitmirror.v1.BlobChunk + 10, // [10:14] is the sub-list for method output_type + 6, // [6:10] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name } func init() { file_gitmirror_v1_gitmirror_proto_init() } @@ -706,15 +824,15 @@ func file_gitmirror_v1_gitmirror_proto_init() { if File_gitmirror_v1_gitmirror_proto != nil { return } - file_gitmirror_v1_gitmirror_proto_msgTypes[6].OneofWrappers = []any{} - file_gitmirror_v1_gitmirror_proto_msgTypes[9].OneofWrappers = []any{} + file_gitmirror_v1_gitmirror_proto_msgTypes[8].OneofWrappers = []any{} + file_gitmirror_v1_gitmirror_proto_msgTypes[11].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_gitmirror_v1_gitmirror_proto_rawDesc), len(file_gitmirror_v1_gitmirror_proto_rawDesc)), NumEnums: 0, - NumMessages: 10, + NumMessages: 12, NumExtensions: 0, NumServices: 1, }, diff --git a/gitmirror/proto/gen/gitmirror_grpc.pb.go b/gitmirror/proto/gen/gitmirror_grpc.pb.go index ec5d5948..4e6ad768 100644 --- a/gitmirror/proto/gen/gitmirror_grpc.pb.go +++ b/gitmirror/proto/gen/gitmirror_grpc.pb.go @@ -22,6 +22,7 @@ const ( GitMirrorService_Commits_FullMethodName = "/gitmirror.v1.GitMirrorService/Commits" GitMirrorService_Diff_FullMethodName = "/gitmirror.v1.GitMirrorService/Diff" GitMirrorService_Interdiff_FullMethodName = "/gitmirror.v1.GitMirrorService/Interdiff" + GitMirrorService_GetBlob_FullMethodName = "/gitmirror.v1.GitMirrorService/GetBlob" ) // GitMirrorServiceClient is the client API for GitMirrorService service. @@ -31,6 +32,8 @@ type GitMirrorServiceClient interface { Commits(ctx context.Context, in *CommitsRequest, opts ...grpc.CallOption) (*CommitsResponse, error) Diff(ctx context.Context, in *DiffRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[FileDiff], error) Interdiff(ctx context.Context, in *InterdiffRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[FileDiff], error) + // GetBlob streams a blob's raw bytes by OID, in chunks. + GetBlob(ctx context.Context, in *GetBlobRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[BlobChunk], error) } type gitMirrorServiceClient struct { @@ -89,6 +92,25 @@ func (c *gitMirrorServiceClient) Interdiff(ctx context.Context, in *InterdiffReq // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. type GitMirrorService_InterdiffClient = grpc.ServerStreamingClient[FileDiff] +func (c *gitMirrorServiceClient) GetBlob(ctx context.Context, in *GetBlobRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[BlobChunk], error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + stream, err := c.cc.NewStream(ctx, &GitMirrorService_ServiceDesc.Streams[2], GitMirrorService_GetBlob_FullMethodName, cOpts...) + if err != nil { + return nil, err + } + x := &grpc.GenericClientStream[GetBlobRequest, BlobChunk]{ClientStream: 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 +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type GitMirrorService_GetBlobClient = grpc.ServerStreamingClient[BlobChunk] + // GitMirrorServiceServer is the server API for GitMirrorService service. // All implementations must embed UnimplementedGitMirrorServiceServer // for forward compatibility. @@ -96,6 +118,8 @@ type GitMirrorServiceServer interface { Commits(context.Context, *CommitsRequest) (*CommitsResponse, error) Diff(*DiffRequest, grpc.ServerStreamingServer[FileDiff]) error Interdiff(*InterdiffRequest, grpc.ServerStreamingServer[FileDiff]) error + // GetBlob streams a blob's raw bytes by OID, in chunks. + GetBlob(*GetBlobRequest, grpc.ServerStreamingServer[BlobChunk]) error mustEmbedUnimplementedGitMirrorServiceServer() } @@ -115,6 +139,9 @@ func (UnimplementedGitMirrorServiceServer) Diff(*DiffRequest, grpc.ServerStreami func (UnimplementedGitMirrorServiceServer) Interdiff(*InterdiffRequest, grpc.ServerStreamingServer[FileDiff]) error { return status.Error(codes.Unimplemented, "method Interdiff not implemented") } +func (UnimplementedGitMirrorServiceServer) GetBlob(*GetBlobRequest, grpc.ServerStreamingServer[BlobChunk]) error { + return status.Error(codes.Unimplemented, "method GetBlob not implemented") +} func (UnimplementedGitMirrorServiceServer) mustEmbedUnimplementedGitMirrorServiceServer() {} func (UnimplementedGitMirrorServiceServer) testEmbeddedByValue() {} @@ -176,6 +203,17 @@ func _GitMirrorService_Interdiff_Handler(srv interface{}, stream grpc.ServerStre // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. type GitMirrorService_InterdiffServer = grpc.ServerStreamingServer[FileDiff] +func _GitMirrorService_GetBlob_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(GetBlobRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(GitMirrorServiceServer).GetBlob(m, &grpc.GenericServerStream[GetBlobRequest, BlobChunk]{ServerStream: stream}) +} + +// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name. +type GitMirrorService_GetBlobServer = grpc.ServerStreamingServer[BlobChunk] + // GitMirrorService_ServiceDesc is the grpc.ServiceDesc for GitMirrorService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -199,6 +237,11 @@ var GitMirrorService_ServiceDesc = grpc.ServiceDesc{ Handler: _GitMirrorService_Interdiff_Handler, ServerStreams: true, }, + { + StreamName: "GetBlob", + Handler: _GitMirrorService_GetBlob_Handler, + ServerStreams: true, + }, }, Metadata: "gitmirror/v1/gitmirror.proto", } diff --git a/gitmirror/proto/gitmirror/v1/gitmirror.proto b/gitmirror/proto/gitmirror/v1/gitmirror.proto index 79973948..aea16488 100644 --- a/gitmirror/proto/gitmirror/v1/gitmirror.proto +++ b/gitmirror/proto/gitmirror/v1/gitmirror.proto @@ -8,6 +8,20 @@ service GitMirrorService { rpc Commits(CommitsRequest) returns (CommitsResponse); rpc Diff(DiffRequest) returns (stream FileDiff); rpc Interdiff(InterdiffRequest) returns (stream FileDiff); + // GetBlob streams a blob's raw bytes by OID, in chunks. + rpc GetBlob(GetBlobRequest) returns (stream BlobChunk); +} + +message GetBlobRequest { + // repo carries the DID as a string. + string repo = 1; + // oid is the blob object id (hex). + string oid = 2; +} + +// One chunk of a blob's raw bytes; concatenate in arrival order. +message BlobChunk { + bytes data = 1; } message CommitsRequest { diff --git a/gitmirror/src/main.rs b/gitmirror/src/main.rs index a601e4fb..6b28b206 100644 --- a/gitmirror/src/main.rs +++ b/gitmirror/src/main.rs @@ -23,11 +23,15 @@ use tokio_stream::wrappers::ReceiverStream; use gitmirror::v1::git_mirror_service_server::{GitMirrorService, GitMirrorServiceServer}; use gitmirror::v1::{ - ByteChanges, CommitsRequest, CommitsResponse, DiffRequest, FileContent, FileDiff, Hunk, - InterdiffRequest, LinePair, + BlobChunk, ByteChanges, CommitsRequest, CommitsResponse, DiffRequest, FileContent, FileDiff, + GetBlobRequest, Hunk, InterdiffRequest, LinePair, }; type FileDiffStream = Pin> + Send>>; +type BlobChunkStream = Pin> + Send>>; + +/// Blob bytes are streamed in chunks of this size. +const BLOB_CHUNK_SIZE: usize = 64 * 1024; #[derive(Parser)] #[command(name = "gitmirror", about = "Git mirror gRPC service")] @@ -108,6 +112,41 @@ impl GitMirrorService for GitMirror { ) -> Result, Status> { Err(Status::unimplemented("not implemented")) } + + type GetBlobStream = BlobChunkStream; + + async fn get_blob( + &self, + request: Request, + ) -> Result, Status> { + let req = request.into_inner(); + let repo = self.open_repo(&req.repo)?; + let oid = gix::ObjectId::from_hex(req.oid.as_bytes()) + .map_err(|e| Status::invalid_argument(format!("bad oid '{}': {e}", req.oid)))?; + let safe = repo.into_sync(); + + let (tx, rx) = mpsc::channel::>(16); + tokio::task::spawn_blocking(move || { + let repo = safe.to_thread_local(); + let run = || -> anyhow::Result<()> { + let blob = repo.find_object(oid)?.try_into_blob()?; + for chunk in blob.data.chunks(BLOB_CHUNK_SIZE) { + let msg = BlobChunk { + data: chunk.to_vec(), + }; + if tx.blocking_send(Ok(msg)).is_err() { + break; // client hung up + } + } + Ok(()) + }; + if let Err(e) = run() { + let _ = tx.blocking_send(Err(Status::internal(e.to_string()))); + } + }); + + Ok(Response::new(Box::pin(ReceiverStream::new(rx)))) + } } /// Resolve a commit-ish ref string (oid, short oid, branch/tag) to its tree oid. diff --git a/shuttle/src/gen/gitmirror/v1/gitmirror.v1.rs b/shuttle/src/gen/gitmirror/v1/gitmirror.v1.rs new file mode 100644 index 00000000..d245bdb1 --- /dev/null +++ b/shuttle/src/gen/gitmirror/v1/gitmirror.v1.rs @@ -0,0 +1,118 @@ +// @generated +// This file is @generated by prost-build. +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct GetBlobRequest { + /// repo carries the DID as a string. + #[prost(string, tag="1")] + pub repo: ::prost::alloc::string::String, + /// oid is the blob object id (hex). + #[prost(string, tag="2")] + pub oid: ::prost::alloc::string::String, +} +/// One chunk of a blob's raw bytes; concatenate in arrival order. +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct BlobChunk { + #[prost(bytes="bytes", tag="1")] + pub data: ::prost::bytes::Bytes, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct CommitsRequest { + /// repo carries the DID as a string; parsing/validation is an impl detail. + #[prost(string, tag="1")] + pub repo: ::prost::alloc::string::String, + #[prost(message, optional, tag="2")] + pub options: ::core::option::Option, +} +/// TODO: fields for the Go CommitsOptions (e.g. ref/branch, limit, cursor) — +/// unspecified for now. +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct CommitsOptions { +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct CommitsResponse { + /// maps the Go \[\]string return. + #[prost(string, repeated, tag="1")] + pub commits: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct DiffRequest { + /// repo carries the DID as a string; parsing/validation is an impl detail. + #[prost(string, tag="1")] + pub repo: ::prost::alloc::string::String, + /// base/head are commit-ish ref strings. + #[prost(string, tag="2")] + pub base: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub head: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct InterdiffRequest { + /// repo carries the DID as a string. + #[prost(string, tag="1")] + pub repo: ::prost::alloc::string::String, + /// Old patch range (from_base..from_head) and new patch range (to_base..to_head). + #[prost(string, tag="2")] + pub from_base: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub from_head: ::prost::alloc::string::String, + #[prost(string, tag="4")] + pub to_base: ::prost::alloc::string::String, + #[prost(string, tag="5")] + pub to_head: ::prost::alloc::string::String, +} +// Diff and Interdiff stream one FileDiff per changed file. + +#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] +pub struct FileContent { + #[prost(string, tag="1")] + pub path: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub oid: ::prost::alloc::string::String, + #[prost(uint64, tag="3")] + pub size: u64, + #[prost(bool, tag="4")] + pub is_binary: bool, + #[prost(bool, tag="5")] + pub is_submodule: bool, +} +/// One aligned line pair; a side is absent (None) when unset. +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct LinePair { + #[prost(uint32, optional, tag="1")] + pub lhs: ::core::option::Option, + #[prost(uint32, optional, tag="2")] + pub rhs: ::core::option::Option, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Hunk { + /// LineNumber (u32) sets containing novel content per side. + #[prost(uint32, repeated, tag="1")] + pub novel_lhs: ::prost::alloc::vec::Vec, + #[prost(uint32, repeated, tag="2")] + pub novel_rhs: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="3")] + pub lines: ::prost::alloc::vec::Vec, +} +/// Number of bytes per side when the files differ (Rust Option<(usize, usize)>). +#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] +pub struct ByteChanges { + #[prost(uint64, tag="1")] + pub lhs: u64, + #[prost(uint64, tag="2")] + pub rhs: u64, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FileDiff { + #[prost(message, optional, tag="1")] + pub lhs_src: ::core::option::Option, + #[prost(message, optional, tag="2")] + pub rhs_src: ::core::option::Option, + #[prost(message, repeated, tag="3")] + pub hunks: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag="4")] + pub has_byte_changes: ::core::option::Option, + /// TODO: lhs_positions / rhs_positions (MatchedPos) — AST, added later. + #[prost(bool, tag="5")] + pub has_syntactic_changes: bool, +} +// @@protoc_insertion_point(module) diff --git a/shuttle/src/gen/spindle/agent/v1/spindle.agent.v1.rs b/shuttle/src/gen/spindle/agent/v1/spindle.agent.v1.rs index 04a12079..a274c2a2 100644 --- a/shuttle/src/gen/spindle/agent/v1/spindle.agent.v1.rs +++ b/shuttle/src/gen/spindle/agent/v1/spindle.agent.v1.rs @@ -2,145 +2,146 @@ // This file is @generated by prost-build. #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct Hello { - #[prost(uint32, tag = "1")] + #[prost(uint32, tag="1")] pub protocol_version: u32, - #[prost(string, tag = "2")] + #[prost(string, tag="2")] pub agent_version: ::prost::alloc::string::String, - #[prost(string, tag = "3")] + #[prost(string, tag="3")] pub boot_id: ::prost::alloc::string::String, - #[prost(string, tag = "4")] + #[prost(string, tag="4")] pub nix_version: ::prost::alloc::string::String, } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct Init { - #[prost(string, tag = "1")] + #[prost(string, tag="1")] pub job_id: ::prost::alloc::string::String, - #[prost(string, repeated, tag = "2")] + #[prost(string, repeated, tag="2")] pub cache_trusted_public_keys: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, - #[prost(uint32, tag = "3")] + #[prost(uint32, tag="3")] pub cache_read_proxy_port: u32, - #[prost(uint32, tag = "4")] + #[prost(uint32, tag="4")] pub cache_upload_proxy_port: u32, - #[prost(uint32, tag = "5")] + #[prost(uint32, tag="5")] pub dns_proxy_port: u32, } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct ExecStart { - #[prost(string, repeated, tag = "1")] + #[prost(string, repeated, tag="1")] pub argv: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, - #[prost(string, repeated, tag = "2")] + #[prost(string, repeated, tag="2")] pub env: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, - #[prost(string, tag = "3")] + #[prost(string, tag="3")] pub cwd: ::prost::alloc::string::String, - #[prost(string, tag = "4")] + #[prost(string, tag="4")] pub user: ::prost::alloc::string::String, - #[prost(uint32, tag = "5")] + #[prost(uint32, tag="5")] pub timeout_seconds: u32, } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct ExecStdout { - #[prost(string, tag = "1")] + #[prost(string, tag="1")] pub data: ::prost::alloc::string::String, } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct ExecStderr { - #[prost(string, tag = "1")] + #[prost(string, tag="1")] pub data: ::prost::alloc::string::String, } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct ExecExit { - #[prost(int32, tag = "1")] + #[prost(int32, tag="1")] pub exit_code: i32, - #[prost(string, tag = "2")] + #[prost(string, tag="2")] pub error: ::prost::alloc::string::String, /// set when the guest killed the step on its own timeout timer, so the host /// can classify it as a timeout rather than inferring failure from exit_code. - #[prost(bool, tag = "3")] + #[prost(bool, tag="3")] pub timed_out: bool, } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct ActivateConfig { - #[prost(string, tag = "1")] + #[prost(string, tag="1")] pub config_key: ::prost::alloc::string::String, - #[prost(string, tag = "2")] + #[prost(string, tag="2")] pub base_config_hash: ::prost::alloc::string::String, - #[prost(string, tag = "3")] + #[prost(string, tag="3")] pub user_config: ::prost::alloc::string::String, - #[prost(string, tag = "4")] + #[prost(string, tag="4")] pub toplevel: ::prost::alloc::string::String, - #[prost(uint32, tag = "5")] + #[prost(uint32, tag="5")] pub timeout_seconds: u32, } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct ActivateConfigResult { - #[prost(string, tag = "1")] + #[prost(string, tag="1")] pub config_key: ::prost::alloc::string::String, - #[prost(string, tag = "2")] + #[prost(string, tag="2")] pub toplevel: ::prost::alloc::string::String, - #[prost(string, tag = "3")] + #[prost(string, tag="3")] pub error: ::prost::alloc::string::String, } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct BuiltPaths { - #[prost(string, repeated, tag = "1")] + #[prost(string, repeated, tag="1")] pub paths: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, - #[prost(string, tag = "2")] + #[prost(string, tag="2")] pub reason: ::prost::alloc::string::String, } #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] pub struct CacheDrain { - #[prost(uint32, tag = "1")] + #[prost(uint32, tag="1")] pub timeout_seconds: u32, } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct CacheDrainResult { - #[prost(string, tag = "1")] + #[prost(string, tag="1")] pub error: ::prost::alloc::string::String, - #[prost(uint32, tag = "2")] + #[prost(uint32, tag="2")] pub cache_queued: u32, - #[prost(uint32, tag = "3")] + #[prost(uint32, tag="3")] pub cache_active: u32, - #[prost(uint32, tag = "4")] + #[prost(uint32, tag="4")] pub cache_uploaded: u32, - #[prost(uint32, tag = "5")] + #[prost(uint32, tag="5")] pub cache_failed: u32, } #[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)] -pub struct Poweroff {} +pub struct Poweroff { +} #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct PoweroffResult { - #[prost(string, tag = "1")] + #[prost(string, tag="1")] pub error: ::prost::alloc::string::String, } #[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)] pub struct Message { - #[prost(string, tag = "1")] + #[prost(string, tag="1")] pub id: ::prost::alloc::string::String, - #[prost(message, optional, tag = "2")] + #[prost(message, optional, tag="2")] pub hello: ::core::option::Option, - #[prost(message, optional, tag = "3")] + #[prost(message, optional, tag="3")] pub init: ::core::option::Option, - #[prost(message, optional, tag = "4")] + #[prost(message, optional, tag="4")] pub exec_start: ::core::option::Option, - #[prost(message, optional, tag = "5")] + #[prost(message, optional, tag="5")] pub exec_stdout: ::core::option::Option, - #[prost(message, optional, tag = "6")] + #[prost(message, optional, tag="6")] pub exec_stderr: ::core::option::Option, - #[prost(message, optional, tag = "7")] + #[prost(message, optional, tag="7")] pub exec_exit: ::core::option::Option, - #[prost(message, optional, tag = "8")] + #[prost(message, optional, tag="8")] pub activate_config: ::core::option::Option, - #[prost(message, optional, tag = "9")] + #[prost(message, optional, tag="9")] pub activate_config_result: ::core::option::Option, - #[prost(message, optional, tag = "10")] + #[prost(message, optional, tag="10")] pub built_paths: ::core::option::Option, - #[prost(message, optional, tag = "11")] + #[prost(message, optional, tag="11")] pub cache_drain: ::core::option::Option, - #[prost(message, optional, tag = "12")] + #[prost(message, optional, tag="12")] pub cache_drain_result: ::core::option::Option, - #[prost(message, optional, tag = "13")] + #[prost(message, optional, tag="13")] pub poweroff: ::core::option::Option, - #[prost(message, optional, tag = "14")] + #[prost(message, optional, tag="14")] pub poweroff_result: ::core::option::Option, } // @@protoc_insertion_point(module)