···11+# Gitmirror
22+33+gitmirror is alternative name of Knotmirror v2. It is designed to be run under the AppView as git syncing service.
44+Because it's internal sync service like tap or hydrant, it uses grpc over xrpc.
···11+//go:build ignore
22+33+// Example: how appview will call gitmirror's GitMirrorService.
44+//
55+// This file is build-ignored so it never breaks `go build ./...`. It is a
66+// copy-ready reference for wiring the client into appview once the Go stubs
77+// are generated (see gitmirror/buf.gen.yaml).
88+package main
99+1010+import (
1111+ "context"
1212+ "log"
1313+ "time"
1414+1515+ "google.golang.org/grpc"
1616+ "google.golang.org/grpc/credentials/insecure"
1717+1818+ gitmirrorv1 "tangled.org/core/gitmirror/proto/gen"
1919+)
2020+2121+func main() {
2222+ conn, err := grpc.NewClient("127.0.0.1:9000",
2323+ grpc.WithTransportCredentials(insecure.NewCredentials()))
2424+ if err != nil {
2525+ log.Fatal(err)
2626+ }
2727+ defer conn.Close()
2828+2929+ client := gitmirrorv1.NewGitMirrorServiceClient(conn)
3030+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
3131+ defer cancel()
3232+3333+ // appview converts its syntax.DID to a string via did.String().
3434+ resp, err := client.Commits(ctx, &gitmirrorv1.CommitsRequest{
3535+ Repo: "did:plc:example",
3636+ Options: &gitmirrorv1.CommitsOptions{},
3737+ })
3838+ if err != nil {
3939+ log.Fatal(err)
4040+ }
4141+ log.Println(resp.GetCommits())
4242+}
+1119
gitmirror/proto/gen/gitmirror.pb.go
···11+// Code generated by protoc-gen-go. DO NOT EDIT.
22+// versions:
33+// protoc-gen-go v1.36.11
44+// protoc (unknown)
55+// source: gitmirror/v1/gitmirror.proto
66+77+package gitmirrorv1
88+99+import (
1010+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
1111+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
1212+ timestamppb "google.golang.org/protobuf/types/known/timestamppb"
1313+ reflect "reflect"
1414+ sync "sync"
1515+ unsafe "unsafe"
1616+)
1717+1818+const (
1919+ // Verify that this generated code is sufficiently up-to-date.
2020+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
2121+ // Verify that runtime/protoimpl is sufficiently up-to-date.
2222+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
2323+)
2424+2525+type DiffRequest_ComparisonType int32
2626+2727+const (
2828+ DiffRequest_COMPARISON_TYPE_UNSPECIFIED DiffRequest_ComparisonType = 0
2929+ // Corresponds to the BASE..HEAD syntax that only returns any commits that
3030+ // are in HEAD but not in BASE.
3131+ DiffRequest_COMPARISON_TYPE_ONLY_IN_HEAD DiffRequest_ComparisonType = 1
3232+ // Corresponds to the BASE...HEAD syntax that returns any commits that are
3333+ // not in both BASE and HEAD.
3434+ DiffRequest_COMPARISON_TYPE_INTERSECTION DiffRequest_ComparisonType = 2
3535+)
3636+3737+// Enum value maps for DiffRequest_ComparisonType.
3838+var (
3939+ DiffRequest_ComparisonType_name = map[int32]string{
4040+ 0: "COMPARISON_TYPE_UNSPECIFIED",
4141+ 1: "COMPARISON_TYPE_ONLY_IN_HEAD",
4242+ 2: "COMPARISON_TYPE_INTERSECTION",
4343+ }
4444+ DiffRequest_ComparisonType_value = map[string]int32{
4545+ "COMPARISON_TYPE_UNSPECIFIED": 0,
4646+ "COMPARISON_TYPE_ONLY_IN_HEAD": 1,
4747+ "COMPARISON_TYPE_INTERSECTION": 2,
4848+ }
4949+)
5050+5151+func (x DiffRequest_ComparisonType) Enum() *DiffRequest_ComparisonType {
5252+ p := new(DiffRequest_ComparisonType)
5353+ *p = x
5454+ return p
5555+}
5656+5757+func (x DiffRequest_ComparisonType) String() string {
5858+ return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
5959+}
6060+6161+func (DiffRequest_ComparisonType) Descriptor() protoreflect.EnumDescriptor {
6262+ return file_gitmirror_v1_gitmirror_proto_enumTypes[0].Descriptor()
6363+}
6464+6565+func (DiffRequest_ComparisonType) Type() protoreflect.EnumType {
6666+ return &file_gitmirror_v1_gitmirror_proto_enumTypes[0]
6767+}
6868+6969+func (x DiffRequest_ComparisonType) Number() protoreflect.EnumNumber {
7070+ return protoreflect.EnumNumber(x)
7171+}
7272+7373+// Deprecated: Use DiffRequest_ComparisonType.Descriptor instead.
7474+func (DiffRequest_ComparisonType) EnumDescriptor() ([]byte, []int) {
7575+ return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{4, 0}
7676+}
7777+7878+type CommitLogRequest struct {
7979+ state protoimpl.MessageState `protogen:"open.v1"`
8080+ // repo DID
8181+ Repo string `protobuf:"bytes,1,opt,name=repo,proto3" json:"repo,omitempty"`
8282+ // Ranges to include in the git log (revspec, "A..B", "A...B", etc.).
8383+ // At least one range, or all_refs must be specified.
8484+ Ranges [][]byte `protobuf:"bytes,2,rep,name=ranges,proto3" json:"ranges,omitempty"`
8585+ // If true, all refs are searched for commits.
8686+ // Must not be true when ranges are given.
8787+ AllRefs bool `protobuf:"varint,3,opt,name=all_refs,json=allRefs,proto3" json:"all_refs,omitempty"`
8888+ // After is an optional parameter to specify the earliest commit to consider.
8989+ After *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=after,proto3" json:"after,omitempty"`
9090+ // Before is an optional parameter to specify the latest commit to consider.
9191+ Before *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=before,proto3" json:"before,omitempty"`
9292+ // MaxCommits is an optional parameter to specify the maximum number of commits
9393+ // to return. If max_commits is 0, all commits that match the criteria will be
9494+ // returned.
9595+ MaxCommits uint32 `protobuf:"varint,6,opt,name=max_commits,json=maxCommits,proto3" json:"max_commits,omitempty"`
9696+ // Skip is an optional parameter to specify the number of commits to skip.
9797+ // This can be used to implement a poor mans pagination.
9898+ Skip uint32 `protobuf:"varint,7,opt,name=skip,proto3" json:"skip,omitempty"`
9999+ unknownFields protoimpl.UnknownFields
100100+ sizeCache protoimpl.SizeCache
101101+}
102102+103103+func (x *CommitLogRequest) Reset() {
104104+ *x = CommitLogRequest{}
105105+ mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[0]
106106+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
107107+ ms.StoreMessageInfo(mi)
108108+}
109109+110110+func (x *CommitLogRequest) String() string {
111111+ return protoimpl.X.MessageStringOf(x)
112112+}
113113+114114+func (*CommitLogRequest) ProtoMessage() {}
115115+116116+func (x *CommitLogRequest) ProtoReflect() protoreflect.Message {
117117+ mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[0]
118118+ if x != nil {
119119+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
120120+ if ms.LoadMessageInfo() == nil {
121121+ ms.StoreMessageInfo(mi)
122122+ }
123123+ return ms
124124+ }
125125+ return mi.MessageOf(x)
126126+}
127127+128128+// Deprecated: Use CommitLogRequest.ProtoReflect.Descriptor instead.
129129+func (*CommitLogRequest) Descriptor() ([]byte, []int) {
130130+ return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{0}
131131+}
132132+133133+func (x *CommitLogRequest) GetRepo() string {
134134+ if x != nil {
135135+ return x.Repo
136136+ }
137137+ return ""
138138+}
139139+140140+func (x *CommitLogRequest) GetRanges() [][]byte {
141141+ if x != nil {
142142+ return x.Ranges
143143+ }
144144+ return nil
145145+}
146146+147147+func (x *CommitLogRequest) GetAllRefs() bool {
148148+ if x != nil {
149149+ return x.AllRefs
150150+ }
151151+ return false
152152+}
153153+154154+func (x *CommitLogRequest) GetAfter() *timestamppb.Timestamp {
155155+ if x != nil {
156156+ return x.After
157157+ }
158158+ return nil
159159+}
160160+161161+func (x *CommitLogRequest) GetBefore() *timestamppb.Timestamp {
162162+ if x != nil {
163163+ return x.Before
164164+ }
165165+ return nil
166166+}
167167+168168+func (x *CommitLogRequest) GetMaxCommits() uint32 {
169169+ if x != nil {
170170+ return x.MaxCommits
171171+ }
172172+ return 0
173173+}
174174+175175+func (x *CommitLogRequest) GetSkip() uint32 {
176176+ if x != nil {
177177+ return x.Skip
178178+ }
179179+ return 0
180180+}
181181+182182+type CommitLogResponse struct {
183183+ state protoimpl.MessageState `protogen:"open.v1"`
184184+ Commits []*GitCommit `protobuf:"bytes,1,rep,name=commits,proto3" json:"commits,omitempty"`
185185+ unknownFields protoimpl.UnknownFields
186186+ sizeCache protoimpl.SizeCache
187187+}
188188+189189+func (x *CommitLogResponse) Reset() {
190190+ *x = CommitLogResponse{}
191191+ mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[1]
192192+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
193193+ ms.StoreMessageInfo(mi)
194194+}
195195+196196+func (x *CommitLogResponse) String() string {
197197+ return protoimpl.X.MessageStringOf(x)
198198+}
199199+200200+func (*CommitLogResponse) ProtoMessage() {}
201201+202202+func (x *CommitLogResponse) ProtoReflect() protoreflect.Message {
203203+ mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[1]
204204+ if x != nil {
205205+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
206206+ if ms.LoadMessageInfo() == nil {
207207+ ms.StoreMessageInfo(mi)
208208+ }
209209+ return ms
210210+ }
211211+ return mi.MessageOf(x)
212212+}
213213+214214+// Deprecated: Use CommitLogResponse.ProtoReflect.Descriptor instead.
215215+func (*CommitLogResponse) Descriptor() ([]byte, []int) {
216216+ return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{1}
217217+}
218218+219219+func (x *CommitLogResponse) GetCommits() []*GitCommit {
220220+ if x != nil {
221221+ return x.Commits
222222+ }
223223+ return nil
224224+}
225225+226226+type GetBlobRequest struct {
227227+ state protoimpl.MessageState `protogen:"open.v1"`
228228+ // repo carries the DID as a string.
229229+ Repo string `protobuf:"bytes,1,opt,name=repo,proto3" json:"repo,omitempty"`
230230+ // oid is the blob object id (hex).
231231+ Oid string `protobuf:"bytes,2,opt,name=oid,proto3" json:"oid,omitempty"`
232232+ unknownFields protoimpl.UnknownFields
233233+ sizeCache protoimpl.SizeCache
234234+}
235235+236236+func (x *GetBlobRequest) Reset() {
237237+ *x = GetBlobRequest{}
238238+ mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[2]
239239+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
240240+ ms.StoreMessageInfo(mi)
241241+}
242242+243243+func (x *GetBlobRequest) String() string {
244244+ return protoimpl.X.MessageStringOf(x)
245245+}
246246+247247+func (*GetBlobRequest) ProtoMessage() {}
248248+249249+func (x *GetBlobRequest) ProtoReflect() protoreflect.Message {
250250+ mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[2]
251251+ if x != nil {
252252+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
253253+ if ms.LoadMessageInfo() == nil {
254254+ ms.StoreMessageInfo(mi)
255255+ }
256256+ return ms
257257+ }
258258+ return mi.MessageOf(x)
259259+}
260260+261261+// Deprecated: Use GetBlobRequest.ProtoReflect.Descriptor instead.
262262+func (*GetBlobRequest) Descriptor() ([]byte, []int) {
263263+ return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{2}
264264+}
265265+266266+func (x *GetBlobRequest) GetRepo() string {
267267+ if x != nil {
268268+ return x.Repo
269269+ }
270270+ return ""
271271+}
272272+273273+func (x *GetBlobRequest) GetOid() string {
274274+ if x != nil {
275275+ return x.Oid
276276+ }
277277+ return ""
278278+}
279279+280280+type BlobChunk struct {
281281+ state protoimpl.MessageState `protogen:"open.v1"`
282282+ Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
283283+ unknownFields protoimpl.UnknownFields
284284+ sizeCache protoimpl.SizeCache
285285+}
286286+287287+func (x *BlobChunk) Reset() {
288288+ *x = BlobChunk{}
289289+ mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[3]
290290+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
291291+ ms.StoreMessageInfo(mi)
292292+}
293293+294294+func (x *BlobChunk) String() string {
295295+ return protoimpl.X.MessageStringOf(x)
296296+}
297297+298298+func (*BlobChunk) ProtoMessage() {}
299299+300300+func (x *BlobChunk) ProtoReflect() protoreflect.Message {
301301+ mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[3]
302302+ if x != nil {
303303+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
304304+ if ms.LoadMessageInfo() == nil {
305305+ ms.StoreMessageInfo(mi)
306306+ }
307307+ return ms
308308+ }
309309+ return mi.MessageOf(x)
310310+}
311311+312312+// Deprecated: Use BlobChunk.ProtoReflect.Descriptor instead.
313313+func (*BlobChunk) Descriptor() ([]byte, []int) {
314314+ return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{3}
315315+}
316316+317317+func (x *BlobChunk) GetData() []byte {
318318+ if x != nil {
319319+ return x.Data
320320+ }
321321+ return nil
322322+}
323323+324324+type DiffRequest struct {
325325+ state protoimpl.MessageState `protogen:"open.v1"`
326326+ // repo carries the DID as a string; parsing/validation is an impl detail.
327327+ Repo string `protobuf:"bytes,1,opt,name=repo,proto3" json:"repo,omitempty"`
328328+ // base_rev_spec/head_rev_spec are commit-ish ref strings.
329329+ // base_rev_spec is optional; when unset, diff head against head's first parent.
330330+ BaseRevSpec []byte `protobuf:"bytes,2,opt,name=base_rev_spec,json=baseRevSpec,proto3,oneof" json:"base_rev_spec,omitempty"`
331331+ HeadRevSpec []byte `protobuf:"bytes,3,opt,name=head_rev_spec,json=headRevSpec,proto3" json:"head_rev_spec,omitempty"`
332332+ ComparisonType DiffRequest_ComparisonType `protobuf:"varint,4,opt,name=comparison_type,json=comparisonType,proto3,enum=gitmirror.v1.DiffRequest_ComparisonType" json:"comparison_type,omitempty"`
333333+ unknownFields protoimpl.UnknownFields
334334+ sizeCache protoimpl.SizeCache
335335+}
336336+337337+func (x *DiffRequest) Reset() {
338338+ *x = DiffRequest{}
339339+ mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[4]
340340+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
341341+ ms.StoreMessageInfo(mi)
342342+}
343343+344344+func (x *DiffRequest) String() string {
345345+ return protoimpl.X.MessageStringOf(x)
346346+}
347347+348348+func (*DiffRequest) ProtoMessage() {}
349349+350350+func (x *DiffRequest) ProtoReflect() protoreflect.Message {
351351+ mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[4]
352352+ if x != nil {
353353+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
354354+ if ms.LoadMessageInfo() == nil {
355355+ ms.StoreMessageInfo(mi)
356356+ }
357357+ return ms
358358+ }
359359+ return mi.MessageOf(x)
360360+}
361361+362362+// Deprecated: Use DiffRequest.ProtoReflect.Descriptor instead.
363363+func (*DiffRequest) Descriptor() ([]byte, []int) {
364364+ return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{4}
365365+}
366366+367367+func (x *DiffRequest) GetRepo() string {
368368+ if x != nil {
369369+ return x.Repo
370370+ }
371371+ return ""
372372+}
373373+374374+func (x *DiffRequest) GetBaseRevSpec() []byte {
375375+ if x != nil {
376376+ return x.BaseRevSpec
377377+ }
378378+ return nil
379379+}
380380+381381+func (x *DiffRequest) GetHeadRevSpec() []byte {
382382+ if x != nil {
383383+ return x.HeadRevSpec
384384+ }
385385+ return nil
386386+}
387387+388388+func (x *DiffRequest) GetComparisonType() DiffRequest_ComparisonType {
389389+ if x != nil {
390390+ return x.ComparisonType
391391+ }
392392+ return DiffRequest_COMPARISON_TYPE_UNSPECIFIED
393393+}
394394+395395+type InterdiffRequest struct {
396396+ state protoimpl.MessageState `protogen:"open.v1"`
397397+ // repo carries the DID as a string.
398398+ Repo string `protobuf:"bytes,1,opt,name=repo,proto3" json:"repo,omitempty"`
399399+ // Old patch range (from_base..from_head) and new patch range (to_base..to_head).
400400+ FromBase []byte `protobuf:"bytes,2,opt,name=from_base,json=fromBase,proto3" json:"from_base,omitempty"`
401401+ FromHead []byte `protobuf:"bytes,3,opt,name=from_head,json=fromHead,proto3" json:"from_head,omitempty"`
402402+ ToBase []byte `protobuf:"bytes,4,opt,name=to_base,json=toBase,proto3" json:"to_base,omitempty"`
403403+ ToHead []byte `protobuf:"bytes,5,opt,name=to_head,json=toHead,proto3" json:"to_head,omitempty"`
404404+ unknownFields protoimpl.UnknownFields
405405+ sizeCache protoimpl.SizeCache
406406+}
407407+408408+func (x *InterdiffRequest) Reset() {
409409+ *x = InterdiffRequest{}
410410+ mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[5]
411411+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
412412+ ms.StoreMessageInfo(mi)
413413+}
414414+415415+func (x *InterdiffRequest) String() string {
416416+ return protoimpl.X.MessageStringOf(x)
417417+}
418418+419419+func (*InterdiffRequest) ProtoMessage() {}
420420+421421+func (x *InterdiffRequest) ProtoReflect() protoreflect.Message {
422422+ mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[5]
423423+ if x != nil {
424424+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
425425+ if ms.LoadMessageInfo() == nil {
426426+ ms.StoreMessageInfo(mi)
427427+ }
428428+ return ms
429429+ }
430430+ return mi.MessageOf(x)
431431+}
432432+433433+// Deprecated: Use InterdiffRequest.ProtoReflect.Descriptor instead.
434434+func (*InterdiffRequest) Descriptor() ([]byte, []int) {
435435+ return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{5}
436436+}
437437+438438+func (x *InterdiffRequest) GetRepo() string {
439439+ if x != nil {
440440+ return x.Repo
441441+ }
442442+ return ""
443443+}
444444+445445+func (x *InterdiffRequest) GetFromBase() []byte {
446446+ if x != nil {
447447+ return x.FromBase
448448+ }
449449+ return nil
450450+}
451451+452452+func (x *InterdiffRequest) GetFromHead() []byte {
453453+ if x != nil {
454454+ return x.FromHead
455455+ }
456456+ return nil
457457+}
458458+459459+func (x *InterdiffRequest) GetToBase() []byte {
460460+ if x != nil {
461461+ return x.ToBase
462462+ }
463463+ return nil
464464+}
465465+466466+func (x *InterdiffRequest) GetToHead() []byte {
467467+ if x != nil {
468468+ return x.ToHead
469469+ }
470470+ return nil
471471+}
472472+473473+type FileContent struct {
474474+ state protoimpl.MessageState `protogen:"open.v1"`
475475+ Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
476476+ Oid string `protobuf:"bytes,2,opt,name=oid,proto3" json:"oid,omitempty"`
477477+ Size uint64 `protobuf:"varint,3,opt,name=size,proto3" json:"size,omitempty"`
478478+ IsBinary bool `protobuf:"varint,4,opt,name=is_binary,json=isBinary,proto3" json:"is_binary,omitempty"`
479479+ IsSubmodule bool `protobuf:"varint,5,opt,name=is_submodule,json=isSubmodule,proto3" json:"is_submodule,omitempty"`
480480+ // Raw file bytes, inlined when the oid is not fetchable by the client
481481+ // (e.g. interdiff)
482482+ Content []byte `protobuf:"bytes,6,opt,name=content,proto3,oneof" json:"content,omitempty"`
483483+ unknownFields protoimpl.UnknownFields
484484+ sizeCache protoimpl.SizeCache
485485+}
486486+487487+func (x *FileContent) Reset() {
488488+ *x = FileContent{}
489489+ mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[6]
490490+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
491491+ ms.StoreMessageInfo(mi)
492492+}
493493+494494+func (x *FileContent) String() string {
495495+ return protoimpl.X.MessageStringOf(x)
496496+}
497497+498498+func (*FileContent) ProtoMessage() {}
499499+500500+func (x *FileContent) ProtoReflect() protoreflect.Message {
501501+ mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[6]
502502+ if x != nil {
503503+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
504504+ if ms.LoadMessageInfo() == nil {
505505+ ms.StoreMessageInfo(mi)
506506+ }
507507+ return ms
508508+ }
509509+ return mi.MessageOf(x)
510510+}
511511+512512+// Deprecated: Use FileContent.ProtoReflect.Descriptor instead.
513513+func (*FileContent) Descriptor() ([]byte, []int) {
514514+ return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{6}
515515+}
516516+517517+func (x *FileContent) GetPath() string {
518518+ if x != nil {
519519+ return x.Path
520520+ }
521521+ return ""
522522+}
523523+524524+func (x *FileContent) GetOid() string {
525525+ if x != nil {
526526+ return x.Oid
527527+ }
528528+ return ""
529529+}
530530+531531+func (x *FileContent) GetSize() uint64 {
532532+ if x != nil {
533533+ return x.Size
534534+ }
535535+ return 0
536536+}
537537+538538+func (x *FileContent) GetIsBinary() bool {
539539+ if x != nil {
540540+ return x.IsBinary
541541+ }
542542+ return false
543543+}
544544+545545+func (x *FileContent) GetIsSubmodule() bool {
546546+ if x != nil {
547547+ return x.IsSubmodule
548548+ }
549549+ return false
550550+}
551551+552552+func (x *FileContent) GetContent() []byte {
553553+ if x != nil {
554554+ return x.Content
555555+ }
556556+ return nil
557557+}
558558+559559+// One aligned line pair; a side is absent (None) when unset.
560560+type LinePair struct {
561561+ state protoimpl.MessageState `protogen:"open.v1"`
562562+ Lhs *uint32 `protobuf:"varint,1,opt,name=lhs,proto3,oneof" json:"lhs,omitempty"`
563563+ Rhs *uint32 `protobuf:"varint,2,opt,name=rhs,proto3,oneof" json:"rhs,omitempty"`
564564+ unknownFields protoimpl.UnknownFields
565565+ sizeCache protoimpl.SizeCache
566566+}
567567+568568+func (x *LinePair) Reset() {
569569+ *x = LinePair{}
570570+ mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[7]
571571+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
572572+ ms.StoreMessageInfo(mi)
573573+}
574574+575575+func (x *LinePair) String() string {
576576+ return protoimpl.X.MessageStringOf(x)
577577+}
578578+579579+func (*LinePair) ProtoMessage() {}
580580+581581+func (x *LinePair) ProtoReflect() protoreflect.Message {
582582+ mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[7]
583583+ if x != nil {
584584+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
585585+ if ms.LoadMessageInfo() == nil {
586586+ ms.StoreMessageInfo(mi)
587587+ }
588588+ return ms
589589+ }
590590+ return mi.MessageOf(x)
591591+}
592592+593593+// Deprecated: Use LinePair.ProtoReflect.Descriptor instead.
594594+func (*LinePair) Descriptor() ([]byte, []int) {
595595+ return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{7}
596596+}
597597+598598+func (x *LinePair) GetLhs() uint32 {
599599+ if x != nil && x.Lhs != nil {
600600+ return *x.Lhs
601601+ }
602602+ return 0
603603+}
604604+605605+func (x *LinePair) GetRhs() uint32 {
606606+ if x != nil && x.Rhs != nil {
607607+ return *x.Rhs
608608+ }
609609+ return 0
610610+}
611611+612612+type Hunk struct {
613613+ state protoimpl.MessageState `protogen:"open.v1"`
614614+ // LineNumber (u32) sets containing novel content per side.
615615+ NovelLhs []uint32 `protobuf:"varint,1,rep,packed,name=novel_lhs,json=novelLhs,proto3" json:"novel_lhs,omitempty"`
616616+ NovelRhs []uint32 `protobuf:"varint,2,rep,packed,name=novel_rhs,json=novelRhs,proto3" json:"novel_rhs,omitempty"`
617617+ Lines []*LinePair `protobuf:"bytes,3,rep,name=lines,proto3" json:"lines,omitempty"`
618618+ unknownFields protoimpl.UnknownFields
619619+ sizeCache protoimpl.SizeCache
620620+}
621621+622622+func (x *Hunk) Reset() {
623623+ *x = Hunk{}
624624+ mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[8]
625625+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
626626+ ms.StoreMessageInfo(mi)
627627+}
628628+629629+func (x *Hunk) String() string {
630630+ return protoimpl.X.MessageStringOf(x)
631631+}
632632+633633+func (*Hunk) ProtoMessage() {}
634634+635635+func (x *Hunk) ProtoReflect() protoreflect.Message {
636636+ mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[8]
637637+ if x != nil {
638638+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
639639+ if ms.LoadMessageInfo() == nil {
640640+ ms.StoreMessageInfo(mi)
641641+ }
642642+ return ms
643643+ }
644644+ return mi.MessageOf(x)
645645+}
646646+647647+// Deprecated: Use Hunk.ProtoReflect.Descriptor instead.
648648+func (*Hunk) Descriptor() ([]byte, []int) {
649649+ return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{8}
650650+}
651651+652652+func (x *Hunk) GetNovelLhs() []uint32 {
653653+ if x != nil {
654654+ return x.NovelLhs
655655+ }
656656+ return nil
657657+}
658658+659659+func (x *Hunk) GetNovelRhs() []uint32 {
660660+ if x != nil {
661661+ return x.NovelRhs
662662+ }
663663+ return nil
664664+}
665665+666666+func (x *Hunk) GetLines() []*LinePair {
667667+ if x != nil {
668668+ return x.Lines
669669+ }
670670+ return nil
671671+}
672672+673673+// Number of bytes per side when the files differ (Rust Option<(usize, usize)>).
674674+type ByteChanges struct {
675675+ state protoimpl.MessageState `protogen:"open.v1"`
676676+ Lhs uint64 `protobuf:"varint,1,opt,name=lhs,proto3" json:"lhs,omitempty"`
677677+ Rhs uint64 `protobuf:"varint,2,opt,name=rhs,proto3" json:"rhs,omitempty"`
678678+ unknownFields protoimpl.UnknownFields
679679+ sizeCache protoimpl.SizeCache
680680+}
681681+682682+func (x *ByteChanges) Reset() {
683683+ *x = ByteChanges{}
684684+ mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[9]
685685+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
686686+ ms.StoreMessageInfo(mi)
687687+}
688688+689689+func (x *ByteChanges) String() string {
690690+ return protoimpl.X.MessageStringOf(x)
691691+}
692692+693693+func (*ByteChanges) ProtoMessage() {}
694694+695695+func (x *ByteChanges) ProtoReflect() protoreflect.Message {
696696+ mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[9]
697697+ if x != nil {
698698+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
699699+ if ms.LoadMessageInfo() == nil {
700700+ ms.StoreMessageInfo(mi)
701701+ }
702702+ return ms
703703+ }
704704+ return mi.MessageOf(x)
705705+}
706706+707707+// Deprecated: Use ByteChanges.ProtoReflect.Descriptor instead.
708708+func (*ByteChanges) Descriptor() ([]byte, []int) {
709709+ return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{9}
710710+}
711711+712712+func (x *ByteChanges) GetLhs() uint64 {
713713+ if x != nil {
714714+ return x.Lhs
715715+ }
716716+ return 0
717717+}
718718+719719+func (x *ByteChanges) GetRhs() uint64 {
720720+ if x != nil {
721721+ return x.Rhs
722722+ }
723723+ return 0
724724+}
725725+726726+type FileDiff struct {
727727+ state protoimpl.MessageState `protogen:"open.v1"`
728728+ LhsSrc *FileContent `protobuf:"bytes,1,opt,name=lhs_src,json=lhsSrc,proto3" json:"lhs_src,omitempty"`
729729+ RhsSrc *FileContent `protobuf:"bytes,2,opt,name=rhs_src,json=rhsSrc,proto3" json:"rhs_src,omitempty"`
730730+ Hunks []*Hunk `protobuf:"bytes,3,rep,name=hunks,proto3" json:"hunks,omitempty"`
731731+ HasByteChanges *ByteChanges `protobuf:"bytes,4,opt,name=has_byte_changes,json=hasByteChanges,proto3,oneof" json:"has_byte_changes,omitempty"`
732732+ HasSyntacticChanges bool `protobuf:"varint,5,opt,name=has_syntactic_changes,json=hasSyntacticChanges,proto3" json:"has_syntactic_changes,omitempty"` // TODO: lhs_positions & rhs_positions
733733+ unknownFields protoimpl.UnknownFields
734734+ sizeCache protoimpl.SizeCache
735735+}
736736+737737+func (x *FileDiff) Reset() {
738738+ *x = FileDiff{}
739739+ mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[10]
740740+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
741741+ ms.StoreMessageInfo(mi)
742742+}
743743+744744+func (x *FileDiff) String() string {
745745+ return protoimpl.X.MessageStringOf(x)
746746+}
747747+748748+func (*FileDiff) ProtoMessage() {}
749749+750750+func (x *FileDiff) ProtoReflect() protoreflect.Message {
751751+ mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[10]
752752+ if x != nil {
753753+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
754754+ if ms.LoadMessageInfo() == nil {
755755+ ms.StoreMessageInfo(mi)
756756+ }
757757+ return ms
758758+ }
759759+ return mi.MessageOf(x)
760760+}
761761+762762+// Deprecated: Use FileDiff.ProtoReflect.Descriptor instead.
763763+func (*FileDiff) Descriptor() ([]byte, []int) {
764764+ return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{10}
765765+}
766766+767767+func (x *FileDiff) GetLhsSrc() *FileContent {
768768+ if x != nil {
769769+ return x.LhsSrc
770770+ }
771771+ return nil
772772+}
773773+774774+func (x *FileDiff) GetRhsSrc() *FileContent {
775775+ if x != nil {
776776+ return x.RhsSrc
777777+ }
778778+ return nil
779779+}
780780+781781+func (x *FileDiff) GetHunks() []*Hunk {
782782+ if x != nil {
783783+ return x.Hunks
784784+ }
785785+ return nil
786786+}
787787+788788+func (x *FileDiff) GetHasByteChanges() *ByteChanges {
789789+ if x != nil {
790790+ return x.HasByteChanges
791791+ }
792792+ return nil
793793+}
794794+795795+func (x *FileDiff) GetHasSyntacticChanges() bool {
796796+ if x != nil {
797797+ return x.HasSyntacticChanges
798798+ }
799799+ return false
800800+}
801801+802802+type GitCommit struct {
803803+ state protoimpl.MessageState `protogen:"open.v1"`
804804+ Oid string `protobuf:"bytes,1,opt,name=oid,proto3" json:"oid,omitempty"`
805805+ Author *GitSignature `protobuf:"bytes,2,opt,name=author,proto3" json:"author,omitempty"`
806806+ Committer *GitSignature `protobuf:"bytes,3,opt,name=committer,proto3" json:"committer,omitempty"`
807807+ Message []byte `protobuf:"bytes,4,opt,name=message,proto3" json:"message,omitempty"`
808808+ Parents []string `protobuf:"bytes,5,rep,name=parents,proto3" json:"parents,omitempty"`
809809+ ExtraHeaders map[string]string `protobuf:"bytes,6,rep,name=extra_headers,json=extraHeaders,proto3" json:"extra_headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
810810+ unknownFields protoimpl.UnknownFields
811811+ sizeCache protoimpl.SizeCache
812812+}
813813+814814+func (x *GitCommit) Reset() {
815815+ *x = GitCommit{}
816816+ mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[11]
817817+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
818818+ ms.StoreMessageInfo(mi)
819819+}
820820+821821+func (x *GitCommit) String() string {
822822+ return protoimpl.X.MessageStringOf(x)
823823+}
824824+825825+func (*GitCommit) ProtoMessage() {}
826826+827827+func (x *GitCommit) ProtoReflect() protoreflect.Message {
828828+ mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[11]
829829+ if x != nil {
830830+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
831831+ if ms.LoadMessageInfo() == nil {
832832+ ms.StoreMessageInfo(mi)
833833+ }
834834+ return ms
835835+ }
836836+ return mi.MessageOf(x)
837837+}
838838+839839+// Deprecated: Use GitCommit.ProtoReflect.Descriptor instead.
840840+func (*GitCommit) Descriptor() ([]byte, []int) {
841841+ return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{11}
842842+}
843843+844844+func (x *GitCommit) GetOid() string {
845845+ if x != nil {
846846+ return x.Oid
847847+ }
848848+ return ""
849849+}
850850+851851+func (x *GitCommit) GetAuthor() *GitSignature {
852852+ if x != nil {
853853+ return x.Author
854854+ }
855855+ return nil
856856+}
857857+858858+func (x *GitCommit) GetCommitter() *GitSignature {
859859+ if x != nil {
860860+ return x.Committer
861861+ }
862862+ return nil
863863+}
864864+865865+func (x *GitCommit) GetMessage() []byte {
866866+ if x != nil {
867867+ return x.Message
868868+ }
869869+ return nil
870870+}
871871+872872+func (x *GitCommit) GetParents() []string {
873873+ if x != nil {
874874+ return x.Parents
875875+ }
876876+ return nil
877877+}
878878+879879+func (x *GitCommit) GetExtraHeaders() map[string]string {
880880+ if x != nil {
881881+ return x.ExtraHeaders
882882+ }
883883+ return nil
884884+}
885885+886886+type GitSignature struct {
887887+ state protoimpl.MessageState `protogen:"open.v1"`
888888+ Name []byte `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
889889+ Email []byte `protobuf:"bytes,2,opt,name=email,proto3" json:"email,omitempty"`
890890+ Date *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=date,proto3" json:"date,omitempty"`
891891+ unknownFields protoimpl.UnknownFields
892892+ sizeCache protoimpl.SizeCache
893893+}
894894+895895+func (x *GitSignature) Reset() {
896896+ *x = GitSignature{}
897897+ mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[12]
898898+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
899899+ ms.StoreMessageInfo(mi)
900900+}
901901+902902+func (x *GitSignature) String() string {
903903+ return protoimpl.X.MessageStringOf(x)
904904+}
905905+906906+func (*GitSignature) ProtoMessage() {}
907907+908908+func (x *GitSignature) ProtoReflect() protoreflect.Message {
909909+ mi := &file_gitmirror_v1_gitmirror_proto_msgTypes[12]
910910+ if x != nil {
911911+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
912912+ if ms.LoadMessageInfo() == nil {
913913+ ms.StoreMessageInfo(mi)
914914+ }
915915+ return ms
916916+ }
917917+ return mi.MessageOf(x)
918918+}
919919+920920+// Deprecated: Use GitSignature.ProtoReflect.Descriptor instead.
921921+func (*GitSignature) Descriptor() ([]byte, []int) {
922922+ return file_gitmirror_v1_gitmirror_proto_rawDescGZIP(), []int{12}
923923+}
924924+925925+func (x *GitSignature) GetName() []byte {
926926+ if x != nil {
927927+ return x.Name
928928+ }
929929+ return nil
930930+}
931931+932932+func (x *GitSignature) GetEmail() []byte {
933933+ if x != nil {
934934+ return x.Email
935935+ }
936936+ return nil
937937+}
938938+939939+func (x *GitSignature) GetDate() *timestamppb.Timestamp {
940940+ if x != nil {
941941+ return x.Date
942942+ }
943943+ return nil
944944+}
945945+946946+var File_gitmirror_v1_gitmirror_proto protoreflect.FileDescriptor
947947+948948+const file_gitmirror_v1_gitmirror_proto_rawDesc = "" +
949949+ "\n" +
950950+ "\x1cgitmirror/v1/gitmirror.proto\x12\fgitmirror.v1\x1a\x1fgoogle/protobuf/timestamp.proto\"\xf4\x01\n" +
951951+ "\x10CommitLogRequest\x12\x12\n" +
952952+ "\x04repo\x18\x01 \x01(\tR\x04repo\x12\x16\n" +
953953+ "\x06ranges\x18\x02 \x03(\fR\x06ranges\x12\x19\n" +
954954+ "\ball_refs\x18\x03 \x01(\bR\aallRefs\x120\n" +
955955+ "\x05after\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\x05after\x122\n" +
956956+ "\x06before\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\x06before\x12\x1f\n" +
957957+ "\vmax_commits\x18\x06 \x01(\rR\n" +
958958+ "maxCommits\x12\x12\n" +
959959+ "\x04skip\x18\a \x01(\rR\x04skip\"F\n" +
960960+ "\x11CommitLogResponse\x121\n" +
961961+ "\acommits\x18\x01 \x03(\v2\x17.gitmirror.v1.GitCommitR\acommits\"6\n" +
962962+ "\x0eGetBlobRequest\x12\x12\n" +
963963+ "\x04repo\x18\x01 \x01(\tR\x04repo\x12\x10\n" +
964964+ "\x03oid\x18\x02 \x01(\tR\x03oid\"\x1f\n" +
965965+ "\tBlobChunk\x12\x12\n" +
966966+ "\x04data\x18\x01 \x01(\fR\x04data\"\xca\x02\n" +
967967+ "\vDiffRequest\x12\x12\n" +
968968+ "\x04repo\x18\x01 \x01(\tR\x04repo\x12'\n" +
969969+ "\rbase_rev_spec\x18\x02 \x01(\fH\x00R\vbaseRevSpec\x88\x01\x01\x12\"\n" +
970970+ "\rhead_rev_spec\x18\x03 \x01(\fR\vheadRevSpec\x12Q\n" +
971971+ "\x0fcomparison_type\x18\x04 \x01(\x0e2(.gitmirror.v1.DiffRequest.ComparisonTypeR\x0ecomparisonType\"u\n" +
972972+ "\x0eComparisonType\x12\x1f\n" +
973973+ "\x1bCOMPARISON_TYPE_UNSPECIFIED\x10\x00\x12 \n" +
974974+ "\x1cCOMPARISON_TYPE_ONLY_IN_HEAD\x10\x01\x12 \n" +
975975+ "\x1cCOMPARISON_TYPE_INTERSECTION\x10\x02B\x10\n" +
976976+ "\x0e_base_rev_spec\"\x92\x01\n" +
977977+ "\x10InterdiffRequest\x12\x12\n" +
978978+ "\x04repo\x18\x01 \x01(\tR\x04repo\x12\x1b\n" +
979979+ "\tfrom_base\x18\x02 \x01(\fR\bfromBase\x12\x1b\n" +
980980+ "\tfrom_head\x18\x03 \x01(\fR\bfromHead\x12\x17\n" +
981981+ "\ato_base\x18\x04 \x01(\fR\x06toBase\x12\x17\n" +
982982+ "\ato_head\x18\x05 \x01(\fR\x06toHead\"\xb2\x01\n" +
983983+ "\vFileContent\x12\x12\n" +
984984+ "\x04path\x18\x01 \x01(\tR\x04path\x12\x10\n" +
985985+ "\x03oid\x18\x02 \x01(\tR\x03oid\x12\x12\n" +
986986+ "\x04size\x18\x03 \x01(\x04R\x04size\x12\x1b\n" +
987987+ "\tis_binary\x18\x04 \x01(\bR\bisBinary\x12!\n" +
988988+ "\fis_submodule\x18\x05 \x01(\bR\visSubmodule\x12\x1d\n" +
989989+ "\acontent\x18\x06 \x01(\fH\x00R\acontent\x88\x01\x01B\n" +
990990+ "\n" +
991991+ "\b_content\"H\n" +
992992+ "\bLinePair\x12\x15\n" +
993993+ "\x03lhs\x18\x01 \x01(\rH\x00R\x03lhs\x88\x01\x01\x12\x15\n" +
994994+ "\x03rhs\x18\x02 \x01(\rH\x01R\x03rhs\x88\x01\x01B\x06\n" +
995995+ "\x04_lhsB\x06\n" +
996996+ "\x04_rhs\"n\n" +
997997+ "\x04Hunk\x12\x1b\n" +
998998+ "\tnovel_lhs\x18\x01 \x03(\rR\bnovelLhs\x12\x1b\n" +
999999+ "\tnovel_rhs\x18\x02 \x03(\rR\bnovelRhs\x12,\n" +
10001000+ "\x05lines\x18\x03 \x03(\v2\x16.gitmirror.v1.LinePairR\x05lines\"1\n" +
10011001+ "\vByteChanges\x12\x10\n" +
10021002+ "\x03lhs\x18\x01 \x01(\x04R\x03lhs\x12\x10\n" +
10031003+ "\x03rhs\x18\x02 \x01(\x04R\x03rhs\"\xaf\x02\n" +
10041004+ "\bFileDiff\x122\n" +
10051005+ "\alhs_src\x18\x01 \x01(\v2\x19.gitmirror.v1.FileContentR\x06lhsSrc\x122\n" +
10061006+ "\arhs_src\x18\x02 \x01(\v2\x19.gitmirror.v1.FileContentR\x06rhsSrc\x12(\n" +
10071007+ "\x05hunks\x18\x03 \x03(\v2\x12.gitmirror.v1.HunkR\x05hunks\x12H\n" +
10081008+ "\x10has_byte_changes\x18\x04 \x01(\v2\x19.gitmirror.v1.ByteChangesH\x00R\x0ehasByteChanges\x88\x01\x01\x122\n" +
10091009+ "\x15has_syntactic_changes\x18\x05 \x01(\bR\x13hasSyntacticChangesB\x13\n" +
10101010+ "\x11_has_byte_changes\"\xd0\x02\n" +
10111011+ "\tGitCommit\x12\x10\n" +
10121012+ "\x03oid\x18\x01 \x01(\tR\x03oid\x122\n" +
10131013+ "\x06author\x18\x02 \x01(\v2\x1a.gitmirror.v1.GitSignatureR\x06author\x128\n" +
10141014+ "\tcommitter\x18\x03 \x01(\v2\x1a.gitmirror.v1.GitSignatureR\tcommitter\x12\x18\n" +
10151015+ "\amessage\x18\x04 \x01(\fR\amessage\x12\x18\n" +
10161016+ "\aparents\x18\x05 \x03(\tR\aparents\x12N\n" +
10171017+ "\rextra_headers\x18\x06 \x03(\v2).gitmirror.v1.GitCommit.ExtraHeadersEntryR\fextraHeaders\x1a?\n" +
10181018+ "\x11ExtraHeadersEntry\x12\x10\n" +
10191019+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
10201020+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"h\n" +
10211021+ "\fGitSignature\x12\x12\n" +
10221022+ "\x04name\x18\x01 \x01(\fR\x04name\x12\x14\n" +
10231023+ "\x05email\x18\x02 \x01(\fR\x05email\x12.\n" +
10241024+ "\x04date\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\x04date2\xaa\x02\n" +
10251025+ "\x10GitMirrorService\x12N\n" +
10261026+ "\tCommitLog\x12\x1e.gitmirror.v1.CommitLogRequest\x1a\x1f.gitmirror.v1.CommitLogResponse0\x01\x12B\n" +
10271027+ "\aGetBlob\x12\x1c.gitmirror.v1.GetBlobRequest\x1a\x17.gitmirror.v1.BlobChunk0\x01\x12;\n" +
10281028+ "\x04Diff\x12\x19.gitmirror.v1.DiffRequest\x1a\x16.gitmirror.v1.FileDiff0\x01\x12E\n" +
10291029+ "\tInterdiff\x12\x1e.gitmirror.v1.InterdiffRequest\x1a\x16.gitmirror.v1.FileDiff0\x01B2Z0tangled.org/core/gitmirror/proto/gen;gitmirrorv1b\x06proto3"
10301030+10311031+var (
10321032+ file_gitmirror_v1_gitmirror_proto_rawDescOnce sync.Once
10331033+ file_gitmirror_v1_gitmirror_proto_rawDescData []byte
10341034+)
10351035+10361036+func file_gitmirror_v1_gitmirror_proto_rawDescGZIP() []byte {
10371037+ file_gitmirror_v1_gitmirror_proto_rawDescOnce.Do(func() {
10381038+ file_gitmirror_v1_gitmirror_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_gitmirror_v1_gitmirror_proto_rawDesc), len(file_gitmirror_v1_gitmirror_proto_rawDesc)))
10391039+ })
10401040+ return file_gitmirror_v1_gitmirror_proto_rawDescData
10411041+}
10421042+10431043+var file_gitmirror_v1_gitmirror_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
10441044+var file_gitmirror_v1_gitmirror_proto_msgTypes = make([]protoimpl.MessageInfo, 14)
10451045+var file_gitmirror_v1_gitmirror_proto_goTypes = []any{
10461046+ (DiffRequest_ComparisonType)(0), // 0: gitmirror.v1.DiffRequest.ComparisonType
10471047+ (*CommitLogRequest)(nil), // 1: gitmirror.v1.CommitLogRequest
10481048+ (*CommitLogResponse)(nil), // 2: gitmirror.v1.CommitLogResponse
10491049+ (*GetBlobRequest)(nil), // 3: gitmirror.v1.GetBlobRequest
10501050+ (*BlobChunk)(nil), // 4: gitmirror.v1.BlobChunk
10511051+ (*DiffRequest)(nil), // 5: gitmirror.v1.DiffRequest
10521052+ (*InterdiffRequest)(nil), // 6: gitmirror.v1.InterdiffRequest
10531053+ (*FileContent)(nil), // 7: gitmirror.v1.FileContent
10541054+ (*LinePair)(nil), // 8: gitmirror.v1.LinePair
10551055+ (*Hunk)(nil), // 9: gitmirror.v1.Hunk
10561056+ (*ByteChanges)(nil), // 10: gitmirror.v1.ByteChanges
10571057+ (*FileDiff)(nil), // 11: gitmirror.v1.FileDiff
10581058+ (*GitCommit)(nil), // 12: gitmirror.v1.GitCommit
10591059+ (*GitSignature)(nil), // 13: gitmirror.v1.GitSignature
10601060+ nil, // 14: gitmirror.v1.GitCommit.ExtraHeadersEntry
10611061+ (*timestamppb.Timestamp)(nil), // 15: google.protobuf.Timestamp
10621062+}
10631063+var file_gitmirror_v1_gitmirror_proto_depIdxs = []int32{
10641064+ 15, // 0: gitmirror.v1.CommitLogRequest.after:type_name -> google.protobuf.Timestamp
10651065+ 15, // 1: gitmirror.v1.CommitLogRequest.before:type_name -> google.protobuf.Timestamp
10661066+ 12, // 2: gitmirror.v1.CommitLogResponse.commits:type_name -> gitmirror.v1.GitCommit
10671067+ 0, // 3: gitmirror.v1.DiffRequest.comparison_type:type_name -> gitmirror.v1.DiffRequest.ComparisonType
10681068+ 8, // 4: gitmirror.v1.Hunk.lines:type_name -> gitmirror.v1.LinePair
10691069+ 7, // 5: gitmirror.v1.FileDiff.lhs_src:type_name -> gitmirror.v1.FileContent
10701070+ 7, // 6: gitmirror.v1.FileDiff.rhs_src:type_name -> gitmirror.v1.FileContent
10711071+ 9, // 7: gitmirror.v1.FileDiff.hunks:type_name -> gitmirror.v1.Hunk
10721072+ 10, // 8: gitmirror.v1.FileDiff.has_byte_changes:type_name -> gitmirror.v1.ByteChanges
10731073+ 13, // 9: gitmirror.v1.GitCommit.author:type_name -> gitmirror.v1.GitSignature
10741074+ 13, // 10: gitmirror.v1.GitCommit.committer:type_name -> gitmirror.v1.GitSignature
10751075+ 14, // 11: gitmirror.v1.GitCommit.extra_headers:type_name -> gitmirror.v1.GitCommit.ExtraHeadersEntry
10761076+ 15, // 12: gitmirror.v1.GitSignature.date:type_name -> google.protobuf.Timestamp
10771077+ 1, // 13: gitmirror.v1.GitMirrorService.CommitLog:input_type -> gitmirror.v1.CommitLogRequest
10781078+ 3, // 14: gitmirror.v1.GitMirrorService.GetBlob:input_type -> gitmirror.v1.GetBlobRequest
10791079+ 5, // 15: gitmirror.v1.GitMirrorService.Diff:input_type -> gitmirror.v1.DiffRequest
10801080+ 6, // 16: gitmirror.v1.GitMirrorService.Interdiff:input_type -> gitmirror.v1.InterdiffRequest
10811081+ 2, // 17: gitmirror.v1.GitMirrorService.CommitLog:output_type -> gitmirror.v1.CommitLogResponse
10821082+ 4, // 18: gitmirror.v1.GitMirrorService.GetBlob:output_type -> gitmirror.v1.BlobChunk
10831083+ 11, // 19: gitmirror.v1.GitMirrorService.Diff:output_type -> gitmirror.v1.FileDiff
10841084+ 11, // 20: gitmirror.v1.GitMirrorService.Interdiff:output_type -> gitmirror.v1.FileDiff
10851085+ 17, // [17:21] is the sub-list for method output_type
10861086+ 13, // [13:17] is the sub-list for method input_type
10871087+ 13, // [13:13] is the sub-list for extension type_name
10881088+ 13, // [13:13] is the sub-list for extension extendee
10891089+ 0, // [0:13] is the sub-list for field type_name
10901090+}
10911091+10921092+func init() { file_gitmirror_v1_gitmirror_proto_init() }
10931093+func file_gitmirror_v1_gitmirror_proto_init() {
10941094+ if File_gitmirror_v1_gitmirror_proto != nil {
10951095+ return
10961096+ }
10971097+ file_gitmirror_v1_gitmirror_proto_msgTypes[4].OneofWrappers = []any{}
10981098+ file_gitmirror_v1_gitmirror_proto_msgTypes[6].OneofWrappers = []any{}
10991099+ file_gitmirror_v1_gitmirror_proto_msgTypes[7].OneofWrappers = []any{}
11001100+ file_gitmirror_v1_gitmirror_proto_msgTypes[10].OneofWrappers = []any{}
11011101+ type x struct{}
11021102+ out := protoimpl.TypeBuilder{
11031103+ File: protoimpl.DescBuilder{
11041104+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
11051105+ RawDescriptor: unsafe.Slice(unsafe.StringData(file_gitmirror_v1_gitmirror_proto_rawDesc), len(file_gitmirror_v1_gitmirror_proto_rawDesc)),
11061106+ NumEnums: 1,
11071107+ NumMessages: 14,
11081108+ NumExtensions: 0,
11091109+ NumServices: 1,
11101110+ },
11111111+ GoTypes: file_gitmirror_v1_gitmirror_proto_goTypes,
11121112+ DependencyIndexes: file_gitmirror_v1_gitmirror_proto_depIdxs,
11131113+ EnumInfos: file_gitmirror_v1_gitmirror_proto_enumTypes,
11141114+ MessageInfos: file_gitmirror_v1_gitmirror_proto_msgTypes,
11151115+ }.Build()
11161116+ File_gitmirror_v1_gitmirror_proto = out.File
11171117+ file_gitmirror_v1_gitmirror_proto_goTypes = nil
11181118+ file_gitmirror_v1_gitmirror_proto_depIdxs = nil
11191119+}
+247
gitmirror/proto/gen/gitmirror_grpc.pb.go
···11+// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
22+// versions:
33+// - protoc-gen-go-grpc v1.6.2
44+// - protoc (unknown)
55+// source: gitmirror/v1/gitmirror.proto
66+77+package gitmirrorv1
88+99+import (
1010+ context "context"
1111+ grpc "google.golang.org/grpc"
1212+ codes "google.golang.org/grpc/codes"
1313+ status "google.golang.org/grpc/status"
1414+)
1515+1616+// This is a compile-time assertion to ensure that this generated file
1717+// is compatible with the grpc package it is being compiled against.
1818+// Requires gRPC-Go v1.64.0 or later.
1919+const _ = grpc.SupportPackageIsVersion9
2020+2121+const (
2222+ GitMirrorService_CommitLog_FullMethodName = "/gitmirror.v1.GitMirrorService/CommitLog"
2323+ GitMirrorService_GetBlob_FullMethodName = "/gitmirror.v1.GitMirrorService/GetBlob"
2424+ GitMirrorService_Diff_FullMethodName = "/gitmirror.v1.GitMirrorService/Diff"
2525+ GitMirrorService_Interdiff_FullMethodName = "/gitmirror.v1.GitMirrorService/Interdiff"
2626+)
2727+2828+// GitMirrorServiceClient is the client API for GitMirrorService service.
2929+//
3030+// 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.
3131+type GitMirrorServiceClient interface {
3232+ CommitLog(ctx context.Context, in *CommitLogRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[CommitLogResponse], error)
3333+ GetBlob(ctx context.Context, in *GetBlobRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[BlobChunk], error)
3434+ Diff(ctx context.Context, in *DiffRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[FileDiff], error)
3535+ Interdiff(ctx context.Context, in *InterdiffRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[FileDiff], error)
3636+}
3737+3838+type gitMirrorServiceClient struct {
3939+ cc grpc.ClientConnInterface
4040+}
4141+4242+func NewGitMirrorServiceClient(cc grpc.ClientConnInterface) GitMirrorServiceClient {
4343+ return &gitMirrorServiceClient{cc}
4444+}
4545+4646+func (c *gitMirrorServiceClient) CommitLog(ctx context.Context, in *CommitLogRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[CommitLogResponse], error) {
4747+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
4848+ stream, err := c.cc.NewStream(ctx, &GitMirrorService_ServiceDesc.Streams[0], GitMirrorService_CommitLog_FullMethodName, cOpts...)
4949+ if err != nil {
5050+ return nil, err
5151+ }
5252+ x := &grpc.GenericClientStream[CommitLogRequest, CommitLogResponse]{ClientStream: stream}
5353+ if err := x.ClientStream.SendMsg(in); err != nil {
5454+ return nil, err
5555+ }
5656+ if err := x.ClientStream.CloseSend(); err != nil {
5757+ return nil, err
5858+ }
5959+ return x, nil
6060+}
6161+6262+// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
6363+type GitMirrorService_CommitLogClient = grpc.ServerStreamingClient[CommitLogResponse]
6464+6565+func (c *gitMirrorServiceClient) GetBlob(ctx context.Context, in *GetBlobRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[BlobChunk], error) {
6666+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
6767+ stream, err := c.cc.NewStream(ctx, &GitMirrorService_ServiceDesc.Streams[1], GitMirrorService_GetBlob_FullMethodName, cOpts...)
6868+ if err != nil {
6969+ return nil, err
7070+ }
7171+ x := &grpc.GenericClientStream[GetBlobRequest, BlobChunk]{ClientStream: stream}
7272+ if err := x.ClientStream.SendMsg(in); err != nil {
7373+ return nil, err
7474+ }
7575+ if err := x.ClientStream.CloseSend(); err != nil {
7676+ return nil, err
7777+ }
7878+ return x, nil
7979+}
8080+8181+// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
8282+type GitMirrorService_GetBlobClient = grpc.ServerStreamingClient[BlobChunk]
8383+8484+func (c *gitMirrorServiceClient) Diff(ctx context.Context, in *DiffRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[FileDiff], error) {
8585+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
8686+ stream, err := c.cc.NewStream(ctx, &GitMirrorService_ServiceDesc.Streams[2], GitMirrorService_Diff_FullMethodName, cOpts...)
8787+ if err != nil {
8888+ return nil, err
8989+ }
9090+ x := &grpc.GenericClientStream[DiffRequest, FileDiff]{ClientStream: stream}
9191+ if err := x.ClientStream.SendMsg(in); err != nil {
9292+ return nil, err
9393+ }
9494+ if err := x.ClientStream.CloseSend(); err != nil {
9595+ return nil, err
9696+ }
9797+ return x, nil
9898+}
9999+100100+// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
101101+type GitMirrorService_DiffClient = grpc.ServerStreamingClient[FileDiff]
102102+103103+func (c *gitMirrorServiceClient) Interdiff(ctx context.Context, in *InterdiffRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[FileDiff], error) {
104104+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
105105+ stream, err := c.cc.NewStream(ctx, &GitMirrorService_ServiceDesc.Streams[3], GitMirrorService_Interdiff_FullMethodName, cOpts...)
106106+ if err != nil {
107107+ return nil, err
108108+ }
109109+ x := &grpc.GenericClientStream[InterdiffRequest, FileDiff]{ClientStream: stream}
110110+ if err := x.ClientStream.SendMsg(in); err != nil {
111111+ return nil, err
112112+ }
113113+ if err := x.ClientStream.CloseSend(); err != nil {
114114+ return nil, err
115115+ }
116116+ return x, nil
117117+}
118118+119119+// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
120120+type GitMirrorService_InterdiffClient = grpc.ServerStreamingClient[FileDiff]
121121+122122+// GitMirrorServiceServer is the server API for GitMirrorService service.
123123+// All implementations must embed UnimplementedGitMirrorServiceServer
124124+// for forward compatibility.
125125+type GitMirrorServiceServer interface {
126126+ CommitLog(*CommitLogRequest, grpc.ServerStreamingServer[CommitLogResponse]) error
127127+ GetBlob(*GetBlobRequest, grpc.ServerStreamingServer[BlobChunk]) error
128128+ Diff(*DiffRequest, grpc.ServerStreamingServer[FileDiff]) error
129129+ Interdiff(*InterdiffRequest, grpc.ServerStreamingServer[FileDiff]) error
130130+ mustEmbedUnimplementedGitMirrorServiceServer()
131131+}
132132+133133+// UnimplementedGitMirrorServiceServer must be embedded to have
134134+// forward compatible implementations.
135135+//
136136+// NOTE: this should be embedded by value instead of pointer to avoid a nil
137137+// pointer dereference when methods are called.
138138+type UnimplementedGitMirrorServiceServer struct{}
139139+140140+func (UnimplementedGitMirrorServiceServer) CommitLog(*CommitLogRequest, grpc.ServerStreamingServer[CommitLogResponse]) error {
141141+ return status.Error(codes.Unimplemented, "method CommitLog not implemented")
142142+}
143143+func (UnimplementedGitMirrorServiceServer) GetBlob(*GetBlobRequest, grpc.ServerStreamingServer[BlobChunk]) error {
144144+ return status.Error(codes.Unimplemented, "method GetBlob not implemented")
145145+}
146146+func (UnimplementedGitMirrorServiceServer) Diff(*DiffRequest, grpc.ServerStreamingServer[FileDiff]) error {
147147+ return status.Error(codes.Unimplemented, "method Diff not implemented")
148148+}
149149+func (UnimplementedGitMirrorServiceServer) Interdiff(*InterdiffRequest, grpc.ServerStreamingServer[FileDiff]) error {
150150+ return status.Error(codes.Unimplemented, "method Interdiff not implemented")
151151+}
152152+func (UnimplementedGitMirrorServiceServer) mustEmbedUnimplementedGitMirrorServiceServer() {}
153153+func (UnimplementedGitMirrorServiceServer) testEmbeddedByValue() {}
154154+155155+// UnsafeGitMirrorServiceServer may be embedded to opt out of forward compatibility for this service.
156156+// Use of this interface is not recommended, as added methods to GitMirrorServiceServer will
157157+// result in compilation errors.
158158+type UnsafeGitMirrorServiceServer interface {
159159+ mustEmbedUnimplementedGitMirrorServiceServer()
160160+}
161161+162162+func RegisterGitMirrorServiceServer(s grpc.ServiceRegistrar, srv GitMirrorServiceServer) {
163163+ // If the following call panics, it indicates UnimplementedGitMirrorServiceServer was
164164+ // embedded by pointer and is nil. This will cause panics if an
165165+ // unimplemented method is ever invoked, so we test this at initialization
166166+ // time to prevent it from happening at runtime later due to I/O.
167167+ if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
168168+ t.testEmbeddedByValue()
169169+ }
170170+ s.RegisterService(&GitMirrorService_ServiceDesc, srv)
171171+}
172172+173173+func _GitMirrorService_CommitLog_Handler(srv interface{}, stream grpc.ServerStream) error {
174174+ m := new(CommitLogRequest)
175175+ if err := stream.RecvMsg(m); err != nil {
176176+ return err
177177+ }
178178+ return srv.(GitMirrorServiceServer).CommitLog(m, &grpc.GenericServerStream[CommitLogRequest, CommitLogResponse]{ServerStream: stream})
179179+}
180180+181181+// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
182182+type GitMirrorService_CommitLogServer = grpc.ServerStreamingServer[CommitLogResponse]
183183+184184+func _GitMirrorService_GetBlob_Handler(srv interface{}, stream grpc.ServerStream) error {
185185+ m := new(GetBlobRequest)
186186+ if err := stream.RecvMsg(m); err != nil {
187187+ return err
188188+ }
189189+ return srv.(GitMirrorServiceServer).GetBlob(m, &grpc.GenericServerStream[GetBlobRequest, BlobChunk]{ServerStream: stream})
190190+}
191191+192192+// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
193193+type GitMirrorService_GetBlobServer = grpc.ServerStreamingServer[BlobChunk]
194194+195195+func _GitMirrorService_Diff_Handler(srv interface{}, stream grpc.ServerStream) error {
196196+ m := new(DiffRequest)
197197+ if err := stream.RecvMsg(m); err != nil {
198198+ return err
199199+ }
200200+ return srv.(GitMirrorServiceServer).Diff(m, &grpc.GenericServerStream[DiffRequest, FileDiff]{ServerStream: stream})
201201+}
202202+203203+// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
204204+type GitMirrorService_DiffServer = grpc.ServerStreamingServer[FileDiff]
205205+206206+func _GitMirrorService_Interdiff_Handler(srv interface{}, stream grpc.ServerStream) error {
207207+ m := new(InterdiffRequest)
208208+ if err := stream.RecvMsg(m); err != nil {
209209+ return err
210210+ }
211211+ return srv.(GitMirrorServiceServer).Interdiff(m, &grpc.GenericServerStream[InterdiffRequest, FileDiff]{ServerStream: stream})
212212+}
213213+214214+// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
215215+type GitMirrorService_InterdiffServer = grpc.ServerStreamingServer[FileDiff]
216216+217217+// GitMirrorService_ServiceDesc is the grpc.ServiceDesc for GitMirrorService service.
218218+// It's only intended for direct use with grpc.RegisterService,
219219+// and not to be introspected or modified (even as a copy)
220220+var GitMirrorService_ServiceDesc = grpc.ServiceDesc{
221221+ ServiceName: "gitmirror.v1.GitMirrorService",
222222+ HandlerType: (*GitMirrorServiceServer)(nil),
223223+ Methods: []grpc.MethodDesc{},
224224+ Streams: []grpc.StreamDesc{
225225+ {
226226+ StreamName: "CommitLog",
227227+ Handler: _GitMirrorService_CommitLog_Handler,
228228+ ServerStreams: true,
229229+ },
230230+ {
231231+ StreamName: "GetBlob",
232232+ Handler: _GitMirrorService_GetBlob_Handler,
233233+ ServerStreams: true,
234234+ },
235235+ {
236236+ StreamName: "Diff",
237237+ Handler: _GitMirrorService_Diff_Handler,
238238+ ServerStreams: true,
239239+ },
240240+ {
241241+ StreamName: "Interdiff",
242242+ Handler: _GitMirrorService_Interdiff_Handler,
243243+ ServerStreams: true,
244244+ },
245245+ },
246246+ Metadata: "gitmirror/v1/gitmirror.proto",
247247+}
+135
gitmirror/proto/gitmirror/v1/gitmirror.proto
···11+syntax = "proto3";
22+33+package gitmirror.v1;
44+55+import "google/protobuf/timestamp.proto";
66+77+option go_package = "tangled.org/core/gitmirror/proto/gen;gitmirrorv1";
88+99+service GitMirrorService {
1010+ rpc CommitLog(CommitLogRequest) returns (stream CommitLogResponse);
1111+ rpc GetBlob(GetBlobRequest) returns (stream BlobChunk);
1212+ rpc Diff(DiffRequest) returns (stream FileDiff);
1313+ rpc Interdiff(InterdiffRequest) returns (stream FileDiff);
1414+}
1515+1616+message CommitLogRequest {
1717+ // repo DID
1818+ string repo = 1;
1919+ // Ranges to include in the git log (revspec, "A..B", "A...B", etc.).
2020+ // At least one range, or all_refs must be specified.
2121+ repeated bytes ranges = 2;
2222+ // If true, all refs are searched for commits.
2323+ // Must not be true when ranges are given.
2424+ bool all_refs = 3;
2525+ // After is an optional parameter to specify the earliest commit to consider.
2626+ google.protobuf.Timestamp after = 4;
2727+ // Before is an optional parameter to specify the latest commit to consider.
2828+ google.protobuf.Timestamp before = 5;
2929+ // MaxCommits is an optional parameter to specify the maximum number of commits
3030+ // to return. If max_commits is 0, all commits that match the criteria will be
3131+ // returned.
3232+ uint32 max_commits = 6;
3333+ // Skip is an optional parameter to specify the number of commits to skip.
3434+ // This can be used to implement a poor mans pagination.
3535+ uint32 skip = 7;
3636+}
3737+message CommitLogResponse {
3838+ repeated GitCommit commits = 1;
3939+}
4040+4141+message GetBlobRequest {
4242+ // repo carries the DID as a string.
4343+ string repo = 1;
4444+ // oid is the blob object id (hex).
4545+ string oid = 2;
4646+}
4747+message BlobChunk {
4848+ bytes data = 1;
4949+}
5050+5151+message DiffRequest {
5252+ // repo carries the DID as a string; parsing/validation is an impl detail.
5353+ string repo = 1;
5454+ // base_rev_spec/head_rev_spec are commit-ish ref strings.
5555+ // base_rev_spec is optional; when unset, diff head against head's first parent.
5656+ optional bytes base_rev_spec = 2;
5757+ bytes head_rev_spec = 3;
5858+ ComparisonType comparison_type = 4;
5959+6060+ enum ComparisonType {
6161+ COMPARISON_TYPE_UNSPECIFIED = 0;
6262+ // Corresponds to the BASE..HEAD syntax that only returns any commits that
6363+ // are in HEAD but not in BASE.
6464+ COMPARISON_TYPE_ONLY_IN_HEAD = 1;
6565+ // Corresponds to the BASE...HEAD syntax that returns any commits that are
6666+ // not in both BASE and HEAD.
6767+ COMPARISON_TYPE_INTERSECTION = 2;
6868+ }
6969+}
7070+7171+message InterdiffRequest {
7272+ // repo carries the DID as a string.
7373+ string repo = 1;
7474+ // Old patch range (from_base..from_head) and new patch range (to_base..to_head).
7575+ bytes from_base = 2;
7676+ bytes from_head = 3;
7777+ bytes to_base = 4;
7878+ bytes to_head = 5;
7979+}
8080+8181+// Diff and Interdiff stream one FileDiff per changed file.
8282+8383+message FileContent {
8484+ string path = 1;
8585+ string oid = 2;
8686+ uint64 size = 3;
8787+ bool is_binary = 4;
8888+ bool is_submodule = 5;
8989+ // Raw file bytes, inlined when the oid is not fetchable by the client
9090+ // (e.g. interdiff)
9191+ optional bytes content = 6;
9292+}
9393+9494+// One aligned line pair; a side is absent (None) when unset.
9595+message LinePair {
9696+ optional uint32 lhs = 1;
9797+ optional uint32 rhs = 2;
9898+}
9999+100100+message Hunk {
101101+ // LineNumber (u32) sets containing novel content per side.
102102+ repeated uint32 novel_lhs = 1;
103103+ repeated uint32 novel_rhs = 2;
104104+ repeated LinePair lines = 3;
105105+}
106106+107107+// Number of bytes per side when the files differ (Rust Option<(usize, usize)>).
108108+message ByteChanges {
109109+ uint64 lhs = 1;
110110+ uint64 rhs = 2;
111111+}
112112+113113+message FileDiff {
114114+ FileContent lhs_src = 1;
115115+ FileContent rhs_src = 2;
116116+ repeated Hunk hunks = 3;
117117+ optional ByteChanges has_byte_changes = 4;
118118+ bool has_syntactic_changes = 5;
119119+ // TODO: lhs_positions & rhs_positions
120120+}
121121+122122+message GitCommit {
123123+ string oid = 1;
124124+ GitSignature author = 2;
125125+ GitSignature committer = 3;
126126+ bytes message = 4;
127127+ repeated string parents = 5;
128128+ map<string, string> extra_headers = 6;
129129+}
130130+131131+message GitSignature {
132132+ bytes name = 1;
133133+ bytes email = 2;
134134+ google.protobuf.Timestamp date = 3;
135135+}
+370
gitmirror/src/diff.rs
···11+use gix::ObjectId;
22+use gix::bstr::ByteSlice as _;
33+use gix::objs::tree::EntryKind;
44+use line_numbers::{LineNumber, SingleLineSpan};
55+use rustc_hash::FxHashSet;
66+use tracing::info;
77+88+/// Blobs larger than this are treated as binary.
99+const LARGE_FILE_THRESHOLD_BYTES: u64 = 1024 * 1024;
1010+1111+#[derive(Debug)]
1212+pub struct FileContent {
1313+ pub(crate) path: String,
1414+ pub(crate) oid: String,
1515+ pub(crate) size: usize,
1616+ pub(crate) is_binary: bool,
1717+ pub(crate) is_submodule: bool,
1818+ /// Raw file bytes, inlined when the `oid` is not fetchable by the client (interdiff's
1919+ /// synthetic rebased tree). `None` for ordinary diffs and for binary/oversized blobs.
2020+ pub(crate) content: Option<Vec<u8>>,
2121+}
2222+2323+impl From<FileContent> for crate::protocol::v1::FileContent {
2424+ fn from(f: FileContent) -> Self {
2525+ Self {
2626+ path: f.path,
2727+ oid: f.oid,
2828+ size: f.size as u64,
2929+ is_binary: f.is_binary,
3030+ is_submodule: f.is_submodule,
3131+ content: f.content,
3232+ }
3333+ }
3434+}
3535+3636+#[derive(Debug, Clone)]
3737+pub struct Hunk {
3838+ pub(crate) novel_lhs: FxHashSet<LineNumber>,
3939+ pub(crate) novel_rhs: FxHashSet<LineNumber>,
4040+ pub(crate) lines: Vec<(Option<LineNumber>, Option<LineNumber>)>,
4141+}
4242+4343+impl From<Hunk> for crate::protocol::v1::Hunk {
4444+ fn from(h: Hunk) -> Self {
4545+ // Sort the novel line sets for deterministic output.
4646+ let sorted = |set: FxHashSet<LineNumber>| -> Vec<u32> {
4747+ let mut v: Vec<u32> = set.into_iter().map(|n| n.0).collect();
4848+ v.sort_unstable();
4949+ v
5050+ };
5151+ Self {
5252+ novel_lhs: sorted(h.novel_lhs),
5353+ novel_rhs: sorted(h.novel_rhs),
5454+ lines: h
5555+ .lines
5656+ .into_iter()
5757+ .map(|(lhs, rhs)| crate::protocol::v1::LinePair {
5858+ lhs: lhs.map(|n| n.0),
5959+ rhs: rhs.map(|n| n.0),
6060+ })
6161+ .collect(),
6262+ }
6363+ }
6464+}
6565+6666+/// A matched token (an atom, a delimiter, or a comment word).
6767+#[derive(PartialEq, Eq, Debug, Clone)]
6868+pub enum MatchKind {
6969+ // TBD
7070+}
7171+7272+#[derive(Debug, Clone, PartialEq, Eq)]
7373+pub struct MatchedPos {
7474+ pub(crate) kind: MatchKind,
7575+ pub(crate) pos: SingleLineSpan,
7676+}
7777+7878+#[derive(Debug)]
7979+pub struct Diff {
8080+ pub(crate) lhs_src: FileContent,
8181+ pub(crate) rhs_src: FileContent,
8282+ pub(crate) hunks: Vec<Hunk>,
8383+8484+ #[allow(unused)]
8585+ pub(crate) lhs_positions: Vec<MatchedPos>,
8686+ #[allow(unused)]
8787+ pub(crate) rhs_positions: Vec<MatchedPos>,
8888+8989+ /// If the two files do not have exactly the same bytes, the
9090+ /// number of bytes in each file.
9191+ pub(crate) has_byte_changes: Option<(usize, usize)>,
9292+ pub(crate) has_syntactic_changes: bool,
9393+}
9494+9595+impl From<Diff> for crate::protocol::v1::FileDiff {
9696+ fn from(d: Diff) -> Self {
9797+ Self {
9898+ lhs_src: Some(d.lhs_src.into()),
9999+ rhs_src: Some(d.rhs_src.into()),
100100+ hunks: d.hunks.into_iter().map(Into::into).collect(),
101101+ has_byte_changes: d.has_byte_changes.map(|(lhs, rhs)| {
102102+ crate::protocol::v1::ByteChanges {
103103+ lhs: lhs as u64,
104104+ rhs: rhs as u64,
105105+ }
106106+ }),
107107+ has_syntactic_changes: d.has_syntactic_changes,
108108+ }
109109+ }
110110+}
111111+112112+/// Diff two trees, yielding one [`Diff`] per changed file (line-level [`Hunk`]s included).
113113+///
114114+/// Returns an iterator of `anyhow::Result<Diff>`: per-file diffing can fail independently, so
115115+/// each item is fallible. The consumer drives it lazily and can stop at any point by dropping
116116+/// the iterator (e.g. when the gRPC client hangs up).
117117+pub fn diff<'repo>(
118118+ repo: &'repo gix::Repository,
119119+ old_tree: &gix::Tree<'_>,
120120+ new_tree: &gix::Tree<'_>,
121121+ embed_content: bool,
122122+) -> anyhow::Result<DiffIter<'repo>> {
123123+ let changes = repo.diff_tree_to_tree(Some(old_tree), Some(new_tree), None)?;
124124+ let mut cache = repo.diff_resource_cache_for_tree_diff()?;
125125+ cache.filter.options.large_file_threshold_bytes = LARGE_FILE_THRESHOLD_BYTES;
126126+ Ok(DiffIter {
127127+ repo,
128128+ changes: changes.into_iter(),
129129+ cache,
130130+ embed_content,
131131+ })
132132+}
133133+134134+/// Lazy iterator over per-file [`Diff`]s. Created by [`diff`].
135135+pub struct DiffIter<'repo> {
136136+ repo: &'repo gix::Repository,
137137+ changes: std::vec::IntoIter<gix::object::tree::diff::ChangeDetached>,
138138+ cache: gix::diff::blob::Platform,
139139+ embed_content: bool,
140140+}
141141+142142+impl Iterator for DiffIter<'_> {
143143+ type Item = anyhow::Result<Diff>;
144144+145145+ fn next(&mut self) -> Option<Self::Item> {
146146+ loop {
147147+ let change = self.changes.next()?;
148148+ let (old, new) = change_sides(&change);
149149+ // Trees are recursed into by gix and never diffed directly; skip defensively.
150150+ if old.is_kind(EntryKind::Tree) || new.is_kind(EntryKind::Tree) {
151151+ continue;
152152+ }
153153+ // Submodules (gitlinks) can't go through the blob pipeline — their commit lives in
154154+ // another repo. Emit the commit oids directly for the caller to compare.
155155+ if old.is_kind(EntryKind::Commit) || new.is_kind(EntryKind::Commit) {
156156+ return Some(Ok(submodule_diff(self.repo, &change, old, new)));
157157+ }
158158+ let result = build_diff(self.repo, &change, &mut self.cache, self.embed_content);
159159+ self.cache.clear_resource_cache_keep_allocation();
160160+ return Some(result);
161161+ }
162162+ }
163163+}
164164+165165+/// One side (old or new) of a change.
166166+enum Side {
167167+ Absent,
168168+ Present { oid: ObjectId, kind: EntryKind },
169169+}
170170+171171+impl Side {
172172+ fn is_kind(&self, wanted: EntryKind) -> bool {
173173+ matches!(self, Side::Present { kind, .. } if *kind == wanted)
174174+ }
175175+}
176176+177177+/// Decompose a change into its `(old, new)` sides regardless of variant.
178178+fn change_sides(change: &gix::object::tree::diff::ChangeDetached) -> (Side, Side) {
179179+ use gix::object::tree::diff::ChangeDetached as C;
180180+ let present = |oid, mode: gix::object::tree::EntryMode| Side::Present {
181181+ oid,
182182+ kind: mode.kind(),
183183+ };
184184+ match change {
185185+ C::Addition { id, entry_mode, .. } => (Side::Absent, present(*id, *entry_mode)),
186186+ C::Deletion { id, entry_mode, .. } => (present(*id, *entry_mode), Side::Absent),
187187+ C::Modification {
188188+ previous_id,
189189+ previous_entry_mode,
190190+ id,
191191+ entry_mode,
192192+ ..
193193+ } => (
194194+ present(*previous_id, *previous_entry_mode),
195195+ present(*id, *entry_mode),
196196+ ),
197197+ C::Rewrite {
198198+ source_id,
199199+ source_entry_mode,
200200+ id,
201201+ entry_mode,
202202+ ..
203203+ } => (
204204+ present(*source_id, *source_entry_mode),
205205+ present(*id, *entry_mode),
206206+ ),
207207+ }
208208+}
209209+210210+/// Build a [`Diff`] for a submodule (gitlink) change: no hunks, both sides carry their commit
211211+/// oid with `is_submodule` set. The caller compares the oids as submodule pointers.
212212+fn submodule_diff(
213213+ repo: &gix::Repository,
214214+ change: &gix::object::tree::diff::ChangeDetached,
215215+ old: Side,
216216+ new: Side,
217217+) -> Diff {
218218+ let path = change.location().to_string();
219219+ let side = |s: Side| FileContent {
220220+ path: path.clone(),
221221+ oid: match s {
222222+ Side::Absent => ObjectId::null(repo.object_hash()).to_string(),
223223+ Side::Present { oid, .. } => oid.to_string(),
224224+ },
225225+ size: 0,
226226+ is_binary: false,
227227+ is_submodule: true,
228228+ content: None,
229229+ };
230230+ Diff {
231231+ lhs_src: side(old),
232232+ rhs_src: side(new),
233233+ hunks: Vec::new(),
234234+ lhs_positions: Vec::new(),
235235+ rhs_positions: Vec::new(),
236236+ has_byte_changes: None,
237237+ has_syntactic_changes: false,
238238+ }
239239+}
240240+241241+/// Build the [`Diff`] for a single changed file.
242242+fn build_diff(
243243+ repo: &gix::Repository,
244244+ change: &gix::object::tree::diff::ChangeDetached,
245245+ cache: &mut gix::diff::blob::Platform,
246246+ embed_content: bool,
247247+) -> anyhow::Result<Diff> {
248248+ use gix::diff::blob::platform::prepare_diff::Operation;
249249+ use gix::diff::blob::platform::resource::Data;
250250+ use gix::diff::blob::{Diff as BlobDiff, InternedInput};
251251+ use gix::prelude::TreeDiffChangeExt as _;
252252+253253+ let change = change.attach(repo, repo);
254254+ let blob = change.diff(cache)?;
255255+ let out = blob.resource_cache.prepare_diff()?;
256256+257257+ // Per-side file metadata, derived from the prepared resources.
258258+ let side =
259259+ |res: &gix::diff::blob::platform::Resource<'_>, embed_content: bool| -> FileContent {
260260+ let (size, is_binary, content) = match res.data {
261261+ // Only textual buffers carry inlinable bytes; binary/missing sides fall back to oid.
262262+ Data::Buffer { buf, .. } => (buf.len(), false, embed_content.then(|| buf.to_vec())),
263263+ Data::Binary { size } => (size as usize, true, None),
264264+ Data::Missing => (0, false, None),
265265+ };
266266+ FileContent {
267267+ path: res.rela_path.to_string(),
268268+ oid: res.id.to_string(),
269269+ size,
270270+ is_binary,
271271+ is_submodule: false,
272272+ content,
273273+ }
274274+ };
275275+ let mut lhs_src = side(&out.old, embed_content);
276276+ let mut rhs_src = side(&out.new, false);
277277+278278+ let has_byte_changes = if lhs_src.size == rhs_src.size && out.old.id == out.new.id {
279279+ None
280280+ } else {
281281+ Some((lhs_src.size, rhs_src.size))
282282+ };
283283+284284+ let hunks = match out.operation {
285285+ Operation::InternalDiff { algorithm } => {
286286+ let input = InternedInput::new(out.old.intern_source(), out.new.intern_source());
287287+ let mut d = BlobDiff::compute(algorithm, &input);
288288+ d.postprocess_lines(&input);
289289+ d.hunks()
290290+ .map(|h| {
291291+ let before: Vec<LineNumber> = h.before.clone().map(LineNumber).collect();
292292+ let after: Vec<LineNumber> = h.after.clone().map(LineNumber).collect();
293293+ let n = before.len().max(after.len());
294294+ let lines = (0..n)
295295+ .map(|i| (before.get(i).copied(), after.get(i).copied()))
296296+ .collect();
297297+ Hunk {
298298+ novel_lhs: before.iter().copied().collect(),
299299+ novel_rhs: after.iter().copied().collect(),
300300+ lines,
301301+ }
302302+ })
303303+ .collect()
304304+ }
305305+ Operation::SourceOrDestinationIsBinary => {
306306+ lhs_src.is_binary = true;
307307+ rhs_src.is_binary = true;
308308+ Vec::new()
309309+ }
310310+ Operation::ExternalCommand { .. } => unreachable!("we disabled that"),
311311+ };
312312+313313+ Ok(Diff {
314314+ lhs_src,
315315+ rhs_src,
316316+ hunks,
317317+ lhs_positions: Vec::new(),
318318+ rhs_positions: Vec::new(),
319319+ has_byte_changes,
320320+ has_syntactic_changes: false,
321321+ })
322322+}
323323+324324+/// Compute the two trees whose diff is the interdiff between an old patch
325325+/// version (`from_base..from_head`) and a new one (`to_base..to_head`).
326326+///
327327+/// Rebase `from_base..from_head` onto `to_base` and return rebased tree so
328328+/// caller can diff between `rebased_tree` and `to_head.tree`
329329+pub fn prepare_interdiff<'repo>(
330330+ repo: &'repo gix::Repository,
331331+ (from_base_id, from_head_id): (ObjectId, ObjectId),
332332+ to_base_id: ObjectId,
333333+) -> anyhow::Result<gix::Tree<'repo>> {
334334+ let from_head = repo.find_commit(from_head_id)?;
335335+336336+ let from_base = repo.find_commit(from_base_id)?;
337337+ let to_base = repo.find_commit(to_base_id)?;
338338+339339+ // Endpoint trees for the 3-way merge.
340340+ let from_base_tree = from_base.tree_id()?.detach();
341341+ let from_head_tree = from_head.tree_id()?.detach();
342342+ let to_base_tree = to_base.tree_id()?.detach();
343343+344344+ // Rebase = replay (from_base_tree -> from_head_tree) onto to_base_tree:
345345+ // ancestor = from_base_tree, ours = to_base_tree, theirs = from_head_tree.
346346+ let options = repo.tree_merge_options()?;
347347+ let labels = gix::merge::blob::builtin_driver::text::Labels {
348348+ ancestor: Some("from.base".as_bytes().as_bstr()),
349349+ current: Some("to.base".as_bytes().as_bstr()),
350350+ other: Some("from.head".as_bytes().as_bstr()),
351351+ };
352352+ let mut outcome = repo.merge_trees(
353353+ from_base_tree,
354354+ to_base_tree,
355355+ from_head_tree,
356356+ labels,
357357+ options,
358358+ )?;
359359+360360+ if !outcome.conflicts.is_empty() {
361361+ info!(
362362+ conflicts = outcome.conflicts.len(),
363363+ "interdiff rebase produced conflicts; tree contains conflict markers"
364364+ );
365365+ }
366366+367367+ let rebased_id = outcome.tree.write()?.detach();
368368+ let rebased_tree = repo.find_tree(rebased_id)?;
369369+ Ok(rebased_tree)
370370+}
+45
gitmirror/src/main.rs
···11+use std::net::SocketAddr;
22+use std::path::PathBuf;
33+44+use clap::{Parser, Subcommand};
55+66+mod diff;
77+mod protocol;
88+mod service;
99+1010+#[derive(Parser)]
1111+#[command(name = "gitmirror", about = "Git mirror gRPC service")]
1212+struct Cli {
1313+ #[command(subcommand)]
1414+ cmd: Command,
1515+}
1616+1717+#[derive(Subcommand)]
1818+enum Command {
1919+ /// Run the gRPC server.
2020+ Serve(ServeArgs),
2121+}
2222+2323+#[derive(clap::Args)]
2424+struct ServeArgs {
2525+ /// Address to bind the gRPC server to.
2626+ #[arg(long, env = "GITMIRROR_ADDR", default_value = "127.0.0.1:9000")]
2727+ addr: SocketAddr,
2828+2929+ /// Base directory holding bare mirror repos, one per DID (<base>/<did>).
3030+ #[arg(long, env = "GITMIRROR_REPO_BASE", default_value = "repos")]
3131+ repo_base: PathBuf,
3232+}
3333+3434+#[tokio::main]
3535+async fn main() -> anyhow::Result<()> {
3636+ tracing_subscriber::fmt()
3737+ .with_env_filter(
3838+ tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| "info".into()),
3939+ )
4040+ .init();
4141+4242+ match Cli::parse().cmd {
4343+ Command::Serve(args) => service::serve(args.addr, args.repo_base).await,
4444+ }
4545+}
+3
gitmirror/src/protocol.rs
···11+pub mod v1 {
22+ tonic::include_proto!("gitmirror.v1");
33+}
+362
gitmirror/src/service.rs
···11+use std::net::SocketAddr;
22+use std::path::PathBuf;
33+use std::pin::Pin;
44+55+use gix::bstr::ByteSlice as _;
66+use gix::revision::plumbing::Spec as RevSpec;
77+use gix::revision::walk::Sorting;
88+use tokio::sync::mpsc;
99+use tokio_stream::Stream;
1010+use tokio_stream::wrappers::ReceiverStream;
1111+use tonic::transport::Server;
1212+use tonic::{Request, Response, Status};
1313+use tracing::{debug, info};
1414+1515+use crate::diff::{self, prepare_interdiff};
1616+use crate::protocol::v1::diff_request::ComparisonType;
1717+use crate::protocol::v1::git_mirror_service_server::{GitMirrorService, GitMirrorServiceServer};
1818+use crate::protocol::v1::{
1919+ BlobChunk, CommitLogRequest, CommitLogResponse, DiffRequest, FileDiff, GetBlobRequest,
2020+ GitCommit, GitSignature, InterdiffRequest,
2121+};
2222+2323+type FileDiffStream = Pin<Box<dyn Stream<Item = Result<FileDiff, Status>> + Send>>;
2424+type BlobChunkStream = Pin<Box<dyn Stream<Item = Result<BlobChunk, Status>> + Send>>;
2525+type CommitLogResponseStream =
2626+ Pin<Box<dyn Stream<Item = Result<CommitLogResponse, Status>> + Send>>;
2727+2828+/// Blob bytes are streamed in chunks of this size.
2929+const BLOB_CHUNK_SIZE: usize = 64 * 1024;
3030+/// GitCommits are streamed in batches of this size.
3131+const COMMIT_LOG_BATCH_SIZE: usize = 128;
3232+3333+struct GitMirror {
3434+ repo_base: PathBuf,
3535+}
3636+3737+impl GitMirror {
3838+ fn open_repo(&self, did: &str) -> Result<gix::Repository, Status> {
3939+ if !did.starts_with("did:") {
4040+ return Err(Status::invalid_argument("repo must be a DID"));
4141+ }
4242+ gix::open(self.repo_base.join(did))
4343+ .map_err(|e| Status::not_found(format!("repo not found: {e}")))
4444+ }
4545+}
4646+4747+#[tonic::async_trait]
4848+impl GitMirrorService for GitMirror {
4949+ type DiffStream = FileDiffStream;
5050+5151+ async fn diff(
5252+ &self,
5353+ request: Request<DiffRequest>,
5454+ ) -> Result<Response<Self::DiffStream>, Status> {
5555+ let req = request.into_inner();
5656+ let repo = self.open_repo(&req.repo)?;
5757+ // Resolve refs up-front so bad input surfaces as the RPC status, then move the
5858+ // owned repo + oids into a blocking task for the (synchronous) diff loop.
5959+ let base_oid = req
6060+ .base_rev_spec
6161+ .as_deref()
6262+ .map(|b| resolve_commit_oid(&repo, b))
6363+ .transpose()?;
6464+ let head_oid = resolve_commit_oid(&repo, &req.head_rev_spec)?;
6565+ let comparison_type =
6666+ ComparisonType::try_from(req.comparison_type).unwrap_or(ComparisonType::Unspecified);
6767+ let safe = repo.into_sync();
6868+6969+ let (tx, rx) = mpsc::channel::<Result<FileDiff, Status>>(16);
7070+ tokio::task::spawn_blocking(move || {
7171+ let repo = safe.to_thread_local();
7272+7373+ let run = || -> anyhow::Result<()> {
7474+ let head_commit = repo.find_commit(head_oid)?;
7575+ let old = match base_oid {
7676+ Some(base_oid) => match comparison_type {
7777+ ComparisonType::OnlyInHead => {
7878+ repo.find_tree(repo.find_commit(base_oid)?.tree_id()?)?
7979+ }
8080+ ComparisonType::Unspecified | ComparisonType::Intersection => {
8181+ let merge_base = repo.merge_base(base_oid, head_oid)?.detach();
8282+ repo.find_tree(repo.find_commit(merge_base)?.tree_id()?)?
8383+ }
8484+ },
8585+ None => match head_commit.parent_ids().next() {
8686+ Some(parent_id) => {
8787+ repo.find_tree(repo.find_commit(parent_id.detach())?.tree_id()?)?
8888+ }
8989+ None => repo.empty_tree(),
9090+ },
9191+ };
9292+ let new = repo.find_tree(head_commit.tree_id()?)?;
9393+ for item in diff::diff(&repo, &old, &new, false)? {
9494+ if tx.blocking_send(Ok(item?.into())).is_err() {
9595+ break; // client hung up
9696+ }
9797+ }
9898+ Ok(())
9999+ };
100100+ if let Err(e) = run() {
101101+ let _ = tx.blocking_send(Err(Status::internal(e.to_string())));
102102+ }
103103+ });
104104+105105+ Ok(Response::new(Box::pin(ReceiverStream::new(rx))))
106106+ }
107107+108108+ type InterdiffStream = FileDiffStream;
109109+110110+ async fn interdiff(
111111+ &self,
112112+ request: Request<InterdiffRequest>,
113113+ ) -> Result<Response<Self::InterdiffStream>, Status> {
114114+ let req = request.into_inner();
115115+ let repo = self.open_repo(&req.repo)?;
116116+ let from_base_id = resolve_commit_oid(&repo, &req.from_base)?;
117117+ let from_head_id = resolve_commit_oid(&repo, &req.from_head)?;
118118+ let to_base_id = resolve_commit_oid(&repo, &req.to_base)?;
119119+ let to_head_id = resolve_commit_oid(&repo, &req.to_head)?;
120120+ let safe = repo.into_sync();
121121+122122+ let (tx, rx) = mpsc::channel::<Result<FileDiff, Status>>(16);
123123+ tokio::task::spawn_blocking(move || {
124124+ let repo = safe.to_thread_local();
125125+ let run = || -> anyhow::Result<()> {
126126+ let to_head = repo.find_commit(to_head_id)?;
127127+ let to_head_tree = to_head.tree()?;
128128+ let rebased_tree =
129129+ prepare_interdiff(&repo, (from_base_id, from_head_id), to_base_id)?;
130130+ for item in diff::diff(&repo, &rebased_tree, &to_head_tree, true)? {
131131+ if tx.blocking_send(Ok(item?.into())).is_err() {
132132+ break; // client hung up
133133+ }
134134+ }
135135+ Ok(())
136136+ };
137137+ if let Err(e) = run() {
138138+ let _ = tx.blocking_send(Err(Status::internal(e.to_string())));
139139+ }
140140+ });
141141+142142+ Ok(Response::new(Box::pin(ReceiverStream::new(rx))))
143143+ }
144144+145145+ type GetBlobStream = BlobChunkStream;
146146+147147+ async fn get_blob(
148148+ &self,
149149+ request: Request<GetBlobRequest>,
150150+ ) -> Result<Response<Self::GetBlobStream>, Status> {
151151+ let req = request.into_inner();
152152+ let repo = self.open_repo(&req.repo)?;
153153+ let oid = gix::ObjectId::from_hex(req.oid.as_bytes())
154154+ .map_err(|e| Status::invalid_argument(format!("bad oid '{}': {e}", req.oid)))?;
155155+ let safe = repo.into_sync();
156156+157157+ let (tx, rx) = mpsc::channel::<Result<BlobChunk, Status>>(16);
158158+ tokio::task::spawn_blocking(move || {
159159+ let repo = safe.to_thread_local();
160160+ let run = || -> anyhow::Result<()> {
161161+ let blob = repo.find_object(oid)?.try_into_blob()?;
162162+ for chunk in blob.data.chunks(BLOB_CHUNK_SIZE) {
163163+ let msg = BlobChunk {
164164+ data: chunk.to_vec(),
165165+ };
166166+ if tx.blocking_send(Ok(msg)).is_err() {
167167+ break; // client hung up
168168+ }
169169+ }
170170+ Ok(())
171171+ };
172172+ if let Err(e) = run() {
173173+ let _ = tx.blocking_send(Err(Status::internal(e.to_string())));
174174+ }
175175+ });
176176+177177+ Ok(Response::new(Box::pin(ReceiverStream::new(rx))))
178178+ }
179179+180180+ type CommitLogStream = CommitLogResponseStream;
181181+182182+ async fn commit_log(
183183+ &self,
184184+ request: Request<CommitLogRequest>,
185185+ ) -> Result<Response<Self::CommitLogStream>, Status> {
186186+ let req = request.into_inner();
187187+ debug!(
188188+ repo = %req.repo,
189189+ all_refs = req.all_refs,
190190+ ranges = ?req.ranges,
191191+ after = ?req.after,
192192+ before = ?req.before,
193193+ max_commits = req.max_commits,
194194+ skip = req.skip,
195195+ "commit_log request",
196196+ );
197197+ if req.all_refs != req.ranges.is_empty() {
198198+ return Err(Status::invalid_argument(
199199+ "specify exactly one of `ranges` or `all_refs`",
200200+ ));
201201+ }
202202+ let repo = self.open_repo(&req.repo)?;
203203+ let safe = repo.into_sync();
204204+205205+ let (tx, rx) = mpsc::channel::<Result<CommitLogResponse, Status>>(16);
206206+ tokio::task::spawn_blocking(move || {
207207+ let repo = safe.to_thread_local();
208208+ let run = || -> anyhow::Result<()> {
209209+ let after = req.after.map(|t| t.seconds);
210210+ let before = req.before.map(|t| t.seconds);
211211+ let max_commits = req.max_commits as usize;
212212+ let mut skip = req.skip as usize;
213213+ let mut sent = 0usize;
214214+ let mut batch = Vec::with_capacity(COMMIT_LOG_BATCH_SIZE);
215215+216216+ for info in commit_log_walk(&repo, &req)? {
217217+ let info = info?;
218218+ let commit_time = info.commit_time();
219219+ if before.is_some_and(|b| commit_time > b) {
220220+ continue;
221221+ }
222222+ if after.is_some_and(|a| commit_time < a) {
223223+ break; // newest-first order: nothing older will match either
224224+ }
225225+ if skip > 0 {
226226+ skip -= 1;
227227+ continue;
228228+ }
229229+ if max_commits != 0 && sent >= max_commits {
230230+ break;
231231+ }
232232+ batch.push(GitCommit::try_from(&info.object()?)?);
233233+ sent += 1;
234234+ if batch.len() == COMMIT_LOG_BATCH_SIZE {
235235+ let msg = CommitLogResponse {
236236+ commits: std::mem::take(&mut batch),
237237+ };
238238+ if tx.blocking_send(Ok(msg)).is_err() {
239239+ return Ok(()); // client hung up
240240+ }
241241+ }
242242+ }
243243+ if !batch.is_empty() {
244244+ let _ = tx.blocking_send(Ok(CommitLogResponse { commits: batch }));
245245+ }
246246+ Ok(())
247247+ };
248248+ if let Err(e) = run() {
249249+ let _ = tx.blocking_send(Err(Status::internal(e.to_string())));
250250+ }
251251+ });
252252+253253+ Ok(Response::new(Box::pin(ReceiverStream::new(rx))))
254254+ }
255255+}
256256+257257+/// Resolve a commit-ish ref string (oid, short oid, branch/tag) to its commit oid.
258258+fn resolve_commit_oid(repo: &gix::Repository, spec: &[u8]) -> Result<gix::ObjectId, Status> {
259259+ let commit = repo
260260+ .rev_parse_single(spec.as_bstr())
261261+ .map_err(|e| {
262262+ Status::invalid_argument(format!("bad rev '{}': {e}", String::from_utf8_lossy(spec)))
263263+ })?
264264+ .object()
265265+ .map_err(|e| Status::internal(e.to_string()))?
266266+ .peel_to_commit()
267267+ .map_err(|e| Status::internal(e.to_string()))?;
268268+ Ok(commit.id)
269269+}
270270+271271+/// Resolve tips/hidden commits from a [`CommitLogRequest`] and return the resulting gix walk,
272272+/// sorted newest-first to match `git log`'s default order.
273273+fn commit_log_walk<'repo>(
274274+ repo: &'repo gix::Repository,
275275+ req: &CommitLogRequest,
276276+) -> anyhow::Result<gix::revision::Walk<'repo>> {
277277+ let mut tips = Vec::new();
278278+ let mut hidden = Vec::new();
279279+280280+ if req.all_refs {
281281+ for r in repo.references()?.all()? {
282282+ let mut r = r.map_err(|e| anyhow::anyhow!(e))?;
283283+ if let Ok(commit) = r.peel_to_commit() {
284284+ tips.push(commit.id);
285285+ }
286286+ }
287287+ } else {
288288+ for range in &req.ranges {
289289+ let revspec = repo.rev_parse(range.as_bstr())?;
290290+ let spec = revspec.detach();
291291+ match spec {
292292+ RevSpec::Include(id) => tips.push(id),
293293+ RevSpec::Range { from, to } => {
294294+ tips.push(to);
295295+ hidden.push(from);
296296+ }
297297+ _ => {
298298+ anyhow::bail!("The spec isn't currently supported: {spec:?}")
299299+ }
300300+ }
301301+ }
302302+ }
303303+304304+ Ok(repo
305305+ .rev_walk(tips)
306306+ .sorting(Sorting::ByCommitTime(Default::default()))
307307+ .with_hidden(hidden)
308308+ .all()?)
309309+}
310310+311311+impl TryFrom<&gix::Commit<'_>> for GitCommit {
312312+ type Error = anyhow::Error;
313313+314314+ fn try_from(commit: &gix::Commit<'_>) -> anyhow::Result<Self> {
315315+ let decoded = commit.decode()?;
316316+ Ok(GitCommit {
317317+ oid: commit.id.to_string(),
318318+ author: Some(decoded.author()?.try_into()?),
319319+ committer: Some(decoded.committer()?.try_into()?),
320320+ message: decoded.message.to_vec(),
321321+ parents: decoded
322322+ .parents
323323+ .into_iter()
324324+ .map(|id| id.to_string())
325325+ .collect(),
326326+ extra_headers: decoded
327327+ .extra_headers
328328+ .iter()
329329+ .map(|(k, v)| (k.to_string(), v.to_string()))
330330+ .collect(),
331331+ })
332332+ }
333333+}
334334+335335+impl TryFrom<gix::actor::SignatureRef<'_>> for GitSignature {
336336+ type Error = anyhow::Error;
337337+338338+ fn try_from(sig: gix::actor::SignatureRef<'_>) -> anyhow::Result<Self> {
339339+ let time = sig.time()?;
340340+ Ok(GitSignature {
341341+ name: sig.name.to_vec(),
342342+ email: sig.email.to_vec(),
343343+ date: Some(prost_types::Timestamp {
344344+ seconds: time.seconds,
345345+ nanos: 0,
346346+ }),
347347+ })
348348+ }
349349+}
350350+351351+/// Build the gRPC service and serve it until the process is stopped.
352352+pub async fn serve(addr: SocketAddr, repo_base: PathBuf) -> anyhow::Result<()> {
353353+ let service = GitMirror { repo_base };
354354+ info!(addr = %addr, "gitmirror gRPC server listening");
355355+356356+ Server::builder()
357357+ .add_service(GitMirrorServiceServer::new(service))
358358+ .serve(addr)
359359+ .await?;
360360+361361+ Ok(())
362362+}
···11+# Development only. Not for production use.
22+33+FROM rust:1.96-bookworm AS build
44+55+RUN apt-get update && apt-get install -y --no-install-recommends protobuf-compiler libprotobuf-dev \
66+ && rm -rf /var/lib/apt/lists/*
77+88+WORKDIR /src
99+COPY . .
1010+1111+# Cache mounts on the cargo registry + target dir. The binary is copied out within the same
1212+# RUN because the target cache mount is unmounted afterwards (a later COPY --from could not
1313+# see it).
1414+RUN --mount=type=cache,target=/usr/local/cargo/registry \
1515+ --mount=type=cache,target=/src/target \
1616+ cargo build --release -p gitmirror \
1717+ && cp /src/target/release/gitmirror /usr/local/bin/gitmirror
1818+1919+FROM debian:bookworm-slim
2020+2121+RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates tini \
2222+ && rm -rf /var/lib/apt/lists/*
2323+2424+COPY --from=build /usr/local/bin/gitmirror /usr/local/bin/gitmirror
2525+2626+EXPOSE 9000
2727+2828+ENTRYPOINT ["/usr/bin/tini", "--"]
2929+CMD ["sh", "-c", "if [ -f /usr/local/share/ca-certificates/caddy.crt ]; then update-ca-certificates; fi && exec /usr/local/bin/gitmirror serve"]