detached 4bb6db7

feat(mr): store auto-merge state and allow_auto_merge setting

commit
4bb6db7919afdadd47a988c5ec165937b4bf960c
author
brice · · 2026-10-20 15:44 UTC
parent
3ed7469
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
8 files · +319 -37
M gen/ermite/storage/merge_request.pb.go +163 -35
@@ -352,9 +352,10 @@ type RepoSettings struct {
352352 // choice of the owner. When false (settings stored before), exactly
353353 // {fast-forward, squash} was the old default and gains MERGE_MODE_MERGE on
354354 // read.
355- MergeModesV2 bool `protobuf:"varint,4,opt,name=merge_modes_v2,json=mergeModesV2,proto3" json:"merge_modes_v2,omitempty"`
356- unknownFields protoimpl.UnknownFields
357- sizeCache protoimpl.SizeCache
355+ MergeModesV2 bool `protobuf:"varint,4,opt,name=merge_modes_v2,json=mergeModesV2,proto3" json:"merge_modes_v2,omitempty"`
356+ AllowAutoMerge bool `protobuf:"varint,5,opt,name=allow_auto_merge,json=allowAutoMerge,proto3" json:"allow_auto_merge,omitempty"`
357+ unknownFields protoimpl.UnknownFields
358+ sizeCache protoimpl.SizeCache
358359 }
359360
360361 func (x *RepoSettings) Reset() {
@@ -415,6 +416,13 @@ func (x *RepoSettings) GetMergeModesV2() bool {
415416 return false
416417 }
417418
419+func (x *RepoSettings) GetAllowAutoMerge() bool {
420+ if x != nil {
421+ return x.AllowAutoMerge
422+ }
423+ return false
424+}
425+
418426 // MergeRequest is stored under the key "mr/<repo>/<number:010d>".
419427 type MergeRequest struct {
420428 state protoimpl.MessageState `protogen:"open.v1"`
@@ -437,7 +445,10 @@ type MergeRequest struct {
437445 MergedCommitId string `protobuf:"bytes,12,opt,name=merged_commit_id,json=mergedCommitId,proto3" json:"merged_commit_id,omitempty"`
438446 MergeMode MergeMode `protobuf:"varint,13,opt,name=merge_mode,json=mergeMode,proto3,enum=ermite.storage.MergeMode" json:"merge_mode,omitempty"`
439447 // Last comment id handed out for this request.
440- CommentSeq int64 `protobuf:"varint,14,opt,name=comment_seq,json=commentSeq,proto3" json:"comment_seq,omitempty"`
448+ CommentSeq int64 `protobuf:"varint,14,opt,name=comment_seq,json=commentSeq,proto3" json:"comment_seq,omitempty"`
449+ // Pending auto-merge; unset when off.
450+ AutoMerge *AutoMerge `protobuf:"bytes,15,opt,name=auto_merge,json=autoMerge,proto3" json:"auto_merge,omitempty"`
451+ AutoMerged bool `protobuf:"varint,16,opt,name=auto_merged,json=autoMerged,proto3" json:"auto_merged,omitempty"`
441452 unknownFields protoimpl.UnknownFields
442453 sizeCache protoimpl.SizeCache
443454 }
@@ -570,6 +581,106 @@ func (x *MergeRequest) GetCommentSeq() int64 {
570581 return 0
571582 }
572583
584+func (x *MergeRequest) GetAutoMerge() *AutoMerge {
585+ if x != nil {
586+ return x.AutoMerge
587+ }
588+ return nil
589+}
590+
591+func (x *MergeRequest) GetAutoMerged() bool {
592+ if x != nil {
593+ return x.AutoMerged
594+ }
595+ return false
596+}
597+
598+// AutoMerge is the pending auto-merge of an open merge request.
599+type AutoMerge struct {
600+ state protoimpl.MessageState `protogen:"open.v1"`
601+ Mode MergeMode `protobuf:"varint,1,opt,name=mode,proto3,enum=ermite.storage.MergeMode" json:"mode,omitempty"`
602+ HeadId string `protobuf:"bytes,2,opt,name=head_id,json=headId,proto3" json:"head_id,omitempty"`
603+ // Kind and token id of the CIPusher that enabled it.
604+ EnablerKind string `protobuf:"bytes,3,opt,name=enabler_kind,json=enablerKind,proto3" json:"enabler_kind,omitempty"`
605+ EnablerTokenId string `protobuf:"bytes,4,opt,name=enabler_token_id,json=enablerTokenId,proto3" json:"enabler_token_id,omitempty"`
606+ EnablerLabel string `protobuf:"bytes,5,opt,name=enabler_label,json=enablerLabel,proto3" json:"enabler_label,omitempty"`
607+ EnabledAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=enabled_at,json=enabledAt,proto3" json:"enabled_at,omitempty"`
608+ unknownFields protoimpl.UnknownFields
609+ sizeCache protoimpl.SizeCache
610+}
611+
612+func (x *AutoMerge) Reset() {
613+ *x = AutoMerge{}
614+ mi := &file_ermite_storage_merge_request_proto_msgTypes[2]
615+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
616+ ms.StoreMessageInfo(mi)
617+}
618+
619+func (x *AutoMerge) String() string {
620+ return protoimpl.X.MessageStringOf(x)
621+}
622+
623+func (*AutoMerge) ProtoMessage() {}
624+
625+func (x *AutoMerge) ProtoReflect() protoreflect.Message {
626+ mi := &file_ermite_storage_merge_request_proto_msgTypes[2]
627+ if x != nil {
628+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
629+ if ms.LoadMessageInfo() == nil {
630+ ms.StoreMessageInfo(mi)
631+ }
632+ return ms
633+ }
634+ return mi.MessageOf(x)
635+}
636+
637+// Deprecated: Use AutoMerge.ProtoReflect.Descriptor instead.
638+func (*AutoMerge) Descriptor() ([]byte, []int) {
639+ return file_ermite_storage_merge_request_proto_rawDescGZIP(), []int{2}
640+}
641+
642+func (x *AutoMerge) GetMode() MergeMode {
643+ if x != nil {
644+ return x.Mode
645+ }
646+ return MergeMode_MERGE_MODE_UNSPECIFIED
647+}
648+
649+func (x *AutoMerge) GetHeadId() string {
650+ if x != nil {
651+ return x.HeadId
652+ }
653+ return ""
654+}
655+
656+func (x *AutoMerge) GetEnablerKind() string {
657+ if x != nil {
658+ return x.EnablerKind
659+ }
660+ return ""
661+}
662+
663+func (x *AutoMerge) GetEnablerTokenId() string {
664+ if x != nil {
665+ return x.EnablerTokenId
666+ }
667+ return ""
668+}
669+
670+func (x *AutoMerge) GetEnablerLabel() string {
671+ if x != nil {
672+ return x.EnablerLabel
673+ }
674+ return ""
675+}
676+
677+func (x *AutoMerge) GetEnabledAt() *timestamppb.Timestamp {
678+ if x != nil {
679+ return x.EnabledAt
680+ }
681+ return nil
682+}
683+
573684 // Comment is stored under the key "mrc/<repo>/<number:010d>/<id:010d>".
574685 type Comment struct {
575686 state protoimpl.MessageState `protogen:"open.v1"`
@@ -593,7 +704,7 @@ type Comment struct {
593704
594705 func (x *Comment) Reset() {
595706 *x = Comment{}
596- mi := &file_ermite_storage_merge_request_proto_msgTypes[2]
707+ mi := &file_ermite_storage_merge_request_proto_msgTypes[3]
597708 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
598709 ms.StoreMessageInfo(mi)
599710 }
@@ -605,7 +716,7 @@ func (x *Comment) String() string {
605716 func (*Comment) ProtoMessage() {}
606717
607718 func (x *Comment) ProtoReflect() protoreflect.Message {
608- mi := &file_ermite_storage_merge_request_proto_msgTypes[2]
719+ mi := &file_ermite_storage_merge_request_proto_msgTypes[3]
609720 if x != nil {
610721 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
611722 if ms.LoadMessageInfo() == nil {
@@ -618,7 +729,7 @@ func (x *Comment) ProtoReflect() protoreflect.Message {
618729
619730 // Deprecated: Use Comment.ProtoReflect.Descriptor instead.
620731 func (*Comment) Descriptor() ([]byte, []int) {
621- return file_ermite_storage_merge_request_proto_rawDescGZIP(), []int{2}
732+ return file_ermite_storage_merge_request_proto_rawDescGZIP(), []int{3}
622733 }
623734
624735 func (x *Comment) GetId() int64 {
@@ -730,7 +841,7 @@ type Sequence struct {
730841
731842 func (x *Sequence) Reset() {
732843 *x = Sequence{}
733- mi := &file_ermite_storage_merge_request_proto_msgTypes[3]
844+ mi := &file_ermite_storage_merge_request_proto_msgTypes[4]
734845 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
735846 ms.StoreMessageInfo(mi)
736847 }
@@ -742,7 +853,7 @@ func (x *Sequence) String() string {
742853 func (*Sequence) ProtoMessage() {}
743854
744855 func (x *Sequence) ProtoReflect() protoreflect.Message {
745- mi := &file_ermite_storage_merge_request_proto_msgTypes[3]
856+ mi := &file_ermite_storage_merge_request_proto_msgTypes[4]
746857 if x != nil {
747858 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
748859 if ms.LoadMessageInfo() == nil {
@@ -755,7 +866,7 @@ func (x *Sequence) ProtoReflect() protoreflect.Message {
755866
756867 // Deprecated: Use Sequence.ProtoReflect.Descriptor instead.
757868 func (*Sequence) Descriptor() ([]byte, []int) {
758- return file_ermite_storage_merge_request_proto_rawDescGZIP(), []int{3}
869+ return file_ermite_storage_merge_request_proto_rawDescGZIP(), []int{4}
759870 }
760871
761872 func (x *Sequence) GetLast() int64 {
@@ -783,7 +894,7 @@ type Mergeability struct {
783894
784895 func (x *Mergeability) Reset() {
785896 *x = Mergeability{}
786- mi := &file_ermite_storage_merge_request_proto_msgTypes[4]
897+ mi := &file_ermite_storage_merge_request_proto_msgTypes[5]
787898 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
788899 ms.StoreMessageInfo(mi)
789900 }
@@ -795,7 +906,7 @@ func (x *Mergeability) String() string {
795906 func (*Mergeability) ProtoMessage() {}
796907
797908 func (x *Mergeability) ProtoReflect() protoreflect.Message {
798- mi := &file_ermite_storage_merge_request_proto_msgTypes[4]
909+ mi := &file_ermite_storage_merge_request_proto_msgTypes[5]
799910 if x != nil {
800911 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
801912 if ms.LoadMessageInfo() == nil {
@@ -808,7 +919,7 @@ func (x *Mergeability) ProtoReflect() protoreflect.Message {
808919
809920 // Deprecated: Use Mergeability.ProtoReflect.Descriptor instead.
810921 func (*Mergeability) Descriptor() ([]byte, []int) {
811- return file_ermite_storage_merge_request_proto_rawDescGZIP(), []int{4}
922+ return file_ermite_storage_merge_request_proto_rawDescGZIP(), []int{5}
812923 }
813924
814925 func (x *Mergeability) GetState() MergeabilityState {
@@ -871,12 +982,13 @@ var File_ermite_storage_merge_request_proto protoreflect.FileDescriptor
871982
872983 const file_ermite_storage_merge_request_proto_rawDesc = "" +
873984 "\n" +
874- "\"ermite/storage/merge_request.proto\x12\x0eermite.storage\x1a\x1fgoogle/protobuf/timestamp.proto\"\xe5\x01\n" +
985+ "\"ermite/storage/merge_request.proto\x12\x0eermite.storage\x1a\x1fgoogle/protobuf/timestamp.proto\"\x8f\x02\n" +
875986 "\fRepoSettings\x12I\n" +
876987 "\x13allowed_merge_modes\x18\x01 \x03(\x0e2\x19.ermite.storage.MergeModeR\x11allowedMergeModes\x120\n" +
877988 "\x14failing_checks_block\x18\x02 \x01(\bR\x12failingChecksBlock\x122\n" +
878989 "\x06issues\x18\x03 \x01(\x0e2\x1a.ermite.storage.IssuesModeR\x06issues\x12$\n" +
879- "\x0emerge_modes_v2\x18\x04 \x01(\bR\fmergeModesV2\"\xa6\x04\n" +
990+ "\x0emerge_modes_v2\x18\x04 \x01(\bR\fmergeModesV2\x12(\n" +
991+ "\x10allow_auto_merge\x18\x05 \x01(\bR\x0eallowAutoMerge\"\x81\x05\n" +
880992 "\fMergeRequest\x12\x16\n" +
881993 "\x06number\x18\x01 \x01(\x03R\x06number\x12\x14\n" +
882994 "\x05title\x18\x02 \x01(\tR\x05title\x12 \n" +
@@ -896,7 +1008,19 @@ const file_ermite_storage_merge_request_proto_rawDesc = "" +
8961008 "\n" +
8971009 "merge_mode\x18\r \x01(\x0e2\x19.ermite.storage.MergeModeR\tmergeMode\x12\x1f\n" +
8981010 "\vcomment_seq\x18\x0e \x01(\x03R\n" +
899- "commentSeq\"\xcf\x03\n" +
1011+ "commentSeq\x128\n" +
1012+ "\n" +
1013+ "auto_merge\x18\x0f \x01(\v2\x19.ermite.storage.AutoMergeR\tautoMerge\x12\x1f\n" +
1014+ "\vauto_merged\x18\x10 \x01(\bR\n" +
1015+ "autoMerged\"\x80\x02\n" +
1016+ "\tAutoMerge\x12-\n" +
1017+ "\x04mode\x18\x01 \x01(\x0e2\x19.ermite.storage.MergeModeR\x04mode\x12\x17\n" +
1018+ "\ahead_id\x18\x02 \x01(\tR\x06headId\x12!\n" +
1019+ "\fenabler_kind\x18\x03 \x01(\tR\venablerKind\x12(\n" +
1020+ "\x10enabler_token_id\x18\x04 \x01(\tR\x0eenablerTokenId\x12#\n" +
1021+ "\renabler_label\x18\x05 \x01(\tR\fenablerLabel\x129\n" +
1022+ "\n" +
1023+ "enabled_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\tenabledAt\"\xcf\x03\n" +
9001024 "\aComment\x12\x0e\n" +
9011025 "\x02id\x18\x01 \x01(\x03R\x02id\x12\x16\n" +
9021026 "\x06author\x18\x02 \x01(\tR\x06author\x12\x12\n" +
@@ -970,7 +1094,7 @@ func file_ermite_storage_merge_request_proto_rawDescGZIP() []byte {
9701094 }
9711095
9721096 var file_ermite_storage_merge_request_proto_enumTypes = make([]protoimpl.EnumInfo, 6)
973-var file_ermite_storage_merge_request_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
1097+var file_ermite_storage_merge_request_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
9741098 var file_ermite_storage_merge_request_proto_goTypes = []any{
9751099 (MergeMode)(0), // 0: ermite.storage.MergeMode
9761100 (IssuesMode)(0), // 1: ermite.storage.IssuesMode
@@ -980,29 +1104,33 @@ var file_ermite_storage_merge_request_proto_goTypes = []any{
9801104 (MergeabilityState)(0), // 5: ermite.storage.MergeabilityState
9811105 (*RepoSettings)(nil), // 6: ermite.storage.RepoSettings
9821106 (*MergeRequest)(nil), // 7: ermite.storage.MergeRequest
983- (*Comment)(nil), // 8: ermite.storage.Comment
984- (*Sequence)(nil), // 9: ermite.storage.Sequence
985- (*Mergeability)(nil), // 10: ermite.storage.Mergeability
986- (*timestamppb.Timestamp)(nil), // 11: google.protobuf.Timestamp
1107+ (*AutoMerge)(nil), // 8: ermite.storage.AutoMerge
1108+ (*Comment)(nil), // 9: ermite.storage.Comment
1109+ (*Sequence)(nil), // 10: ermite.storage.Sequence
1110+ (*Mergeability)(nil), // 11: ermite.storage.Mergeability
1111+ (*timestamppb.Timestamp)(nil), // 12: google.protobuf.Timestamp
9871112 }
9881113 var file_ermite_storage_merge_request_proto_depIdxs = []int32{
9891114 0, // 0: ermite.storage.RepoSettings.allowed_merge_modes:type_name -> ermite.storage.MergeMode
9901115 1, // 1: ermite.storage.RepoSettings.issues:type_name -> ermite.storage.IssuesMode
9911116 2, // 2: ermite.storage.MergeRequest.state:type_name -> ermite.storage.MergeRequestState
992- 11, // 3: ermite.storage.MergeRequest.created_at:type_name -> google.protobuf.Timestamp
993- 11, // 4: ermite.storage.MergeRequest.updated_at:type_name -> google.protobuf.Timestamp
1117+ 12, // 3: ermite.storage.MergeRequest.created_at:type_name -> google.protobuf.Timestamp
1118+ 12, // 4: ermite.storage.MergeRequest.updated_at:type_name -> google.protobuf.Timestamp
9941119 0, // 5: ermite.storage.MergeRequest.merge_mode:type_name -> ermite.storage.MergeMode
995- 3, // 6: ermite.storage.Comment.side:type_name -> ermite.storage.DiffSide
996- 11, // 7: ermite.storage.Comment.created_at:type_name -> google.protobuf.Timestamp
997- 4, // 8: ermite.storage.Comment.verdict:type_name -> ermite.storage.Verdict
998- 11, // 9: ermite.storage.Comment.edited_at:type_name -> google.protobuf.Timestamp
999- 5, // 10: ermite.storage.Mergeability.state:type_name -> ermite.storage.MergeabilityState
1000- 11, // 11: ermite.storage.Mergeability.checked_at:type_name -> google.protobuf.Timestamp
1001- 12, // [12:12] is the sub-list for method output_type
1002- 12, // [12:12] is the sub-list for method input_type
1003- 12, // [12:12] is the sub-list for extension type_name
1004- 12, // [12:12] is the sub-list for extension extendee
1005- 0, // [0:12] is the sub-list for field type_name
1120+ 8, // 6: ermite.storage.MergeRequest.auto_merge:type_name -> ermite.storage.AutoMerge
1121+ 0, // 7: ermite.storage.AutoMerge.mode:type_name -> ermite.storage.MergeMode
1122+ 12, // 8: ermite.storage.AutoMerge.enabled_at:type_name -> google.protobuf.Timestamp
1123+ 3, // 9: ermite.storage.Comment.side:type_name -> ermite.storage.DiffSide
1124+ 12, // 10: ermite.storage.Comment.created_at:type_name -> google.protobuf.Timestamp
1125+ 4, // 11: ermite.storage.Comment.verdict:type_name -> ermite.storage.Verdict
1126+ 12, // 12: ermite.storage.Comment.edited_at:type_name -> google.protobuf.Timestamp
1127+ 5, // 13: ermite.storage.Mergeability.state:type_name -> ermite.storage.MergeabilityState
1128+ 12, // 14: ermite.storage.Mergeability.checked_at:type_name -> google.protobuf.Timestamp
1129+ 15, // [15:15] is the sub-list for method output_type
1130+ 15, // [15:15] is the sub-list for method input_type
1131+ 15, // [15:15] is the sub-list for extension type_name
1132+ 15, // [15:15] is the sub-list for extension extendee
1133+ 0, // [0:15] is the sub-list for field type_name
10061134 }
10071135
10081136 func init() { file_ermite_storage_merge_request_proto_init() }
@@ -1016,7 +1144,7 @@ func file_ermite_storage_merge_request_proto_init() {
10161144 GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
10171145 RawDescriptor: unsafe.Slice(unsafe.StringData(file_ermite_storage_merge_request_proto_rawDesc), len(file_ermite_storage_merge_request_proto_rawDesc)),
10181146 NumEnums: 6,
1019- NumMessages: 5,
1147+ NumMessages: 6,
10201148 NumExtensions: 0,
10211149 NumServices: 0,
10221150 },
@@ -352,9 +352,10 @@ type RepoSettings struct {
352 // choice of the owner. When false (settings stored before), exactly352 // choice of the owner. When false (settings stored before), exactly
353 // {fast-forward, squash} was the old default and gains MERGE_MODE_MERGE on353 // {fast-forward, squash} was the old default and gains MERGE_MODE_MERGE on
354 // read.354 // read.
355- MergeModesV2 bool `protobuf:"varint,4,opt,name=merge_modes_v2,json=mergeModesV2,proto3" json:"merge_modes_v2,omitempty"`355+ MergeModesV2 bool `protobuf:"varint,4,opt,name=merge_modes_v2,json=mergeModesV2,proto3" json:"merge_modes_v2,omitempty"`
356- unknownFields protoimpl.UnknownFields356+ AllowAutoMerge bool `protobuf:"varint,5,opt,name=allow_auto_merge,json=allowAutoMerge,proto3" json:"allow_auto_merge,omitempty"`
357- sizeCache protoimpl.SizeCache357+ unknownFields protoimpl.UnknownFields
358+ sizeCache protoimpl.SizeCache
358 }359 }
359 360
360 func (x *RepoSettings) Reset() {361 func (x *RepoSettings) Reset() {
@@ -415,6 +416,13 @@ func (x *RepoSettings) GetMergeModesV2() bool {
415 return false416 return false
416 }417 }
417 418
419+func (x *RepoSettings) GetAllowAutoMerge() bool {
420+ if x != nil {
421+ return x.AllowAutoMerge
422+ }
423+ return false
424+}
425+
418 // MergeRequest is stored under the key "mr/<repo>/<number:010d>".426 // MergeRequest is stored under the key "mr/<repo>/<number:010d>".
419 type MergeRequest struct {427 type MergeRequest struct {
420 state protoimpl.MessageState `protogen:"open.v1"`428 state protoimpl.MessageState `protogen:"open.v1"`
@@ -437,7 +445,10 @@ type MergeRequest struct {
437 MergedCommitId string `protobuf:"bytes,12,opt,name=merged_commit_id,json=mergedCommitId,proto3" json:"merged_commit_id,omitempty"`445 MergedCommitId string `protobuf:"bytes,12,opt,name=merged_commit_id,json=mergedCommitId,proto3" json:"merged_commit_id,omitempty"`
438 MergeMode MergeMode `protobuf:"varint,13,opt,name=merge_mode,json=mergeMode,proto3,enum=ermite.storage.MergeMode" json:"merge_mode,omitempty"`446 MergeMode MergeMode `protobuf:"varint,13,opt,name=merge_mode,json=mergeMode,proto3,enum=ermite.storage.MergeMode" json:"merge_mode,omitempty"`
439 // Last comment id handed out for this request.447 // Last comment id handed out for this request.
440- CommentSeq int64 `protobuf:"varint,14,opt,name=comment_seq,json=commentSeq,proto3" json:"comment_seq,omitempty"`448+ CommentSeq int64 `protobuf:"varint,14,opt,name=comment_seq,json=commentSeq,proto3" json:"comment_seq,omitempty"`
449+ // Pending auto-merge; unset when off.
450+ AutoMerge *AutoMerge `protobuf:"bytes,15,opt,name=auto_merge,json=autoMerge,proto3" json:"auto_merge,omitempty"`
451+ AutoMerged bool `protobuf:"varint,16,opt,name=auto_merged,json=autoMerged,proto3" json:"auto_merged,omitempty"`
441 unknownFields protoimpl.UnknownFields452 unknownFields protoimpl.UnknownFields
442 sizeCache protoimpl.SizeCache453 sizeCache protoimpl.SizeCache
443 }454 }
@@ -570,6 +581,106 @@ func (x *MergeRequest) GetCommentSeq() int64 {
570 return 0581 return 0
571 }582 }
572 583
584+func (x *MergeRequest) GetAutoMerge() *AutoMerge {
585+ if x != nil {
586+ return x.AutoMerge
587+ }
588+ return nil
589+}
590+
591+func (x *MergeRequest) GetAutoMerged() bool {
592+ if x != nil {
593+ return x.AutoMerged
594+ }
595+ return false
596+}
597+
598+// AutoMerge is the pending auto-merge of an open merge request.
599+type AutoMerge struct {
600+ state protoimpl.MessageState `protogen:"open.v1"`
601+ Mode MergeMode `protobuf:"varint,1,opt,name=mode,proto3,enum=ermite.storage.MergeMode" json:"mode,omitempty"`
602+ HeadId string `protobuf:"bytes,2,opt,name=head_id,json=headId,proto3" json:"head_id,omitempty"`
603+ // Kind and token id of the CIPusher that enabled it.
604+ EnablerKind string `protobuf:"bytes,3,opt,name=enabler_kind,json=enablerKind,proto3" json:"enabler_kind,omitempty"`
605+ EnablerTokenId string `protobuf:"bytes,4,opt,name=enabler_token_id,json=enablerTokenId,proto3" json:"enabler_token_id,omitempty"`
606+ EnablerLabel string `protobuf:"bytes,5,opt,name=enabler_label,json=enablerLabel,proto3" json:"enabler_label,omitempty"`
607+ EnabledAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=enabled_at,json=enabledAt,proto3" json:"enabled_at,omitempty"`
608+ unknownFields protoimpl.UnknownFields
609+ sizeCache protoimpl.SizeCache
610+}
611+
612+func (x *AutoMerge) Reset() {
613+ *x = AutoMerge{}
614+ mi := &file_ermite_storage_merge_request_proto_msgTypes[2]
615+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
616+ ms.StoreMessageInfo(mi)
617+}
618+
619+func (x *AutoMerge) String() string {
620+ return protoimpl.X.MessageStringOf(x)
621+}
622+
623+func (*AutoMerge) ProtoMessage() {}
624+
625+func (x *AutoMerge) ProtoReflect() protoreflect.Message {
626+ mi := &file_ermite_storage_merge_request_proto_msgTypes[2]
627+ if x != nil {
628+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
629+ if ms.LoadMessageInfo() == nil {
630+ ms.StoreMessageInfo(mi)
631+ }
632+ return ms
633+ }
634+ return mi.MessageOf(x)
635+}
636+
637+// Deprecated: Use AutoMerge.ProtoReflect.Descriptor instead.
638+func (*AutoMerge) Descriptor() ([]byte, []int) {
639+ return file_ermite_storage_merge_request_proto_rawDescGZIP(), []int{2}
640+}
641+
642+func (x *AutoMerge) GetMode() MergeMode {
643+ if x != nil {
644+ return x.Mode
645+ }
646+ return MergeMode_MERGE_MODE_UNSPECIFIED
647+}
648+
649+func (x *AutoMerge) GetHeadId() string {
650+ if x != nil {
651+ return x.HeadId
652+ }
653+ return ""
654+}
655+
656+func (x *AutoMerge) GetEnablerKind() string {
657+ if x != nil {
658+ return x.EnablerKind
659+ }
660+ return ""
661+}
662+
663+func (x *AutoMerge) GetEnablerTokenId() string {
664+ if x != nil {
665+ return x.EnablerTokenId
666+ }
667+ return ""
668+}
669+
670+func (x *AutoMerge) GetEnablerLabel() string {
671+ if x != nil {
672+ return x.EnablerLabel
673+ }
674+ return ""
675+}
676+
677+func (x *AutoMerge) GetEnabledAt() *timestamppb.Timestamp {
678+ if x != nil {
679+ return x.EnabledAt
680+ }
681+ return nil
682+}
683+
573 // Comment is stored under the key "mrc/<repo>/<number:010d>/<id:010d>".684 // Comment is stored under the key "mrc/<repo>/<number:010d>/<id:010d>".
574 type Comment struct {685 type Comment struct {
575 state protoimpl.MessageState `protogen:"open.v1"`686 state protoimpl.MessageState `protogen:"open.v1"`
@@ -593,7 +704,7 @@ type Comment struct {
593 704
594 func (x *Comment) Reset() {705 func (x *Comment) Reset() {
595 *x = Comment{}706 *x = Comment{}
596- mi := &file_ermite_storage_merge_request_proto_msgTypes[2]707+ mi := &file_ermite_storage_merge_request_proto_msgTypes[3]
597 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))708 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
598 ms.StoreMessageInfo(mi)709 ms.StoreMessageInfo(mi)
599 }710 }
@@ -605,7 +716,7 @@ func (x *Comment) String() string {
605 func (*Comment) ProtoMessage() {}716 func (*Comment) ProtoMessage() {}
606 717
607 func (x *Comment) ProtoReflect() protoreflect.Message {718 func (x *Comment) ProtoReflect() protoreflect.Message {
608- mi := &file_ermite_storage_merge_request_proto_msgTypes[2]719+ mi := &file_ermite_storage_merge_request_proto_msgTypes[3]
609 if x != nil {720 if x != nil {
610 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))721 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
611 if ms.LoadMessageInfo() == nil {722 if ms.LoadMessageInfo() == nil {
@@ -618,7 +729,7 @@ func (x *Comment) ProtoReflect() protoreflect.Message {
618 729
619 // Deprecated: Use Comment.ProtoReflect.Descriptor instead.730 // Deprecated: Use Comment.ProtoReflect.Descriptor instead.
620 func (*Comment) Descriptor() ([]byte, []int) {731 func (*Comment) Descriptor() ([]byte, []int) {
621- return file_ermite_storage_merge_request_proto_rawDescGZIP(), []int{2}732+ return file_ermite_storage_merge_request_proto_rawDescGZIP(), []int{3}
622 }733 }
623 734
624 func (x *Comment) GetId() int64 {735 func (x *Comment) GetId() int64 {
@@ -730,7 +841,7 @@ type Sequence struct {
730 841
731 func (x *Sequence) Reset() {842 func (x *Sequence) Reset() {
732 *x = Sequence{}843 *x = Sequence{}
733- mi := &file_ermite_storage_merge_request_proto_msgTypes[3]844+ mi := &file_ermite_storage_merge_request_proto_msgTypes[4]
734 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))845 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
735 ms.StoreMessageInfo(mi)846 ms.StoreMessageInfo(mi)
736 }847 }
@@ -742,7 +853,7 @@ func (x *Sequence) String() string {
742 func (*Sequence) ProtoMessage() {}853 func (*Sequence) ProtoMessage() {}
743 854
744 func (x *Sequence) ProtoReflect() protoreflect.Message {855 func (x *Sequence) ProtoReflect() protoreflect.Message {
745- mi := &file_ermite_storage_merge_request_proto_msgTypes[3]856+ mi := &file_ermite_storage_merge_request_proto_msgTypes[4]
746 if x != nil {857 if x != nil {
747 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))858 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
748 if ms.LoadMessageInfo() == nil {859 if ms.LoadMessageInfo() == nil {
@@ -755,7 +866,7 @@ func (x *Sequence) ProtoReflect() protoreflect.Message {
755 866
756 // Deprecated: Use Sequence.ProtoReflect.Descriptor instead.867 // Deprecated: Use Sequence.ProtoReflect.Descriptor instead.
757 func (*Sequence) Descriptor() ([]byte, []int) {868 func (*Sequence) Descriptor() ([]byte, []int) {
758- return file_ermite_storage_merge_request_proto_rawDescGZIP(), []int{3}869+ return file_ermite_storage_merge_request_proto_rawDescGZIP(), []int{4}
759 }870 }
760 871
761 func (x *Sequence) GetLast() int64 {872 func (x *Sequence) GetLast() int64 {
@@ -783,7 +894,7 @@ type Mergeability struct {
783 894
784 func (x *Mergeability) Reset() {895 func (x *Mergeability) Reset() {
785 *x = Mergeability{}896 *x = Mergeability{}
786- mi := &file_ermite_storage_merge_request_proto_msgTypes[4]897+ mi := &file_ermite_storage_merge_request_proto_msgTypes[5]
787 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))898 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
788 ms.StoreMessageInfo(mi)899 ms.StoreMessageInfo(mi)
789 }900 }
@@ -795,7 +906,7 @@ func (x *Mergeability) String() string {
795 func (*Mergeability) ProtoMessage() {}906 func (*Mergeability) ProtoMessage() {}
796 907
797 func (x *Mergeability) ProtoReflect() protoreflect.Message {908 func (x *Mergeability) ProtoReflect() protoreflect.Message {
798- mi := &file_ermite_storage_merge_request_proto_msgTypes[4]909+ mi := &file_ermite_storage_merge_request_proto_msgTypes[5]
799 if x != nil {910 if x != nil {
800 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))911 ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
801 if ms.LoadMessageInfo() == nil {912 if ms.LoadMessageInfo() == nil {
@@ -808,7 +919,7 @@ func (x *Mergeability) ProtoReflect() protoreflect.Message {
808 919
809 // Deprecated: Use Mergeability.ProtoReflect.Descriptor instead.920 // Deprecated: Use Mergeability.ProtoReflect.Descriptor instead.
810 func (*Mergeability) Descriptor() ([]byte, []int) {921 func (*Mergeability) Descriptor() ([]byte, []int) {
811- return file_ermite_storage_merge_request_proto_rawDescGZIP(), []int{4}922+ return file_ermite_storage_merge_request_proto_rawDescGZIP(), []int{5}
812 }923 }
813 924
814 func (x *Mergeability) GetState() MergeabilityState {925 func (x *Mergeability) GetState() MergeabilityState {
@@ -871,12 +982,13 @@ var File_ermite_storage_merge_request_proto protoreflect.FileDescriptor
871 982
872 const file_ermite_storage_merge_request_proto_rawDesc = "" +983 const file_ermite_storage_merge_request_proto_rawDesc = "" +
873 "\n" +984 "\n" +
874- "\"ermite/storage/merge_request.proto\x12\x0eermite.storage\x1a\x1fgoogle/protobuf/timestamp.proto\"\xe5\x01\n" +985+ "\"ermite/storage/merge_request.proto\x12\x0eermite.storage\x1a\x1fgoogle/protobuf/timestamp.proto\"\x8f\x02\n" +
875 "\fRepoSettings\x12I\n" +986 "\fRepoSettings\x12I\n" +
876 "\x13allowed_merge_modes\x18\x01 \x03(\x0e2\x19.ermite.storage.MergeModeR\x11allowedMergeModes\x120\n" +987 "\x13allowed_merge_modes\x18\x01 \x03(\x0e2\x19.ermite.storage.MergeModeR\x11allowedMergeModes\x120\n" +
877 "\x14failing_checks_block\x18\x02 \x01(\bR\x12failingChecksBlock\x122\n" +988 "\x14failing_checks_block\x18\x02 \x01(\bR\x12failingChecksBlock\x122\n" +
878 "\x06issues\x18\x03 \x01(\x0e2\x1a.ermite.storage.IssuesModeR\x06issues\x12$\n" +989 "\x06issues\x18\x03 \x01(\x0e2\x1a.ermite.storage.IssuesModeR\x06issues\x12$\n" +
879- "\x0emerge_modes_v2\x18\x04 \x01(\bR\fmergeModesV2\"\xa6\x04\n" +990+ "\x0emerge_modes_v2\x18\x04 \x01(\bR\fmergeModesV2\x12(\n" +
991+ "\x10allow_auto_merge\x18\x05 \x01(\bR\x0eallowAutoMerge\"\x81\x05\n" +
880 "\fMergeRequest\x12\x16\n" +992 "\fMergeRequest\x12\x16\n" +
881 "\x06number\x18\x01 \x01(\x03R\x06number\x12\x14\n" +993 "\x06number\x18\x01 \x01(\x03R\x06number\x12\x14\n" +
882 "\x05title\x18\x02 \x01(\tR\x05title\x12 \n" +994 "\x05title\x18\x02 \x01(\tR\x05title\x12 \n" +
@@ -896,7 +1008,19 @@ const file_ermite_storage_merge_request_proto_rawDesc = "" +
896 "\n" +1008 "\n" +
897 "merge_mode\x18\r \x01(\x0e2\x19.ermite.storage.MergeModeR\tmergeMode\x12\x1f\n" +1009 "merge_mode\x18\r \x01(\x0e2\x19.ermite.storage.MergeModeR\tmergeMode\x12\x1f\n" +
898 "\vcomment_seq\x18\x0e \x01(\x03R\n" +1010 "\vcomment_seq\x18\x0e \x01(\x03R\n" +
899- "commentSeq\"\xcf\x03\n" +1011+ "commentSeq\x128\n" +
1012+ "\n" +
1013+ "auto_merge\x18\x0f \x01(\v2\x19.ermite.storage.AutoMergeR\tautoMerge\x12\x1f\n" +
1014+ "\vauto_merged\x18\x10 \x01(\bR\n" +
1015+ "autoMerged\"\x80\x02\n" +
1016+ "\tAutoMerge\x12-\n" +
1017+ "\x04mode\x18\x01 \x01(\x0e2\x19.ermite.storage.MergeModeR\x04mode\x12\x17\n" +
1018+ "\ahead_id\x18\x02 \x01(\tR\x06headId\x12!\n" +
1019+ "\fenabler_kind\x18\x03 \x01(\tR\venablerKind\x12(\n" +
1020+ "\x10enabler_token_id\x18\x04 \x01(\tR\x0eenablerTokenId\x12#\n" +
1021+ "\renabler_label\x18\x05 \x01(\tR\fenablerLabel\x129\n" +
1022+ "\n" +
1023+ "enabled_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\tenabledAt\"\xcf\x03\n" +
900 "\aComment\x12\x0e\n" +1024 "\aComment\x12\x0e\n" +
901 "\x02id\x18\x01 \x01(\x03R\x02id\x12\x16\n" +1025 "\x02id\x18\x01 \x01(\x03R\x02id\x12\x16\n" +
902 "\x06author\x18\x02 \x01(\tR\x06author\x12\x12\n" +1026 "\x06author\x18\x02 \x01(\tR\x06author\x12\x12\n" +
@@ -970,7 +1094,7 @@ func file_ermite_storage_merge_request_proto_rawDescGZIP() []byte {
970 }1094 }
971 1095
972 var file_ermite_storage_merge_request_proto_enumTypes = make([]protoimpl.EnumInfo, 6)1096 var file_ermite_storage_merge_request_proto_enumTypes = make([]protoimpl.EnumInfo, 6)
973-var file_ermite_storage_merge_request_proto_msgTypes = make([]protoimpl.MessageInfo, 5)1097+var file_ermite_storage_merge_request_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
974 var file_ermite_storage_merge_request_proto_goTypes = []any{1098 var file_ermite_storage_merge_request_proto_goTypes = []any{
975 (MergeMode)(0), // 0: ermite.storage.MergeMode1099 (MergeMode)(0), // 0: ermite.storage.MergeMode
976 (IssuesMode)(0), // 1: ermite.storage.IssuesMode1100 (IssuesMode)(0), // 1: ermite.storage.IssuesMode
@@ -980,29 +1104,33 @@ var file_ermite_storage_merge_request_proto_goTypes = []any{
980 (MergeabilityState)(0), // 5: ermite.storage.MergeabilityState1104 (MergeabilityState)(0), // 5: ermite.storage.MergeabilityState
981 (*RepoSettings)(nil), // 6: ermite.storage.RepoSettings1105 (*RepoSettings)(nil), // 6: ermite.storage.RepoSettings
982 (*MergeRequest)(nil), // 7: ermite.storage.MergeRequest1106 (*MergeRequest)(nil), // 7: ermite.storage.MergeRequest
983- (*Comment)(nil), // 8: ermite.storage.Comment1107+ (*AutoMerge)(nil), // 8: ermite.storage.AutoMerge
984- (*Sequence)(nil), // 9: ermite.storage.Sequence1108+ (*Comment)(nil), // 9: ermite.storage.Comment
985- (*Mergeability)(nil), // 10: ermite.storage.Mergeability1109+ (*Sequence)(nil), // 10: ermite.storage.Sequence
986- (*timestamppb.Timestamp)(nil), // 11: google.protobuf.Timestamp1110+ (*Mergeability)(nil), // 11: ermite.storage.Mergeability
1111+ (*timestamppb.Timestamp)(nil), // 12: google.protobuf.Timestamp
987 }1112 }
988 var file_ermite_storage_merge_request_proto_depIdxs = []int32{1113 var file_ermite_storage_merge_request_proto_depIdxs = []int32{
989 0, // 0: ermite.storage.RepoSettings.allowed_merge_modes:type_name -> ermite.storage.MergeMode1114 0, // 0: ermite.storage.RepoSettings.allowed_merge_modes:type_name -> ermite.storage.MergeMode
990 1, // 1: ermite.storage.RepoSettings.issues:type_name -> ermite.storage.IssuesMode1115 1, // 1: ermite.storage.RepoSettings.issues:type_name -> ermite.storage.IssuesMode
991 2, // 2: ermite.storage.MergeRequest.state:type_name -> ermite.storage.MergeRequestState1116 2, // 2: ermite.storage.MergeRequest.state:type_name -> ermite.storage.MergeRequestState
992- 11, // 3: ermite.storage.MergeRequest.created_at:type_name -> google.protobuf.Timestamp1117+ 12, // 3: ermite.storage.MergeRequest.created_at:type_name -> google.protobuf.Timestamp
993- 11, // 4: ermite.storage.MergeRequest.updated_at:type_name -> google.protobuf.Timestamp1118+ 12, // 4: ermite.storage.MergeRequest.updated_at:type_name -> google.protobuf.Timestamp
994 0, // 5: ermite.storage.MergeRequest.merge_mode:type_name -> ermite.storage.MergeMode1119 0, // 5: ermite.storage.MergeRequest.merge_mode:type_name -> ermite.storage.MergeMode
995- 3, // 6: ermite.storage.Comment.side:type_name -> ermite.storage.DiffSide1120+ 8, // 6: ermite.storage.MergeRequest.auto_merge:type_name -> ermite.storage.AutoMerge
996- 11, // 7: ermite.storage.Comment.created_at:type_name -> google.protobuf.Timestamp1121+ 0, // 7: ermite.storage.AutoMerge.mode:type_name -> ermite.storage.MergeMode
997- 4, // 8: ermite.storage.Comment.verdict:type_name -> ermite.storage.Verdict1122+ 12, // 8: ermite.storage.AutoMerge.enabled_at:type_name -> google.protobuf.Timestamp
998- 11, // 9: ermite.storage.Comment.edited_at:type_name -> google.protobuf.Timestamp1123+ 3, // 9: ermite.storage.Comment.side:type_name -> ermite.storage.DiffSide
999- 5, // 10: ermite.storage.Mergeability.state:type_name -> ermite.storage.MergeabilityState1124+ 12, // 10: ermite.storage.Comment.created_at:type_name -> google.protobuf.Timestamp
1000- 11, // 11: ermite.storage.Mergeability.checked_at:type_name -> google.protobuf.Timestamp1125+ 4, // 11: ermite.storage.Comment.verdict:type_name -> ermite.storage.Verdict
1001- 12, // [12:12] is the sub-list for method output_type1126+ 12, // 12: ermite.storage.Comment.edited_at:type_name -> google.protobuf.Timestamp
1002- 12, // [12:12] is the sub-list for method input_type1127+ 5, // 13: ermite.storage.Mergeability.state:type_name -> ermite.storage.MergeabilityState
1003- 12, // [12:12] is the sub-list for extension type_name1128+ 12, // 14: ermite.storage.Mergeability.checked_at:type_name -> google.protobuf.Timestamp
1004- 12, // [12:12] is the sub-list for extension extendee1129+ 15, // [15:15] is the sub-list for method output_type
1005- 0, // [0:12] is the sub-list for field type_name1130+ 15, // [15:15] is the sub-list for method input_type
1131+ 15, // [15:15] is the sub-list for extension type_name
1132+ 15, // [15:15] is the sub-list for extension extendee
1133+ 0, // [0:15] is the sub-list for field type_name
1006 }1134 }
1007 1135
1008 func init() { file_ermite_storage_merge_request_proto_init() }1136 func init() { file_ermite_storage_merge_request_proto_init() }
@@ -1016,7 +1144,7 @@ func file_ermite_storage_merge_request_proto_init() {
1016 GoPackagePath: reflect.TypeOf(x{}).PkgPath(),1144 GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
1017 RawDescriptor: unsafe.Slice(unsafe.StringData(file_ermite_storage_merge_request_proto_rawDesc), len(file_ermite_storage_merge_request_proto_rawDesc)),1145 RawDescriptor: unsafe.Slice(unsafe.StringData(file_ermite_storage_merge_request_proto_rawDesc), len(file_ermite_storage_merge_request_proto_rawDesc)),
1018 NumEnums: 6,1146 NumEnums: 6,
1019- NumMessages: 5,1147+ NumMessages: 6,
1020 NumExtensions: 0,1148 NumExtensions: 0,
1021 NumServices: 0,1149 NumServices: 0,
1022 },1150 },
M internal/adapters/kvstore/mergerequests.go +32 -2
@@ -728,6 +728,18 @@ func encodeMergeRequest(mr domain.MergeRequest) ([]byte, error) {
728728 if err != nil {
729729 return nil, err
730730 }
731+ var autoMerge *storagepb.AutoMerge
732+ if am := mr.AutoMerge; am != nil {
733+ amMode, err := encodeMode(am.Mode)
734+ if err != nil || amMode == storagepb.MergeMode_MERGE_MODE_UNSPECIFIED {
735+ return nil, fmt.Errorf("%w: merge mode %q in the auto-merge", domain.ErrInvalid, am.Mode)
736+ }
737+ autoMerge = &storagepb.AutoMerge{
738+ Mode: amMode, HeadId: am.HeadID.String(),
739+ EnablerKind: string(am.EnabledBy.Kind), EnablerTokenId: am.EnabledBy.TokenID,
740+ EnablerLabel: am.EnablerLabel, EnabledAt: encodeTime(am.EnabledAt),
741+ }
742+ }
731743 return mustMarshal(&storagepb.MergeRequest{
732744 Number: mr.Number,
733745 Title: mr.Title.String(),
@@ -743,6 +755,8 @@ func encodeMergeRequest(mr domain.MergeRequest) ([]byte, error) {
743755 MergedCommitId: mr.MergedCommitID.String(),
744756 MergeMode: mode,
745757 CommentSeq: mr.CommentSeq,
758+ AutoMerge: autoMerge,
759+ AutoMerged: mr.AutoMerged,
746760 }), nil
747761 }
748762
@@ -773,8 +787,24 @@ func decodeMergeRequest(repo domain.RepoName, item kv.Item) (domain.MergeRequest
773787 CreatedAt: decodeTime(pb.GetCreatedAt()), UpdatedAt: decodeTime(pb.GetUpdatedAt()),
774788 MergeMode: decodeMode(pb.GetMergeMode()),
775789 CommentSeq: pb.GetCommentSeq(),
790+ AutoMerged: pb.GetAutoMerged(),
776791 Version: item.Version,
777792 }
793+ if am := pb.GetAutoMerge(); am != nil {
794+ head, err := domain.ParseObjectID(am.GetHeadId())
795+ if err != nil {
796+ return fail(err)
797+ }
798+ mode := decodeMode(am.GetMode())
799+ if mode == "" {
800+ return fail(fmt.Errorf("unknown auto-merge mode %d", am.GetMode()))
801+ }
802+ mr.AutoMerge = &domain.AutoMerge{
803+ Mode: mode, HeadID: head,
804+ EnabledBy: domain.CIPusher{Kind: domain.CIPusherKind(am.GetEnablerKind()), TokenID: am.GetEnablerTokenId()},
805+ EnablerLabel: am.GetEnablerLabel(), EnabledAt: decodeTime(am.GetEnabledAt()),
806+ }
807+ }
778808 for _, f := range []struct {
779809 dst *domain.ObjectID
780810 src string
@@ -909,7 +939,7 @@ func decodeSide(s storagepb.DiffSide) domain.DiffSide {
909939 }
910940
911941 func encodeSettings(s domain.RepoSettings) (*storagepb.RepoSettings, error) {
912- pb := &storagepb.RepoSettings{FailingChecksBlock: s.FailingChecksBlock, MergeModesV2: true}
942+ pb := &storagepb.RepoSettings{FailingChecksBlock: s.FailingChecksBlock, MergeModesV2: true, AllowAutoMerge: s.AllowAutoMerge}
913943 for _, m := range s.AllowedMergeModes {
914944 mode, err := encodeMode(m)
915945 if err != nil || mode == storagepb.MergeMode_MERGE_MODE_UNSPECIFIED {
@@ -949,5 +979,5 @@ func decodeSettings(pb *storagepb.RepoSettings) domain.RepoSettings {
949979 if !pb.GetMergeModesV2() && slices.Equal(normalized, []domain.MergeMode{domain.MergeModeFastForward, domain.MergeModeSquash}) {
950980 normalized = domain.MergeModes()
951981 }
952- return domain.RepoSettings{AllowedMergeModes: normalized, FailingChecksBlock: pb.GetFailingChecksBlock(), Issues: decodeIssuesMode(pb.GetIssues())}
982+ return domain.RepoSettings{AllowedMergeModes: normalized, FailingChecksBlock: pb.GetFailingChecksBlock(), Issues: decodeIssuesMode(pb.GetIssues()), AllowAutoMerge: pb.GetAllowAutoMerge()}
953983 }
@@ -728,6 +728,18 @@ func encodeMergeRequest(mr domain.MergeRequest) ([]byte, error) {
728 if err != nil {728 if err != nil {
729 return nil, err729 return nil, err
730 }730 }
731+ var autoMerge *storagepb.AutoMerge
732+ if am := mr.AutoMerge; am != nil {
733+ amMode, err := encodeMode(am.Mode)
734+ if err != nil || amMode == storagepb.MergeMode_MERGE_MODE_UNSPECIFIED {
735+ return nil, fmt.Errorf("%w: merge mode %q in the auto-merge", domain.ErrInvalid, am.Mode)
736+ }
737+ autoMerge = &storagepb.AutoMerge{
738+ Mode: amMode, HeadId: am.HeadID.String(),
739+ EnablerKind: string(am.EnabledBy.Kind), EnablerTokenId: am.EnabledBy.TokenID,
740+ EnablerLabel: am.EnablerLabel, EnabledAt: encodeTime(am.EnabledAt),
741+ }
742+ }
731 return mustMarshal(&storagepb.MergeRequest{743 return mustMarshal(&storagepb.MergeRequest{
732 Number: mr.Number,744 Number: mr.Number,
733 Title: mr.Title.String(),745 Title: mr.Title.String(),
@@ -743,6 +755,8 @@ func encodeMergeRequest(mr domain.MergeRequest) ([]byte, error) {
743 MergedCommitId: mr.MergedCommitID.String(),755 MergedCommitId: mr.MergedCommitID.String(),
744 MergeMode: mode,756 MergeMode: mode,
745 CommentSeq: mr.CommentSeq,757 CommentSeq: mr.CommentSeq,
758+ AutoMerge: autoMerge,
759+ AutoMerged: mr.AutoMerged,
746 }), nil760 }), nil
747 }761 }
748 762
@@ -773,8 +787,24 @@ func decodeMergeRequest(repo domain.RepoName, item kv.Item) (domain.MergeRequest
773 CreatedAt: decodeTime(pb.GetCreatedAt()), UpdatedAt: decodeTime(pb.GetUpdatedAt()),787 CreatedAt: decodeTime(pb.GetCreatedAt()), UpdatedAt: decodeTime(pb.GetUpdatedAt()),
774 MergeMode: decodeMode(pb.GetMergeMode()),788 MergeMode: decodeMode(pb.GetMergeMode()),
775 CommentSeq: pb.GetCommentSeq(),789 CommentSeq: pb.GetCommentSeq(),
790+ AutoMerged: pb.GetAutoMerged(),
776 Version: item.Version,791 Version: item.Version,
777 }792 }
793+ if am := pb.GetAutoMerge(); am != nil {
794+ head, err := domain.ParseObjectID(am.GetHeadId())
795+ if err != nil {
796+ return fail(err)
797+ }
798+ mode := decodeMode(am.GetMode())
799+ if mode == "" {
800+ return fail(fmt.Errorf("unknown auto-merge mode %d", am.GetMode()))
801+ }
802+ mr.AutoMerge = &domain.AutoMerge{
803+ Mode: mode, HeadID: head,
804+ EnabledBy: domain.CIPusher{Kind: domain.CIPusherKind(am.GetEnablerKind()), TokenID: am.GetEnablerTokenId()},
805+ EnablerLabel: am.GetEnablerLabel(), EnabledAt: decodeTime(am.GetEnabledAt()),
806+ }
807+ }
778 for _, f := range []struct {808 for _, f := range []struct {
779 dst *domain.ObjectID809 dst *domain.ObjectID
780 src string810 src string
@@ -909,7 +939,7 @@ func decodeSide(s storagepb.DiffSide) domain.DiffSide {
909 }939 }
910 940
911 func encodeSettings(s domain.RepoSettings) (*storagepb.RepoSettings, error) {941 func encodeSettings(s domain.RepoSettings) (*storagepb.RepoSettings, error) {
912- pb := &storagepb.RepoSettings{FailingChecksBlock: s.FailingChecksBlock, MergeModesV2: true}942+ pb := &storagepb.RepoSettings{FailingChecksBlock: s.FailingChecksBlock, MergeModesV2: true, AllowAutoMerge: s.AllowAutoMerge}
913 for _, m := range s.AllowedMergeModes {943 for _, m := range s.AllowedMergeModes {
914 mode, err := encodeMode(m)944 mode, err := encodeMode(m)
915 if err != nil || mode == storagepb.MergeMode_MERGE_MODE_UNSPECIFIED {945 if err != nil || mode == storagepb.MergeMode_MERGE_MODE_UNSPECIFIED {
@@ -949,5 +979,5 @@ func decodeSettings(pb *storagepb.RepoSettings) domain.RepoSettings {
949 if !pb.GetMergeModesV2() && slices.Equal(normalized, []domain.MergeMode{domain.MergeModeFastForward, domain.MergeModeSquash}) {979 if !pb.GetMergeModesV2() && slices.Equal(normalized, []domain.MergeMode{domain.MergeModeFastForward, domain.MergeModeSquash}) {
950 normalized = domain.MergeModes()980 normalized = domain.MergeModes()
951 }981 }
952- return domain.RepoSettings{AllowedMergeModes: normalized, FailingChecksBlock: pb.GetFailingChecksBlock(), Issues: decodeIssuesMode(pb.GetIssues())}982+ return domain.RepoSettings{AllowedMergeModes: normalized, FailingChecksBlock: pb.GetFailingChecksBlock(), Issues: decodeIssuesMode(pb.GetIssues()), AllowAutoMerge: pb.GetAllowAutoMerge()}
953 }983 }
M internal/adapters/kvstore/mergerequests_test.go +47 -0
@@ -94,6 +94,35 @@ func TestMergeRequestRoundTrip(t *testing.T) {
9494 assert.ErrorIs(t, err, domain.ErrNotFound)
9595 }
9696
97+func TestMergeRequestAutoMergeRoundTrip(t *testing.T) {
98+ ctx := context.Background()
99+ _, mrs, n := repoWithMRs(t)
100+
101+ set := newMR(t, n, "Auto")
102+ set.AutoMerge = &domain.AutoMerge{
103+ Mode: domain.MergeModeSquash, HeadID: oid('d'),
104+ EnabledBy: domain.CIPusher{Kind: domain.CIPusherToken, TokenID: "tok1"},
105+ EnablerLabel: "ci-bot", EnabledAt: created,
106+ }
107+ created1, err := mrs.Create(ctx, set)
108+ require.NoError(t, err)
109+ got, err := mrs.Get(ctx, n, created1.Number)
110+ require.NoError(t, err)
111+ require.NotNil(t, got.AutoMerge)
112+ assert.Equal(t, *set.AutoMerge, *got.AutoMerge)
113+ assert.False(t, got.AutoMerged)
114+
115+ unset := newMR(t, n, "Merged by auto")
116+ unset.State = domain.MRStateMerged
117+ unset.AutoMerged = true
118+ created2, err := mrs.Create(ctx, unset)
119+ require.NoError(t, err)
120+ got, err = mrs.Get(ctx, n, created2.Number)
121+ require.NoError(t, err)
122+ assert.Nil(t, got.AutoMerge)
123+ assert.True(t, got.AutoMerged)
124+}
125+
97126 func TestMergeRequestListIsNewestFirstAndPerRepository(t *testing.T) {
98127 ctx := context.Background()
99128 s, mrs, n := repoWithMRs(t)
@@ -453,6 +482,24 @@ func TestRepoSettings(t *testing.T) {
453482 assert.ErrorIs(t, err, domain.ErrInvalid)
454483 }
455484
485+func TestRepoSettingsKeepAllowAutoMerge(t *testing.T) {
486+ ctx := context.Background()
487+ s := openStore(t, t.TempDir())
488+ n := name(t, "proj")
489+ require.NoError(t, s.Create(ctx, domain.Repo{Name: n, CreatedAt: created, Visibility: domain.VisibilityPrivate}))
490+ got, err := s.Get(ctx, n)
491+ require.NoError(t, err)
492+ assert.False(t, got.Settings.AllowAutoMerge)
493+
494+ settings := domain.DefaultRepoSettings()
495+ settings.AllowAutoMerge = true
496+ _, err = s.Mutate(ctx, n, func(r *domain.Repo) (bool, error) { r.Settings = settings; return true, nil })
497+ require.NoError(t, err)
498+ got, err = s.Get(ctx, n)
499+ require.NoError(t, err)
500+ assert.Equal(t, settings, got.Settings)
501+}
502+
456503 // TestRepoWithoutStoredSettingsReadsTheDefaults: a repository stored before
457504 // merge requests existed has no settings field.
458505 func TestRepoWithoutStoredSettingsReadsTheDefaults(t *testing.T) {
@@ -94,6 +94,35 @@ func TestMergeRequestRoundTrip(t *testing.T) {
94 assert.ErrorIs(t, err, domain.ErrNotFound)94 assert.ErrorIs(t, err, domain.ErrNotFound)
95 }95 }
96 96
97+func TestMergeRequestAutoMergeRoundTrip(t *testing.T) {
98+ ctx := context.Background()
99+ _, mrs, n := repoWithMRs(t)
100+
101+ set := newMR(t, n, "Auto")
102+ set.AutoMerge = &domain.AutoMerge{
103+ Mode: domain.MergeModeSquash, HeadID: oid('d'),
104+ EnabledBy: domain.CIPusher{Kind: domain.CIPusherToken, TokenID: "tok1"},
105+ EnablerLabel: "ci-bot", EnabledAt: created,
106+ }
107+ created1, err := mrs.Create(ctx, set)
108+ require.NoError(t, err)
109+ got, err := mrs.Get(ctx, n, created1.Number)
110+ require.NoError(t, err)
111+ require.NotNil(t, got.AutoMerge)
112+ assert.Equal(t, *set.AutoMerge, *got.AutoMerge)
113+ assert.False(t, got.AutoMerged)
114+
115+ unset := newMR(t, n, "Merged by auto")
116+ unset.State = domain.MRStateMerged
117+ unset.AutoMerged = true
118+ created2, err := mrs.Create(ctx, unset)
119+ require.NoError(t, err)
120+ got, err = mrs.Get(ctx, n, created2.Number)
121+ require.NoError(t, err)
122+ assert.Nil(t, got.AutoMerge)
123+ assert.True(t, got.AutoMerged)
124+}
125+
97 func TestMergeRequestListIsNewestFirstAndPerRepository(t *testing.T) {126 func TestMergeRequestListIsNewestFirstAndPerRepository(t *testing.T) {
98 ctx := context.Background()127 ctx := context.Background()
99 s, mrs, n := repoWithMRs(t)128 s, mrs, n := repoWithMRs(t)
@@ -453,6 +482,24 @@ func TestRepoSettings(t *testing.T) {
453 assert.ErrorIs(t, err, domain.ErrInvalid)482 assert.ErrorIs(t, err, domain.ErrInvalid)
454 }483 }
455 484
485+func TestRepoSettingsKeepAllowAutoMerge(t *testing.T) {
486+ ctx := context.Background()
487+ s := openStore(t, t.TempDir())
488+ n := name(t, "proj")
489+ require.NoError(t, s.Create(ctx, domain.Repo{Name: n, CreatedAt: created, Visibility: domain.VisibilityPrivate}))
490+ got, err := s.Get(ctx, n)
491+ require.NoError(t, err)
492+ assert.False(t, got.Settings.AllowAutoMerge)
493+
494+ settings := domain.DefaultRepoSettings()
495+ settings.AllowAutoMerge = true
496+ _, err = s.Mutate(ctx, n, func(r *domain.Repo) (bool, error) { r.Settings = settings; return true, nil })
497+ require.NoError(t, err)
498+ got, err = s.Get(ctx, n)
499+ require.NoError(t, err)
500+ assert.Equal(t, settings, got.Settings)
501+}
502+
456 // TestRepoWithoutStoredSettingsReadsTheDefaults: a repository stored before503 // TestRepoWithoutStoredSettingsReadsTheDefaults: a repository stored before
457 // merge requests existed has no settings field.504 // merge requests existed has no settings field.
458 func TestRepoWithoutStoredSettingsReadsTheDefaults(t *testing.T) {505 func TestRepoWithoutStoredSettingsReadsTheDefaults(t *testing.T) {
M internal/domain/event.go +28 -0
@@ -131,11 +131,39 @@ type MergeRequestMerged struct {
131131 At time.Time
132132 Visibility Visibility
133133 CreatedAt time.Time
134+ // Auto says that auto-merge made the merge.
135+ Auto bool
134136 }
135137
136138 // EventKind implements Event.
137139 func (MergeRequestMerged) EventKind() string { return "mr.merged" }
138140
141+// MergeRequestAutoMergeEnabled is published when auto-merge was enabled on a
142+// merge request.
143+type MergeRequestAutoMergeEnabled struct {
144+ MergeRequest MergeRequest
145+ At time.Time
146+ Visibility Visibility
147+ CreatedAt time.Time
148+}
149+
150+// EventKind implements Event.
151+func (MergeRequestAutoMergeEnabled) EventKind() string { return "mr.auto_merge_enabled" }
152+
153+// MergeRequestAutoMergeCancelled is published when auto-merge was cancelled
154+// on a merge request, by hand or by the server.
155+type MergeRequestAutoMergeCancelled struct {
156+ MergeRequest MergeRequest
157+ // Reason says why, in words.
158+ Reason string
159+ At time.Time
160+ Visibility Visibility
161+ CreatedAt time.Time
162+}
163+
164+// EventKind implements Event.
165+func (MergeRequestAutoMergeCancelled) EventKind() string { return "mr.auto_merge_cancelled" }
166+
139167 // MergeRequestClosed is published when a merge request was closed without
140168 // merging.
141169 type MergeRequestClosed struct {
@@ -131,11 +131,39 @@ type MergeRequestMerged struct {
131 At time.Time131 At time.Time
132 Visibility Visibility132 Visibility Visibility
133 CreatedAt time.Time133 CreatedAt time.Time
134+ // Auto says that auto-merge made the merge.
135+ Auto bool
134 }136 }
135 137
136 // EventKind implements Event.138 // EventKind implements Event.
137 func (MergeRequestMerged) EventKind() string { return "mr.merged" }139 func (MergeRequestMerged) EventKind() string { return "mr.merged" }
138 140
141+// MergeRequestAutoMergeEnabled is published when auto-merge was enabled on a
142+// merge request.
143+type MergeRequestAutoMergeEnabled struct {
144+ MergeRequest MergeRequest
145+ At time.Time
146+ Visibility Visibility
147+ CreatedAt time.Time
148+}
149+
150+// EventKind implements Event.
151+func (MergeRequestAutoMergeEnabled) EventKind() string { return "mr.auto_merge_enabled" }
152+
153+// MergeRequestAutoMergeCancelled is published when auto-merge was cancelled
154+// on a merge request, by hand or by the server.
155+type MergeRequestAutoMergeCancelled struct {
156+ MergeRequest MergeRequest
157+ // Reason says why, in words.
158+ Reason string
159+ At time.Time
160+ Visibility Visibility
161+ CreatedAt time.Time
162+}
163+
164+// EventKind implements Event.
165+func (MergeRequestAutoMergeCancelled) EventKind() string { return "mr.auto_merge_cancelled" }
166+
139 // MergeRequestClosed is published when a merge request was closed without167 // MergeRequestClosed is published when a merge request was closed without
140 // merging.168 // merging.
141 type MergeRequestClosed struct {169 type MergeRequestClosed struct {
M internal/domain/event_test.go +7 -0
@@ -52,3 +52,10 @@ func TestEventKinds(t *testing.T) {
5252 assert.Equal(t, "repo.pushed", domain.PushEvent{}.EventKind())
5353 assert.Equal(t, "gap", domain.EventGap{}.EventKind())
5454 }
55+
56+func TestAutoMergeEventKinds(t *testing.T) {
57+ t.Parallel()
58+ assert.Equal(t, "mr.auto_merge_enabled", domain.MergeRequestAutoMergeEnabled{}.EventKind())
59+ assert.Equal(t, "mr.auto_merge_cancelled", domain.MergeRequestAutoMergeCancelled{}.EventKind())
60+ assert.Equal(t, "mr.merged", domain.MergeRequestMerged{Auto: true}.EventKind())
61+}
@@ -52,3 +52,10 @@ func TestEventKinds(t *testing.T) {
52 assert.Equal(t, "repo.pushed", domain.PushEvent{}.EventKind())52 assert.Equal(t, "repo.pushed", domain.PushEvent{}.EventKind())
53 assert.Equal(t, "gap", domain.EventGap{}.EventKind())53 assert.Equal(t, "gap", domain.EventGap{}.EventKind())
54 }54 }
55+
56+func TestAutoMergeEventKinds(t *testing.T) {
57+ t.Parallel()
58+ assert.Equal(t, "mr.auto_merge_enabled", domain.MergeRequestAutoMergeEnabled{}.EventKind())
59+ assert.Equal(t, "mr.auto_merge_cancelled", domain.MergeRequestAutoMergeCancelled{}.EventKind())
60+ assert.Equal(t, "mr.merged", domain.MergeRequestMerged{Auto: true}.EventKind())
61+}
M internal/domain/mergerequest.go +21 -0
@@ -50,6 +50,9 @@ type RepoSettings struct {
5050 FailingChecksBlock bool
5151 // Issues says who may write issues; the empty mode means IssuesOff.
5252 Issues IssuesMode
53+ // AllowAutoMerge says that merge requests may be set to merge themselves
54+ // once their checks pass. Off by default.
55+ AllowAutoMerge bool
5356 }
5457
5558 // DefaultRepoSettings are the settings of a new repository: every mode, and
@@ -139,11 +142,29 @@ type MergeRequest struct {
139142 MergeMode MergeMode
140143 // CommentSeq is the id of the last comment of the request.
141144 CommentSeq int64
145+ // AutoMerge is the pending auto-merge; nil when off.
146+ AutoMerge *AutoMerge
147+ // AutoMerged says that the request was merged by auto-merge.
148+ AutoMerged bool
142149 // Version is set by the store on read: Update fails with ErrConflict when
143150 // the stored request changed since. Opaque to everyone else.
144151 Version uint64
145152 }
146153
154+// AutoMerge is the pending auto-merge of an open merge request.
155+type AutoMerge struct {
156+ // Mode is the merge mode chosen when auto-merge was enabled.
157+ Mode MergeMode
158+ // HeadID is the head it was enabled for; a new head cancels it.
159+ HeadID ObjectID
160+ // EnabledBy is who enabled it; the merge is made on their behalf.
161+ EnabledBy CIPusher
162+ // EnablerLabel is the display name of the enabler.
163+ EnablerLabel string
164+ // EnabledAt is when auto-merge was enabled.
165+ EnabledAt time.Time
166+}
167+
147168 // draftPrefixes mark a draft at the start of a title, compared without regard
148169 // to case, as Forgejo and Gitea do.
149170 var draftPrefixes = []string{"wip:", "[wip]"}
@@ -50,6 +50,9 @@ type RepoSettings struct {
50 FailingChecksBlock bool50 FailingChecksBlock bool
51 // Issues says who may write issues; the empty mode means IssuesOff.51 // Issues says who may write issues; the empty mode means IssuesOff.
52 Issues IssuesMode52 Issues IssuesMode
53+ // AllowAutoMerge says that merge requests may be set to merge themselves
54+ // once their checks pass. Off by default.
55+ AllowAutoMerge bool
53 }56 }
54 57
55 // DefaultRepoSettings are the settings of a new repository: every mode, and58 // DefaultRepoSettings are the settings of a new repository: every mode, and
@@ -139,11 +142,29 @@ type MergeRequest struct {
139 MergeMode MergeMode142 MergeMode MergeMode
140 // CommentSeq is the id of the last comment of the request.143 // CommentSeq is the id of the last comment of the request.
141 CommentSeq int64144 CommentSeq int64
145+ // AutoMerge is the pending auto-merge; nil when off.
146+ AutoMerge *AutoMerge
147+ // AutoMerged says that the request was merged by auto-merge.
148+ AutoMerged bool
142 // Version is set by the store on read: Update fails with ErrConflict when149 // Version is set by the store on read: Update fails with ErrConflict when
143 // the stored request changed since. Opaque to everyone else.150 // the stored request changed since. Opaque to everyone else.
144 Version uint64151 Version uint64
145 }152 }
146 153
154+// AutoMerge is the pending auto-merge of an open merge request.
155+type AutoMerge struct {
156+ // Mode is the merge mode chosen when auto-merge was enabled.
157+ Mode MergeMode
158+ // HeadID is the head it was enabled for; a new head cancels it.
159+ HeadID ObjectID
160+ // EnabledBy is who enabled it; the merge is made on their behalf.
161+ EnabledBy CIPusher
162+ // EnablerLabel is the display name of the enabler.
163+ EnablerLabel string
164+ // EnabledAt is when auto-merge was enabled.
165+ EnabledAt time.Time
166+}
167+
147 // draftPrefixes mark a draft at the start of a title, compared without regard168 // draftPrefixes mark a draft at the start of a title, compared without regard
148 // to case, as Forgejo and Gitea do.169 // to case, as Forgejo and Gitea do.
149 var draftPrefixes = []string{"wip:", "[wip]"}170 var draftPrefixes = []string{"wip:", "[wip]"}
M internal/domain/mergerequest_test.go +6 -0
@@ -330,3 +330,9 @@ func TestMergeabilityAllowsAndIsFor(t *testing.T) {
330330 assert.False(t, m.IsFor(oid(t, 'a'), oid(t, 'c')))
331331 assert.False(t, domain.Mergeability{}.IsFor(domain.ObjectID{}, domain.ObjectID{}))
332332 }
333+
334+func TestRepoSettingsAllowAutoMergeDefaultOff(t *testing.T) {
335+ t.Parallel()
336+ assert.False(t, domain.DefaultRepoSettings().AllowAutoMerge)
337+ assert.False(t, domain.RepoSettings{}.AllowAutoMerge)
338+}
@@ -330,3 +330,9 @@ func TestMergeabilityAllowsAndIsFor(t *testing.T) {
330 assert.False(t, m.IsFor(oid(t, 'a'), oid(t, 'c')))330 assert.False(t, m.IsFor(oid(t, 'a'), oid(t, 'c')))
331 assert.False(t, domain.Mergeability{}.IsFor(domain.ObjectID{}, domain.ObjectID{}))331 assert.False(t, domain.Mergeability{}.IsFor(domain.ObjectID{}, domain.ObjectID{}))
332 }332 }
333+
334+func TestRepoSettingsAllowAutoMergeDefaultOff(t *testing.T) {
335+ t.Parallel()
336+ assert.False(t, domain.DefaultRepoSettings().AllowAutoMerge)
337+ assert.False(t, domain.RepoSettings{}.AllowAutoMerge)
338+}
M proto/ermite/storage/merge_request.proto +15 -0
@@ -30,6 +30,7 @@ message RepoSettings {
3030 // {fast-forward, squash} was the old default and gains MERGE_MODE_MERGE on
3131 // read.
3232 bool merge_modes_v2 = 4;
33+ bool allow_auto_merge = 5;
3334 }
3435
3536 // IssuesMode says who may write issues in a repository.
@@ -78,6 +79,20 @@ message MergeRequest {
7879 MergeMode merge_mode = 13;
7980 // Last comment id handed out for this request.
8081 int64 comment_seq = 14;
82+ // Pending auto-merge; unset when off.
83+ AutoMerge auto_merge = 15;
84+ bool auto_merged = 16;
85+}
86+
87+// AutoMerge is the pending auto-merge of an open merge request.
88+message AutoMerge {
89+ MergeMode mode = 1;
90+ string head_id = 2;
91+ // Kind and token id of the CIPusher that enabled it.
92+ string enabler_kind = 3;
93+ string enabler_token_id = 4;
94+ string enabler_label = 5;
95+ google.protobuf.Timestamp enabled_at = 6;
8196 }
8297
8398 // Verdict is the immutable review decision of a note.
@@ -30,6 +30,7 @@ message RepoSettings {
30 // {fast-forward, squash} was the old default and gains MERGE_MODE_MERGE on30 // {fast-forward, squash} was the old default and gains MERGE_MODE_MERGE on
31 // read.31 // read.
32 bool merge_modes_v2 = 4;32 bool merge_modes_v2 = 4;
33+ bool allow_auto_merge = 5;
33 }34 }
34 35
35 // IssuesMode says who may write issues in a repository.36 // IssuesMode says who may write issues in a repository.
@@ -78,6 +79,20 @@ message MergeRequest {
78 MergeMode merge_mode = 13;79 MergeMode merge_mode = 13;
79 // Last comment id handed out for this request.80 // Last comment id handed out for this request.
80 int64 comment_seq = 14;81 int64 comment_seq = 14;
82+ // Pending auto-merge; unset when off.
83+ AutoMerge auto_merge = 15;
84+ bool auto_merged = 16;
85+}
86+
87+// AutoMerge is the pending auto-merge of an open merge request.
88+message AutoMerge {
89+ MergeMode mode = 1;
90+ string head_id = 2;
91+ // Kind and token id of the CIPusher that enabled it.
92+ string enabler_kind = 3;
93+ string enabler_token_id = 4;
94+ string enabler_label = 5;
95+ google.protobuf.Timestamp enabled_at = 6;
81 }96 }
82 97
83 // Verdict is the immutable review decision of a note.98 // Verdict is the immutable review decision of a note.

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 43

↑↓ moveenter openesc close