From 66392f9dc4892c29a03cbad9875bafa4f4c3f578 Mon Sep 17 00:00:00 2001 From: manjari Date: Fri, 16 Oct 2026 19:34:58 +0000 Subject: [PATCH 1/4] feat(conflict): Add Tango request and signature intersection --- .../extension/conflict/tango/BUILD.bazel | 13 ++- .../extension/conflict/tango/intersect.go | 32 +++++++ .../conflict/tango/intersect_test.go | 61 ++++++++++++ .../extension/conflict/tango/mock_request.go | 95 +++++++++++++++++++ .../extension/conflict/tango/request.go | 72 ++++++++++++++ .../extension/conflict/tango/request_test.go | 54 +++++++++++ 6 files changed, 326 insertions(+), 1 deletion(-) create mode 100644 submitqueue/extension/conflict/tango/intersect.go create mode 100644 submitqueue/extension/conflict/tango/intersect_test.go create mode 100644 submitqueue/extension/conflict/tango/mock_request.go create mode 100644 submitqueue/extension/conflict/tango/request.go create mode 100644 submitqueue/extension/conflict/tango/request_test.go diff --git a/submitqueue/extension/conflict/tango/BUILD.bazel b/submitqueue/extension/conflict/tango/BUILD.bazel index a48a755c..b578d1fd 100644 --- a/submitqueue/extension/conflict/tango/BUILD.bazel +++ b/submitqueue/extension/conflict/tango/BUILD.bazel @@ -4,7 +4,10 @@ go_library( name = "go_default_library", srcs = [ "cache.go", + "intersect.go", + "mock_request.go", "mock_store.go", + "request.go", "signature.go", "store.go", "target_label.go", @@ -12,17 +15,25 @@ go_library( ], importpath = "github.com/uber/submitqueue/submitqueue/extension/conflict/tango", visibility = ["//visibility:public"], - deps = ["@org_uber_go_mock//gomock:go_default_library"], + deps = [ + "//platform/base/change:go_default_library", + "//platform/base/mergestrategy:go_default_library", + "@org_uber_go_mock//gomock:go_default_library", + ], ) go_test( name = "go_default_test", srcs = [ "cache_test.go", + "intersect_test.go", + "request_test.go", "signature_test.go", ], embed = [":go_default_library"], deps = [ + "//platform/base/change:go_default_library", + "//platform/base/mergestrategy:go_default_library", "@com_github_stretchr_testify//assert:go_default_library", "@com_github_stretchr_testify//require:go_default_library", ], diff --git a/submitqueue/extension/conflict/tango/intersect.go b/submitqueue/extension/conflict/tango/intersect.go new file mode 100644 index 00000000..2da8c1df --- /dev/null +++ b/submitqueue/extension/conflict/tango/intersect.go @@ -0,0 +1,32 @@ +// Copyright (c) 2026 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tango + +// sharesTarget reports whether two signatures have a target ID in common. Both +// must be sorted ascending by ID, so the check is one pass over each. +func sharesTarget(a, b []SignatureTarget) bool { + i, j := 0, 0 + for i < len(a) && j < len(b) { + switch { + case a[i].ID < b[j].ID: + i++ + case a[i].ID > b[j].ID: + j++ + default: + return true + } + } + return false +} diff --git a/submitqueue/extension/conflict/tango/intersect_test.go b/submitqueue/extension/conflict/tango/intersect_test.go new file mode 100644 index 00000000..2ee4948b --- /dev/null +++ b/submitqueue/extension/conflict/tango/intersect_test.go @@ -0,0 +1,61 @@ +// Copyright (c) 2026 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tango + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func targetsWithIDs(ids ...uint64) []SignatureTarget { + targets := make([]SignatureTarget, len(ids)) + for i, id := range ids { + targets[i] = SignatureTarget{ID: id} + } + return targets +} + +func TestSharesTarget(t *testing.T) { + const highBit = uint64(1) << 63 + + tests := []struct { + name string + a, b []SignatureTarget + want bool + }{ + {name: "both empty"}, + {name: "one empty", a: targetsWithIDs(1, 2)}, + {name: "disjoint", a: targetsWithIDs(1, 3, 5), b: targetsWithIDs(2, 4, 6)}, + {name: "same first ID", a: targetsWithIDs(1, 3), b: targetsWithIDs(1, 9), want: true}, + {name: "same last ID", a: targetsWithIDs(1, 7), b: targetsWithIDs(2, 7), want: true}, + {name: "same middle ID", a: targetsWithIDs(1, 4, 9), b: targetsWithIDs(2, 4, 8), want: true}, + {name: "different lengths", a: targetsWithIDs(5), b: targetsWithIDs(1, 2, 3, 4, 5, 6), want: true}, + {name: "IDs above the signed range order as unsigned", a: targetsWithIDs(1, highBit), b: targetsWithIDs(2, highBit+1)}, + {name: "same ID above the signed range", a: targetsWithIDs(1, highBit), b: targetsWithIDs(highBit), want: true}, + { + name: "distance does not affect the result", + a: []SignatureTarget{{ID: 4, Distance: 0}}, + b: []SignatureTarget{{ID: 4, Distance: 9}}, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, sharesTarget(tt.a, tt.b)) + assert.Equal(t, tt.want, sharesTarget(tt.b, tt.a)) + }) + } +} diff --git a/submitqueue/extension/conflict/tango/mock_request.go b/submitqueue/extension/conflict/tango/mock_request.go new file mode 100644 index 00000000..bb4a82d3 --- /dev/null +++ b/submitqueue/extension/conflict/tango/mock_request.go @@ -0,0 +1,95 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: request.go +// +// Generated by this command: +// +// mockgen -source=request.go -destination=mock_request.go -package=tango +// + +// Package tango is a generated GoMock package. +package tango + +import ( + context "context" + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockChangedTargetsClient is a mock of ChangedTargetsClient interface. +type MockChangedTargetsClient struct { + ctrl *gomock.Controller + recorder *MockChangedTargetsClientMockRecorder + isgomock struct{} +} + +// MockChangedTargetsClientMockRecorder is the mock recorder for MockChangedTargetsClient. +type MockChangedTargetsClientMockRecorder struct { + mock *MockChangedTargetsClient +} + +// NewMockChangedTargetsClient creates a new mock instance. +func NewMockChangedTargetsClient(ctrl *gomock.Controller) *MockChangedTargetsClient { + mock := &MockChangedTargetsClient{ctrl: ctrl} + mock.recorder = &MockChangedTargetsClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockChangedTargetsClient) EXPECT() *MockChangedTargetsClientMockRecorder { + return m.recorder +} + +// ChangedTargets mocks base method. +func (m *MockChangedTargetsClient) ChangedTargets(ctx context.Context, request ChangedTargetsRequest) ([]LabelDistance, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChangedTargets", ctx, request) + ret0, _ := ret[0].([]LabelDistance) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChangedTargets indicates an expected call of ChangedTargets. +func (mr *MockChangedTargetsClientMockRecorder) ChangedTargets(ctx, request any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChangedTargets", reflect.TypeOf((*MockChangedTargetsClient)(nil).ChangedTargets), ctx, request) +} + +// MockBaseResolver is a mock of BaseResolver interface. +type MockBaseResolver struct { + ctrl *gomock.Controller + recorder *MockBaseResolverMockRecorder + isgomock struct{} +} + +// MockBaseResolverMockRecorder is the mock recorder for MockBaseResolver. +type MockBaseResolverMockRecorder struct { + mock *MockBaseResolver +} + +// NewMockBaseResolver creates a new mock instance. +func NewMockBaseResolver(ctrl *gomock.Controller) *MockBaseResolver { + mock := &MockBaseResolver{ctrl: ctrl} + mock.recorder = &MockBaseResolverMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockBaseResolver) EXPECT() *MockBaseResolverMockRecorder { + return m.recorder +} + +// Base mocks base method. +func (m *MockBaseResolver) Base(ctx context.Context, queue string) (Base, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Base", ctx, queue) + ret0, _ := ret[0].(Base) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Base indicates an expected call of Base. +func (mr *MockBaseResolverMockRecorder) Base(ctx, queue any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Base", reflect.TypeOf((*MockBaseResolver)(nil).Base), ctx, queue) +} diff --git a/submitqueue/extension/conflict/tango/request.go b/submitqueue/extension/conflict/tango/request.go new file mode 100644 index 00000000..5b3d7c7c --- /dev/null +++ b/submitqueue/extension/conflict/tango/request.go @@ -0,0 +1,72 @@ +// Copyright (c) 2026 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tango + +//go:generate mockgen -source=request.go -destination=mock_request.go -package=tango + +import ( + "context" + + "github.com/uber/submitqueue/platform/base/change" + "github.com/uber/submitqueue/platform/base/mergestrategy" +) + +// ChangedTargetsRequest asks which targets change when Changes are applied on +// top of a base revision. +type ChangedTargetsRequest struct { + // Remote is the repository remote that BaseRevision belongs to. + Remote string + + // BaseRevision is the commit SHA the changes are applied on top of. + BaseRevision string + + // ChangeURIs lists every change to apply, in batch order. + ChangeURIs []string + + // Strategy is the merge strategy used to apply the changes. + Strategy mergestrategy.MergeStrategy +} + +// ChangedTargetsClient returns the targets a request affects. An adapter owns +// the Tango client and its stream, collects the response, and resolves each +// target's label; any failure to produce a complete answer is an error. +type ChangedTargetsClient interface { + // ChangedTargets returns one entry per affected target, in no particular + // order. An empty result is valid and means nothing is affected. + ChangedTargets(ctx context.Context, request ChangedTargetsRequest) ([]LabelDistance, error) +} + +// Base is the newest revision of a queue's target branch. +type Base struct { + // Remote is the repository remote the revision lives on. + Remote string + + // Revision is the full commit SHA of the branch tip. + Revision string +} + +// BaseResolver finds the base a queue's batches are analyzed against. +type BaseResolver interface { + // Base returns the current base of the named queue. + Base(ctx context.Context, queue string) (Base, error) +} + +func changedTargetsRequestFor(base Base, strategy mergestrategy.MergeStrategy, changes []change.Change) ChangedTargetsRequest { + var uris []string + for _, c := range changes { + uris = append(uris, c.URIs...) + } + return ChangedTargetsRequest{Remote: base.Remote, BaseRevision: base.Revision, ChangeURIs: uris, Strategy: strategy} +} diff --git a/submitqueue/extension/conflict/tango/request_test.go b/submitqueue/extension/conflict/tango/request_test.go new file mode 100644 index 00000000..0a954335 --- /dev/null +++ b/submitqueue/extension/conflict/tango/request_test.go @@ -0,0 +1,54 @@ +// Copyright (c) 2026 Uber Technologies, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tango + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/uber/submitqueue/platform/base/change" + "github.com/uber/submitqueue/platform/base/mergestrategy" +) + +func TestChangedTargetsRequestFor(t *testing.T) { + base := Base{Remote: "git://host/repo", Revision: "9f2c1d0e3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d"} + + tests := []struct { + name string + changes []change.Change + want []string + }{ + {name: "no changes", want: nil}, + {name: "one change", changes: []change.Change{{URIs: []string{"a"}}}, want: []string{"a"}}, + { + name: "URIs are flattened in batch order", + changes: []change.Change{{URIs: []string{"a", "b"}}, {URIs: []string{"c"}}, {URIs: []string{"d", "e"}}}, + want: []string{"a", "b", "c", "d", "e"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := changedTargetsRequestFor(base, mergestrategy.MergeStrategySquashRebase, tt.changes) + + assert.Equal(t, ChangedTargetsRequest{ + Remote: base.Remote, + BaseRevision: base.Revision, + ChangeURIs: tt.want, + Strategy: mergestrategy.MergeStrategySquashRebase, + }, got) + }) + } +} From 9dc3d494145ca4b62d885f7950f9b7f7b43fc466 Mon Sep 17 00:00:00 2001 From: manjari Date: Fri, 16 Oct 2026 21:00:16 +0000 Subject: [PATCH 2/4] fix(conflict): Remove merge strategy from Tango changed-targets request --- submitqueue/extension/conflict/tango/BUILD.bazel | 2 -- submitqueue/extension/conflict/tango/request.go | 10 +++------- submitqueue/extension/conflict/tango/request_test.go | 4 +--- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/submitqueue/extension/conflict/tango/BUILD.bazel b/submitqueue/extension/conflict/tango/BUILD.bazel index b578d1fd..ae13cbc1 100644 --- a/submitqueue/extension/conflict/tango/BUILD.bazel +++ b/submitqueue/extension/conflict/tango/BUILD.bazel @@ -17,7 +17,6 @@ go_library( visibility = ["//visibility:public"], deps = [ "//platform/base/change:go_default_library", - "//platform/base/mergestrategy:go_default_library", "@org_uber_go_mock//gomock:go_default_library", ], ) @@ -33,7 +32,6 @@ go_test( embed = [":go_default_library"], deps = [ "//platform/base/change:go_default_library", - "//platform/base/mergestrategy:go_default_library", "@com_github_stretchr_testify//assert:go_default_library", "@com_github_stretchr_testify//require:go_default_library", ], diff --git a/submitqueue/extension/conflict/tango/request.go b/submitqueue/extension/conflict/tango/request.go index 5b3d7c7c..193889ef 100644 --- a/submitqueue/extension/conflict/tango/request.go +++ b/submitqueue/extension/conflict/tango/request.go @@ -20,7 +20,6 @@ import ( "context" "github.com/uber/submitqueue/platform/base/change" - "github.com/uber/submitqueue/platform/base/mergestrategy" ) // ChangedTargetsRequest asks which targets change when Changes are applied on @@ -34,9 +33,6 @@ type ChangedTargetsRequest struct { // ChangeURIs lists every change to apply, in batch order. ChangeURIs []string - - // Strategy is the merge strategy used to apply the changes. - Strategy mergestrategy.MergeStrategy } // ChangedTargetsClient returns the targets a request affects. An adapter owns @@ -50,7 +46,7 @@ type ChangedTargetsClient interface { // Base is the newest revision of a queue's target branch. type Base struct { - // Remote is the repository remote the revision lives on. + // Remote names the repository in the form the ChangedTargetsClient expects. Remote string // Revision is the full commit SHA of the branch tip. @@ -63,10 +59,10 @@ type BaseResolver interface { Base(ctx context.Context, queue string) (Base, error) } -func changedTargetsRequestFor(base Base, strategy mergestrategy.MergeStrategy, changes []change.Change) ChangedTargetsRequest { +func changedTargetsRequestFor(base Base, changes []change.Change) ChangedTargetsRequest { var uris []string for _, c := range changes { uris = append(uris, c.URIs...) } - return ChangedTargetsRequest{Remote: base.Remote, BaseRevision: base.Revision, ChangeURIs: uris, Strategy: strategy} + return ChangedTargetsRequest{Remote: base.Remote, BaseRevision: base.Revision, ChangeURIs: uris} } diff --git a/submitqueue/extension/conflict/tango/request_test.go b/submitqueue/extension/conflict/tango/request_test.go index 0a954335..d60bca32 100644 --- a/submitqueue/extension/conflict/tango/request_test.go +++ b/submitqueue/extension/conflict/tango/request_test.go @@ -20,7 +20,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/uber/submitqueue/platform/base/change" - "github.com/uber/submitqueue/platform/base/mergestrategy" ) func TestChangedTargetsRequestFor(t *testing.T) { @@ -41,13 +40,12 @@ func TestChangedTargetsRequestFor(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := changedTargetsRequestFor(base, mergestrategy.MergeStrategySquashRebase, tt.changes) + got := changedTargetsRequestFor(base, tt.changes) assert.Equal(t, ChangedTargetsRequest{ Remote: base.Remote, BaseRevision: base.Revision, ChangeURIs: tt.want, - Strategy: mergestrategy.MergeStrategySquashRebase, }, got) }) } From 0f34932adc55156aac3fbe41d82ed474042d6f7b Mon Sep 17 00:00:00 2001 From: manjari Date: Fri, 16 Oct 2026 21:30:03 +0000 Subject: [PATCH 3/4] refactor(conflict): Simplify the Tango changed-targets request --- .../extension/conflict/tango/mock_request.go | 39 ------------------- .../extension/conflict/tango/request.go | 28 +++---------- .../extension/conflict/tango/request_test.go | 7 ++-- 3 files changed, 8 insertions(+), 66 deletions(-) diff --git a/submitqueue/extension/conflict/tango/mock_request.go b/submitqueue/extension/conflict/tango/mock_request.go index bb4a82d3..7aa4199b 100644 --- a/submitqueue/extension/conflict/tango/mock_request.go +++ b/submitqueue/extension/conflict/tango/mock_request.go @@ -54,42 +54,3 @@ func (mr *MockChangedTargetsClientMockRecorder) ChangedTargets(ctx, request any) mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChangedTargets", reflect.TypeOf((*MockChangedTargetsClient)(nil).ChangedTargets), ctx, request) } - -// MockBaseResolver is a mock of BaseResolver interface. -type MockBaseResolver struct { - ctrl *gomock.Controller - recorder *MockBaseResolverMockRecorder - isgomock struct{} -} - -// MockBaseResolverMockRecorder is the mock recorder for MockBaseResolver. -type MockBaseResolverMockRecorder struct { - mock *MockBaseResolver -} - -// NewMockBaseResolver creates a new mock instance. -func NewMockBaseResolver(ctrl *gomock.Controller) *MockBaseResolver { - mock := &MockBaseResolver{ctrl: ctrl} - mock.recorder = &MockBaseResolverMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockBaseResolver) EXPECT() *MockBaseResolverMockRecorder { - return m.recorder -} - -// Base mocks base method. -func (m *MockBaseResolver) Base(ctx context.Context, queue string) (Base, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Base", ctx, queue) - ret0, _ := ret[0].(Base) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// Base indicates an expected call of Base. -func (mr *MockBaseResolverMockRecorder) Base(ctx, queue any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Base", reflect.TypeOf((*MockBaseResolver)(nil).Base), ctx, queue) -} diff --git a/submitqueue/extension/conflict/tango/request.go b/submitqueue/extension/conflict/tango/request.go index 193889ef..675491b8 100644 --- a/submitqueue/extension/conflict/tango/request.go +++ b/submitqueue/extension/conflict/tango/request.go @@ -22,13 +22,10 @@ import ( "github.com/uber/submitqueue/platform/base/change" ) -// ChangedTargetsRequest asks which targets change when Changes are applied on -// top of a base revision. +// ChangedTargetsRequest asks which targets change when ChangeURIs are applied +// on top of a base revision. type ChangedTargetsRequest struct { - // Remote is the repository remote that BaseRevision belongs to. - Remote string - - // BaseRevision is the commit SHA the changes are applied on top of. + // BaseRevision is the full commit SHA the changes are applied on top of. BaseRevision string // ChangeURIs lists every change to apply, in batch order. @@ -44,25 +41,10 @@ type ChangedTargetsClient interface { ChangedTargets(ctx context.Context, request ChangedTargetsRequest) ([]LabelDistance, error) } -// Base is the newest revision of a queue's target branch. -type Base struct { - // Remote names the repository in the form the ChangedTargetsClient expects. - Remote string - - // Revision is the full commit SHA of the branch tip. - Revision string -} - -// BaseResolver finds the base a queue's batches are analyzed against. -type BaseResolver interface { - // Base returns the current base of the named queue. - Base(ctx context.Context, queue string) (Base, error) -} - -func changedTargetsRequestFor(base Base, changes []change.Change) ChangedTargetsRequest { +func changedTargetsRequestFor(baseRevision string, changes []change.Change) ChangedTargetsRequest { var uris []string for _, c := range changes { uris = append(uris, c.URIs...) } - return ChangedTargetsRequest{Remote: base.Remote, BaseRevision: base.Revision, ChangeURIs: uris} + return ChangedTargetsRequest{BaseRevision: baseRevision, ChangeURIs: uris} } diff --git a/submitqueue/extension/conflict/tango/request_test.go b/submitqueue/extension/conflict/tango/request_test.go index d60bca32..7f4efe75 100644 --- a/submitqueue/extension/conflict/tango/request_test.go +++ b/submitqueue/extension/conflict/tango/request_test.go @@ -23,7 +23,7 @@ import ( ) func TestChangedTargetsRequestFor(t *testing.T) { - base := Base{Remote: "git://host/repo", Revision: "9f2c1d0e3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d"} + baseRevision := "9f2c1d0e3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d" tests := []struct { name string @@ -40,11 +40,10 @@ func TestChangedTargetsRequestFor(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := changedTargetsRequestFor(base, tt.changes) + got := changedTargetsRequestFor(baseRevision, tt.changes) assert.Equal(t, ChangedTargetsRequest{ - Remote: base.Remote, - BaseRevision: base.Revision, + BaseRevision: baseRevision, ChangeURIs: tt.want, }, got) }) From aee98630a5bdfc0654f46f2eda1fdd4dace72c10 Mon Sep 17 00:00:00 2001 From: manjari Date: Fri, 16 Oct 2026 22:11:07 +0000 Subject: [PATCH 4/4] docs(conflict): Document what a Tango client adapter own --- submitqueue/extension/conflict/tango/request.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/submitqueue/extension/conflict/tango/request.go b/submitqueue/extension/conflict/tango/request.go index 675491b8..e038a4a1 100644 --- a/submitqueue/extension/conflict/tango/request.go +++ b/submitqueue/extension/conflict/tango/request.go @@ -34,10 +34,13 @@ type ChangedTargetsRequest struct { // ChangedTargetsClient returns the targets a request affects. An adapter owns // the Tango client and its stream, collects the response, and resolves each -// target's label; any failure to produce a complete answer is an error. +// target's label; any failure to produce a complete answer is an error. It also +// owns the Tango repository remote and computation strategy, which the request +// does not carry. ChangeURIs are passed through as given, so a URI scheme Tango +// does not support is an error. type ChangedTargetsClient interface { - // ChangedTargets returns one entry per affected target, in no particular - // order. An empty result is valid and means nothing is affected. + // ChangedTargets returns the affected targets in no particular order, and a + // label may repeat. An empty result is valid and means nothing is affected. ChangedTargets(ctx context.Context, request ChangedTargetsRequest) ([]LabelDistance, error) }