From 32e49988f8bef29380b97d9a3dca81a7e72b648d Mon Sep 17 00:00:00 2001 From: Yushan Lin Date: Fri, 29 May 2026 13:24:22 -0700 Subject: [PATCH 1/4] Add max_distance config --- config/repository_config.go | 4 ++ controller/BUILD.bazel | 1 + controller/controller.go | 13 ++++++ controller/distance_filter.go | 24 +++++++++- controller/distance_filter_test.go | 48 ++++++++++++++++++++ controller/getchangedtargets.go | 34 +++++++------- controller/getchangedtargets_test.go | 20 ++++---- controller/getchangedtargetsandedges.go | 25 +++++----- controller/getchangedtargetsandedges_test.go | 22 ++++----- 9 files changed, 140 insertions(+), 51 deletions(-) diff --git a/config/repository_config.go b/config/repository_config.go index c7654b0..0321348 100644 --- a/config/repository_config.go +++ b/config/repository_config.go @@ -25,4 +25,8 @@ type RepositoryConfig struct { BazelCommand string `yaml:"bazel_command"` QueryTimeout int64 `yaml:"query_timeout"` // in seconds BazelExtraArgs []string `yaml:"bazel_extra_args"` + // MaxDistance is the server-side default BFS distance cap applied when the + // client does not set output_config.compute_distances. 0 means unset (no cap). + // Positive values enable distance trimming with the given limit. + MaxDistance int32 `yaml:"max_distance"` } diff --git a/controller/BUILD.bazel b/controller/BUILD.bazel index 446790e..849995e 100644 --- a/controller/BUILD.bazel +++ b/controller/BUILD.bazel @@ -36,6 +36,7 @@ go_test( ], embed = [":controller"], deps = [ + "//config", "//core/common", "//core/storage", "//core/storage/storagemock", diff --git a/controller/controller.go b/controller/controller.go index fd4142d..9a3c9ea 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -33,6 +33,7 @@ type Params struct { Orchestrator orchestrator.Orchestrator Scope tally.Scope `optional:"true"` ChunkConfig config.ChunkConfig `optional:"true"` + Cfg *config.Config `optional:"true"` } type controller struct { @@ -43,6 +44,7 @@ type controller struct { targetChunkSize int changedTargetChunkSize int metadataMapChunkSize int + cfg *config.Config } // NewController creates a new controller. @@ -71,5 +73,16 @@ func NewController(p Params) pb.TangoYARPCServer { targetChunkSize: targetChunkSize, changedTargetChunkSize: changedTargetChunkSize, metadataMapChunkSize: metadataMapChunkSize, + cfg: p.Cfg, } } + +// getRepoConfig returns the RepositoryConfig for the given remote, or a +// zero-value config when no server-side configuration exists for that remote. +func (c *controller) getRepoConfig(remote string) config.RepositoryConfig { + if c.cfg == nil { + return config.RepositoryConfig{} + } + repoConfig, _ := c.cfg.GetRepositoryConfig(remote) + return repoConfig +} diff --git a/controller/distance_filter.go b/controller/distance_filter.go index d5c17a3..b4beabc 100644 --- a/controller/distance_filter.go +++ b/controller/distance_filter.go @@ -14,7 +14,10 @@ package controller -import pb "github.com/uber/tango/tangopb" +import ( + "github.com/uber/tango/config" + pb "github.com/uber/tango/tangopb" +) // maxDistanceFromOutputConfig returns the BFS distance cap for filtering // changed targets, or -1 when filtering is disabled. A non-negative value @@ -26,6 +29,25 @@ func maxDistanceFromOutputConfig(cfg *pb.OutputConfig) int32 { return -1 } +// resolveMaxDistance determines the effective BFS distance cap by merging the +// server-side repository default with the per-request output config. +// +// Priority (highest first): +// 1. outputConfig.compute_distances is true → respect the client's setting +// (returns outputConfig.max_distance, which may be 0 = direct-only or >0 = limit). +// 2. repoConfig.MaxDistance > 0 → server default: enable distance trimming +// with this limit even when the client did not request it. +// 3. Neither set → return -1 (no distance trimming). +func resolveMaxDistance(repoConfig config.RepositoryConfig, outputConfig *pb.OutputConfig) int32 { + if outputConfig.GetComputeDistances() { + return outputConfig.GetMaxDistance() + } + if repoConfig.MaxDistance > 0 { + return repoConfig.MaxDistance + } + return -1 +} + // filterChangedTargetsByDistance returns targets where 0 <= distance <= maxDist. // Returns the input slice unchanged when maxDist < 0 (filtering disabled). // Negative-distance targets (e.g. CHANGE_TYPE_NEW or unreachable from a diff --git a/controller/distance_filter_test.go b/controller/distance_filter_test.go index 1e42bf2..005ca88 100644 --- a/controller/distance_filter_test.go +++ b/controller/distance_filter_test.go @@ -18,6 +18,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/uber/tango/config" pb "github.com/uber/tango/tangopb" ) @@ -40,6 +41,53 @@ func TestMaxDistanceFromOutputConfig(t *testing.T) { } } +func TestResolveMaxDistance(t *testing.T) { + tests := []struct { + name string + repoCfg config.RepositoryConfig + outputCfg *pb.OutputConfig + want int32 + }{ + { + name: "neither set: no trimming", + repoCfg: config.RepositoryConfig{}, + want: -1, + }, + { + name: "repo config default applied when client has no compute_distances", + repoCfg: config.RepositoryConfig{MaxDistance: 3}, + want: 3, + }, + { + name: "client compute_distances overrides repo config", + repoCfg: config.RepositoryConfig{MaxDistance: 3}, + outputCfg: &pb.OutputConfig{ComputeDistances: true, MaxDistance: 5}, + want: 5, + }, + { + name: "client compute_distances=true with max_distance=0 overrides repo config", + repoCfg: config.RepositoryConfig{MaxDistance: 3}, + outputCfg: &pb.OutputConfig{ComputeDistances: true, MaxDistance: 0}, + want: 0, + }, + { + name: "client compute_distances=true, no repo config", + outputCfg: &pb.OutputConfig{ComputeDistances: true, MaxDistance: 2}, + want: 2, + }, + { + name: "repo config=0 means unset: no trimming", + repoCfg: config.RepositoryConfig{MaxDistance: 0}, + want: -1, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, resolveMaxDistance(tt.repoCfg, tt.outputCfg)) + }) + } +} + func TestFilterChangedTargetsByDistance(t *testing.T) { targets := []*pb.ChangedTarget{ {Distance: 0}, diff --git a/controller/getchangedtargets.go b/controller/getchangedtargets.go index cdcad33..a377f57 100644 --- a/controller/getchangedtargets.go +++ b/controller/getchangedtargets.go @@ -61,6 +61,8 @@ func (c *controller) GetChangedTargets(request *pb.GetChangedTargetsRequest, str logger.Info("GetChangedTargets: Processing request") + maxDist := resolveMaxDistance(c.getRepoConfig(request.GetFirstRevision().GetRemote()), request.GetOutputConfig()) + // Try to serve from cache first using the stored treehashes for both revisions. // readTreehash returns "" on any miss/error so we silently skip the cache when // either treehash is not yet available. @@ -103,7 +105,7 @@ func (c *controller) GetChangedTargets(request *pb.GetChangedTargetsRequest, str ) scope.Counter("cache_hit").Inc(1) scope.Timer("cache_read_duration").Record(cacheReadDuration) - if sendErr := sendWithDistanceFilter(stream, cached, request.GetOutputConfig()); sendErr != nil { + if sendErr := sendWithDistanceFilter(stream, cached, maxDist); sendErr != nil { logger.Error("GetChangedTargets: Failed to send cached response", zap.Error(sendErr)) return fmt.Errorf("failed to send cached response: %w", sendErr) } @@ -236,7 +238,7 @@ func (c *controller) GetChangedTargets(request *pb.GetChangedTargetsRequest, str jobs[1].graphStreamChunks = nil compareStart := time.Now() - changedTargetsResponses, err := c.compareTargetGraphs(logger, firstGraph, secondGraph, request.GetOutputConfig()) + changedTargetsResponses, err := c.compareTargetGraphs(logger, firstGraph, secondGraph, maxDist) // Allow GC of raw graph data while the caching goroutine runs. firstGraph = nil secondGraph = nil @@ -267,7 +269,7 @@ func (c *controller) GetChangedTargets(request *pb.GetChangedTargetsRequest, str }() sendStart := time.Now() - if err := sendWithDistanceFilter(stream, changedTargetsResponses, request.GetOutputConfig()); err != nil { + if err := sendWithDistanceFilter(stream, changedTargetsResponses, maxDist); err != nil { logger.Error("GetChangedTargets: Failed to send response", zap.Error(err)) return fmt.Errorf("failed to send response: %w", err) } @@ -283,7 +285,7 @@ func (c *controller) GetChangedTargets(request *pb.GetChangedTargetsRequest, str return nil } -func (c *controller) compareTargetGraphs(logger *zap.Logger, firstGraph, secondGraph []*pb.GetTargetGraphResponse, outputConfig *pb.OutputConfig) ([]*pb.GetChangedTargetsResponse, error) { +func (c *controller) compareTargetGraphs(logger *zap.Logger, firstGraph, secondGraph []*pb.GetTargetGraphResponse, maxDist int32) ([]*pb.GetChangedTargetsResponse, error) { start := time.Now() scope := c.scope.SubScope("compare_target_graphs") logger.Info("compareTargetGraphs: Computing differences between target graphs") @@ -338,7 +340,7 @@ func (c *controller) compareTargetGraphs(logger *zap.Logger, firstGraph, secondG secondMetadata.GetAttributeStringValueMapping(), getTargetId, getRuleTypeId, getTagId, getAttrNameId, getAttrValId, ), - Distance: getDefaultDistance(outputConfig, true), + Distance: getDefaultDistance(maxDist, true), } continue } @@ -382,7 +384,7 @@ func (c *controller) compareTargetGraphs(logger *zap.Logger, firstGraph, secondG ChangeType: initial, OldTarget: oldTarget, NewTarget: newTarget, - Distance: getDefaultDistance(outputConfig, false), + Distance: getDefaultDistance(maxDist, false), } } diffScanDuration := time.Since(diffScanStart) @@ -425,11 +427,10 @@ func (c *controller) compareTargetGraphs(logger *zap.Logger, firstGraph, secondG classifyDuration := time.Since(classifyStart) scope.Timer("classify_duration").Record(classifyDuration) - // Compute BFS distances from CHANGE_TYPE_DIRECT targets through the dependency graph. - // max_distance is only considered when compute_distances is true. - if outputConfig.GetComputeDistances() { + // Compute BFS distances when distance trimming is active (maxDist >= 0). + if maxDist >= 0 { distancesStart := time.Now() - computeDistances(c.logger, changedByName, secondByName, secondMetadata, outputConfig.GetMaxDistance()) + computeDistances(c.logger, changedByName, secondByName, secondMetadata, maxDist) distancesDuration := time.Since(distancesStart) scope.Timer("distances_duration").Record(distancesDuration) } @@ -749,11 +750,10 @@ func transposeOptimizedTarget( } // sendWithDistanceFilter streams responses to the client, filtering changed targets to those -// within outputConfig.MaxDistance from any CHANGE_TYPE_DIRECT target when compute_distances is true -// and max_distance >= 0. Metadata and other non-target responses are always forwarded. +// within maxDist from any CHANGE_TYPE_DIRECT target when maxDist >= 0. +// Metadata and other non-target responses are always forwarded. // Filtering and sending are combined into a single pass to avoid an intermediate allocation. -func sendWithDistanceFilter(stream pb.TangoServiceGetChangedTargetsYARPCServer, responses []*pb.GetChangedTargetsResponse, outputConfig *pb.OutputConfig) error { - maxDist := maxDistanceFromOutputConfig(outputConfig) +func sendWithDistanceFilter(stream pb.TangoServiceGetChangedTargetsYARPCServer, responses []*pb.GetChangedTargetsResponse, maxDist int32) error { for _, resp := range responses { toSend := resp if maxDist >= 0 { @@ -777,7 +777,7 @@ func sendWithDistanceFilter(stream pb.TangoServiceGetChangedTargetsYARPCServer, // target to each changed target via the reverse dependency graph using BFS. // DIRECT targets get distance 0, their reverse dependants get 1, and so on. // When maxDistance >= 0, the BFS is pruned: targets at distance > maxDistance are never -// enqueued, so they keep their initial distance of -1 (out-of-range). 0 means no limit. +// enqueued, so they keep their initial distance of -1 (out-of-range). // // Targets unreachable from any DIRECT target keep the initial distance of -1. func computeDistances(logger *zap.Logger, changedByName map[string]*pb.ChangedTarget, targetsByName map[string]*pb.OptimizedTarget, meta *pb.Metadata, maxDistance int32) { @@ -878,8 +878,8 @@ func validateGetChangedTargetsRequest(request *pb.GetChangedTargetsRequest) erro return nil } -func getDefaultDistance(outputConfig *pb.OutputConfig, forNewTarget bool) int32 { - if !outputConfig.GetComputeDistances() { +func getDefaultDistance(maxDist int32, forNewTarget bool) int32 { + if maxDist < 0 { return -1 } // New targets are always CHANGE_TYPE_NEW → distance 0. diff --git a/controller/getchangedtargets_test.go b/controller/getchangedtargets_test.go index 94da60e..cc15997 100644 --- a/controller/getchangedtargets_test.go +++ b/controller/getchangedtargets_test.go @@ -137,7 +137,7 @@ func TestCompareTargetGraphs(t *testing.T) { }, } - response, err := c.compareTargetGraphs(zap.NewNop(), []*pb.GetTargetGraphResponse{firstGraph}, []*pb.GetTargetGraphResponse{secondGraph}, nil) + response, err := c.compareTargetGraphs(zap.NewNop(), []*pb.GetTargetGraphResponse{firstGraph}, []*pb.GetTargetGraphResponse{secondGraph}, -1) require.NoError(t, err) require.NotNil(t, response) } @@ -436,7 +436,7 @@ func TestCompareTargetGraphs_NewTarget_CanonicalIDs(t *testing.T) { }, }, } - res, err := c.compareTargetGraphs(zap.NewNop(), first, second, nil) + res, err := c.compareTargetGraphs(zap.NewNop(), first, second, -1) require.NoError(t, err) require.Len(t, res, 2) cs := res[0].GetChangedTargets() @@ -508,7 +508,7 @@ func TestCompareTargetGraphs_SourceFileDirectAndPropagation(t *testing.T) { }, }, } - res, err := c.compareTargetGraphs(zap.NewNop(), first, second, nil) + res, err := c.compareTargetGraphs(zap.NewNop(), first, second, -1) require.NoError(t, err) cs := res[0].GetChangedTargets() require.NotNil(t, cs) @@ -576,7 +576,7 @@ func TestCompareTargetGraphs_IndirectWhenNoSourceDep(t *testing.T) { }, }, } - res, err := c.compareTargetGraphs(zap.NewNop(), first, second, nil) + res, err := c.compareTargetGraphs(zap.NewNop(), first, second, -1) require.NoError(t, err) cs := res[0].GetChangedTargets() require.NotNil(t, cs) @@ -641,7 +641,7 @@ func TestCompareTargetGraphs_DirectWhenDependenciesChanged(t *testing.T) { }, }, } - res, err := c.compareTargetGraphs(zap.NewNop(), first, second, nil) + res, err := c.compareTargetGraphs(zap.NewNop(), first, second, -1) require.NoError(t, err) cs := res[0].GetChangedTargets() require.NotNil(t, cs) @@ -722,7 +722,7 @@ func TestCompareTargetGraphs_DirectWhenAttributesChanged(t *testing.T) { }, }, } - res, err := c.compareTargetGraphs(zap.NewNop(), first, second, nil) + res, err := c.compareTargetGraphs(zap.NewNop(), first, second, -1) require.NoError(t, err) cs := res[0].GetChangedTargets() require.NotNil(t, cs) @@ -802,7 +802,7 @@ func TestCompareTargetGraphs_DirectWhenNewAttributeAdded(t *testing.T) { }, }, } - res, err := c.compareTargetGraphs(zap.NewNop(), first, second, nil) + res, err := c.compareTargetGraphs(zap.NewNop(), first, second, -1) require.NoError(t, err) cs := res[0].GetChangedTargets() require.NotNil(t, cs) @@ -877,7 +877,7 @@ func TestSendWithDistanceFilter_MetadataAlwaysForwarded(t *testing.T) { }).Times(2) // max_distance=1 filters out the distance-5 target, metadata always forwarded - require.NoError(t, sendWithDistanceFilter(stream, responses, &pb.OutputConfig{ComputeDistances: true, MaxDistance: 1})) + require.NoError(t, sendWithDistanceFilter(stream, responses, 1)) // First response: target filtered out (distance 5 > maxDist 1) assert.Empty(t, sent[0].GetChangedTargets().GetChangedTargets()) @@ -899,7 +899,7 @@ func TestSendWithDistanceFilter_SendError(t *testing.T) { stream.EXPECT().Send(gomock.Any()).Return(errors.New("send error")) - err := sendWithDistanceFilter(stream, responses, &pb.OutputConfig{}) + err := sendWithDistanceFilter(stream, responses, -1) assert.EqualError(t, err, "send error") } @@ -1048,7 +1048,7 @@ func TestCompareTargetGraphs_IndirectWhenOnlyHashChanged(t *testing.T) { }, }, } - res, err := c.compareTargetGraphs(zap.NewNop(), first, second, nil) + res, err := c.compareTargetGraphs(zap.NewNop(), first, second, -1) require.NoError(t, err) cs := res[0].GetChangedTargets() require.NotNil(t, cs) diff --git a/controller/getchangedtargetsandedges.go b/controller/getchangedtargetsandedges.go index 5a8ba1b..99e33f6 100644 --- a/controller/getchangedtargetsandedges.go +++ b/controller/getchangedtargetsandedges.go @@ -61,6 +61,8 @@ func (c *controller) GetChangedTargetsAndEdges(request *pb.GetChangedTargetsAndE logger.Info("GetChangedTargetsAndEdges: Processing request") + maxDist := resolveMaxDistance(c.getRepoConfig(request.GetFirstRevision().GetRemote()), request.GetOutputConfig()) + // Try to serve from cache first. if !request.GetBypassCache() { cacheStart := time.Now() @@ -97,7 +99,7 @@ func (c *controller) GetChangedTargetsAndEdges(request *pb.GetChangedTargetsAndE ) scope.Counter("cache_hit").Inc(1) scope.Timer("cache_read_duration").Record(cacheReadDuration) - if err := sendWithDistanceFilterForEdges(stream, cached, request.GetOutputConfig()); err != nil { + if err := sendWithDistanceFilterForEdges(stream, cached, maxDist); err != nil { logger.Error("GetChangedTargetsAndEdges: Failed to send cached response", zap.Error(err)) return err } @@ -213,7 +215,7 @@ func (c *controller) GetChangedTargetsAndEdges(request *pb.GetChangedTargetsAndE jobs[1].graphStreamChunks = nil compareStart := time.Now() - responses, err := c.compareTargetGraphsAndEdges(logger, firstGraph, secondGraph, request.GetOutputConfig()) + responses, err := c.compareTargetGraphsAndEdges(logger, firstGraph, secondGraph, maxDist) // Allow GC of raw graph data while the caching goroutine runs. firstGraph = nil secondGraph = nil @@ -241,7 +243,7 @@ func (c *controller) GetChangedTargetsAndEdges(request *pb.GetChangedTargetsAndE }() sendStart := time.Now() - if err := sendWithDistanceFilterForEdges(stream, responses, request.GetOutputConfig()); err != nil { + if err := sendWithDistanceFilterForEdges(stream, responses, maxDist); err != nil { logger.Error("GetChangedTargetsAndEdges: Failed to send response", zap.Error(err)) return err } @@ -257,7 +259,7 @@ func (c *controller) GetChangedTargetsAndEdges(request *pb.GetChangedTargetsAndE return nil } -func (c *controller) compareTargetGraphsAndEdges(logger *zap.Logger, firstGraph, secondGraph []*pb.GetTargetGraphResponse, outputConfig *pb.OutputConfig) ([]*pb.GetChangedTargetsAndEdgesResponse, error) { +func (c *controller) compareTargetGraphsAndEdges(logger *zap.Logger, firstGraph, secondGraph []*pb.GetTargetGraphResponse, maxDist int32) ([]*pb.GetChangedTargetsAndEdgesResponse, error) { start := time.Now() scope := c.scope.SubScope("compare_target_graphs_and_edges") logger.Info("compareTargetGraphsAndEdges: Computing differences between target graphs") @@ -323,7 +325,7 @@ func (c *controller) compareTargetGraphsAndEdges(logger *zap.Logger, firstGraph, changedByName[name] = &pb.ChangedTarget{ ChangeType: pb.CHANGE_TYPE_NEW, NewTarget: transposed, - Distance: getDefaultDistance(outputConfig, true), + Distance: getDefaultDistance(maxDist, true), } addedTargets = append(addedTargets, transposed) continue @@ -359,7 +361,7 @@ func (c *controller) compareTargetGraphsAndEdges(logger *zap.Logger, firstGraph, ChangeType: initial, OldTarget: oldTarget, NewTarget: newTarget, - Distance: getDefaultDistance(outputConfig, false), + Distance: getDefaultDistance(maxDist, false), } } @@ -392,9 +394,9 @@ func (c *controller) compareTargetGraphsAndEdges(logger *zap.Logger, firstGraph, } } - // 5) Compute BFS distances if requested. - if outputConfig.GetComputeDistances() { - computeDistances(logger, changedByName, secondByName, secondMetadata, outputConfig.GetMaxDistance()) + // 5) Compute BFS distances when distance trimming is active (maxDist >= 0). + if maxDist >= 0 { + computeDistances(logger, changedByName, secondByName, secondMetadata, maxDist) } // 6) Collect changed targets. @@ -541,15 +543,14 @@ func buildEdgeSet(byName map[string]*pb.OptimizedTarget, meta *pb.Metadata) map[ } // sendWithDistanceFilterForEdges streams responses, filtering changed_targets by -// BFS distance when output_config.compute_distances is set. Added/removed targets +// BFS distance when maxDist >= 0. Added/removed targets // and edges pass through unchanged — they represent graph topology deltas that // are not ranked by distance from a CHANGE_TYPE_DIRECT seed. func sendWithDistanceFilterForEdges( stream pb.TangoServiceGetChangedTargetsAndEdgesYARPCServer, responses []*pb.GetChangedTargetsAndEdgesResponse, - outputConfig *pb.OutputConfig, + maxDist int32, ) error { - maxDist := maxDistanceFromOutputConfig(outputConfig) for _, resp := range responses { toSend := resp if maxDist >= 0 { diff --git a/controller/getchangedtargetsandedges_test.go b/controller/getchangedtargetsandedges_test.go index a4363d4..630cdd8 100644 --- a/controller/getchangedtargetsandedges_test.go +++ b/controller/getchangedtargetsandedges_test.go @@ -258,7 +258,7 @@ func TestCompareTargetGraphsAndEdges_Empty(t *testing.T) { }} } - res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), emptyGraph(), emptyGraph(), nil) + res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), emptyGraph(), emptyGraph(), -1) require.NoError(t, err) require.Len(t, res, 2) cte, _ := collectCTEResponses(res) @@ -298,7 +298,7 @@ func TestCompareTargetGraphsAndEdges_AddedTarget(t *testing.T) { }, } - res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), first, second, nil) + res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), first, second, -1) require.NoError(t, err) cte, meta := collectCTEResponses(res) @@ -340,7 +340,7 @@ func TestCompareTargetGraphsAndEdges_RemovedTarget(t *testing.T) { }}, }} - res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), first, second, nil) + res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), first, second, -1) require.NoError(t, err) cte, meta := collectCTEResponses(res) @@ -395,7 +395,7 @@ func TestCompareTargetGraphsAndEdges_NewEdge(t *testing.T) { }, } - res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), first, second, nil) + res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), first, second, -1) require.NoError(t, err) cte, meta := collectCTEResponses(res) @@ -446,7 +446,7 @@ func TestCompareTargetGraphsAndEdges_RemovedEdge(t *testing.T) { }, } - res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), first, second, nil) + res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), first, second, -1) require.NoError(t, err) cte, meta := collectCTEResponses(res) @@ -499,7 +499,7 @@ func TestCompareTargetGraphsAndEdges_ChangedTargetClassification(t *testing.T) { }, } - res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), first, second, nil) + res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), first, second, -1) require.NoError(t, err) cte, meta := collectCTEResponses(res) @@ -546,7 +546,7 @@ func TestCompareTargetGraphsAndEdges_UnchangedTargetNotReturned(t *testing.T) { }, } - res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), first, second, nil) + res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), first, second, -1) require.NoError(t, err) cte, _ := collectCTEResponses(res) assert.Empty(t, cte.GetChangedTargets()) @@ -583,7 +583,7 @@ func TestCompareTargetGraphsAndEdges_EdgePreservedWhenTargetUnchanged(t *testing res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), mkGraph(1, 2, "hA", "hB"), mkGraph(10, 20, "hA", "hB"), - nil, + -1, ) require.NoError(t, err) cte, _ := collectCTEResponses(res) @@ -632,7 +632,7 @@ func TestCompareTargetGraphsAndEdges_CanonicalIDs(t *testing.T) { }, } - res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), first, second, nil) + res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), first, second, -1) require.NoError(t, err) cte, meta := collectCTEResponses(res) @@ -1112,7 +1112,7 @@ func TestSendWithDistanceFilterForEdges_NonChangedTargetFieldsPreserved(t *testi return nil }).Times(2) - require.NoError(t, sendWithDistanceFilterForEdges(stream, responses, &pb.OutputConfig{ComputeDistances: true, MaxDistance: 1})) + require.NoError(t, sendWithDistanceFilterForEdges(stream, responses, 1)) cte := sent[0].GetChangedTargetsAndEdges() require.Len(t, cte.GetChangedTargets(), 1, "only distance-0 target should survive") @@ -1140,7 +1140,7 @@ func TestSendWithDistanceFilterForEdges_SendError(t *testing.T) { stream.EXPECT().Send(gomock.Any()).Return(errors.New("send error")) - err := sendWithDistanceFilterForEdges(stream, responses, &pb.OutputConfig{}) + err := sendWithDistanceFilterForEdges(stream, responses, -1) require.Error(t, err) assert.Contains(t, err.Error(), "send error") } From 6c17e97fde6d6b9934241fe5719d209f9d04421f Mon Sep 17 00:00:00 2001 From: Yushan Lin Date: Fri, 29 May 2026 14:02:13 -0700 Subject: [PATCH 2/4] Update with repositoryConfigProvider --- config/config.go | 2 ++ config/repository_config.go | 6 ++++++ controller/controller.go | 22 +++++++++++----------- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/config/config.go b/config/config.go index 0dc1fbd..77ca493 100644 --- a/config/config.go +++ b/config/config.go @@ -22,6 +22,8 @@ import ( yaml "github.com/goccy/go-yaml" ) +var _ RepositoryConfigProvider = (*Config)(nil) + // Config is the root configuration structure. type Config struct { Repository []RepositoryConfig `yaml:"repository"` diff --git a/config/repository_config.go b/config/repository_config.go index 0321348..dd197e0 100644 --- a/config/repository_config.go +++ b/config/repository_config.go @@ -30,3 +30,9 @@ type RepositoryConfig struct { // Positive values enable distance trimming with the given limit. MaxDistance int32 `yaml:"max_distance"` } + +// RepositoryConfigProvider looks up per-repository configuration by remote. +// Implementations may read from a local file, a remote config service, etc. +type RepositoryConfigProvider interface { + GetRepositoryConfig(remote string) (RepositoryConfig, bool) +} diff --git a/controller/controller.go b/controller/controller.go index 9a3c9ea..4ec4763 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -28,12 +28,12 @@ import ( // Params are the parameters for the controller. type Params struct { fx.In - Logger *zap.Logger - Storage storage.Storage - Orchestrator orchestrator.Orchestrator - Scope tally.Scope `optional:"true"` - ChunkConfig config.ChunkConfig `optional:"true"` - Cfg *config.Config `optional:"true"` + Logger *zap.Logger + Storage storage.Storage + Orchestrator orchestrator.Orchestrator + Scope tally.Scope `optional:"true"` + ChunkConfig config.ChunkConfig `optional:"true"` + RepoConfigProvider config.RepositoryConfigProvider `optional:"true"` } type controller struct { @@ -44,7 +44,7 @@ type controller struct { targetChunkSize int changedTargetChunkSize int metadataMapChunkSize int - cfg *config.Config + repoConfigProvider config.RepositoryConfigProvider } // NewController creates a new controller. @@ -73,16 +73,16 @@ func NewController(p Params) pb.TangoYARPCServer { targetChunkSize: targetChunkSize, changedTargetChunkSize: changedTargetChunkSize, metadataMapChunkSize: metadataMapChunkSize, - cfg: p.Cfg, + repoConfigProvider: p.RepoConfigProvider, } } // getRepoConfig returns the RepositoryConfig for the given remote, or a -// zero-value config when no server-side configuration exists for that remote. +// zero-value config when no provider is configured or the remote is unknown. func (c *controller) getRepoConfig(remote string) config.RepositoryConfig { - if c.cfg == nil { + if c.repoConfigProvider == nil { return config.RepositoryConfig{} } - repoConfig, _ := c.cfg.GetRepositoryConfig(remote) + repoConfig, _ := c.repoConfigProvider.GetRepositoryConfig(remote) return repoConfig } From a5dbf675bc93d022206b232c9bb022268ec4cb46 Mon Sep 17 00:00:00 2001 From: Yushan Lin Date: Fri, 29 May 2026 14:14:03 -0700 Subject: [PATCH 3/4] update --- controller/controller.go | 6 +++--- controller/distance_filter_test.go | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/controller/controller.go b/controller/controller.go index 4ec4763..8d52cdb 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -31,9 +31,9 @@ type Params struct { Logger *zap.Logger Storage storage.Storage Orchestrator orchestrator.Orchestrator - Scope tally.Scope `optional:"true"` - ChunkConfig config.ChunkConfig `optional:"true"` - RepoConfigProvider config.RepositoryConfigProvider `optional:"true"` + Scope tally.Scope `optional:"true"` + ChunkConfig config.ChunkConfig `optional:"true"` + RepoConfigProvider config.RepositoryConfigProvider `optional:"true"` } type controller struct { diff --git a/controller/distance_filter_test.go b/controller/distance_filter_test.go index 005ca88..06ba46b 100644 --- a/controller/distance_filter_test.go +++ b/controller/distance_filter_test.go @@ -43,10 +43,10 @@ func TestMaxDistanceFromOutputConfig(t *testing.T) { func TestResolveMaxDistance(t *testing.T) { tests := []struct { - name string - repoCfg config.RepositoryConfig - outputCfg *pb.OutputConfig - want int32 + name string + repoCfg config.RepositoryConfig + outputCfg *pb.OutputConfig + want int32 }{ { name: "neither set: no trimming", From 2325e0728d09ec9b8a540180fbc8e15a62ba7ee3 Mon Sep 17 00:00:00 2001 From: Yushan Lin Date: Fri, 29 May 2026 14:38:13 -0700 Subject: [PATCH 4/4] Update compute_distance meaning --- controller/distance_filter.go | 27 ++++-------- controller/distance_filter_test.go | 44 +++++++------------- controller/getchangedtargets.go | 16 +++---- controller/getchangedtargets_test.go | 16 +++---- controller/getchangedtargetsandedges.go | 12 +++--- controller/getchangedtargetsandedges_test.go | 18 ++++---- proto/tango.proto | 8 ++-- 7 files changed, 60 insertions(+), 81 deletions(-) diff --git a/controller/distance_filter.go b/controller/distance_filter.go index b4beabc..1b3f445 100644 --- a/controller/distance_filter.go +++ b/controller/distance_filter.go @@ -19,27 +19,18 @@ import ( pb "github.com/uber/tango/tangopb" ) -// maxDistanceFromOutputConfig returns the BFS distance cap for filtering -// changed targets, or -1 when filtering is disabled. A non-negative value -// means only targets with 0 <= distance <= max should be kept. -func maxDistanceFromOutputConfig(cfg *pb.OutputConfig) int32 { - if cfg.GetComputeDistances() { - return cfg.GetMaxDistance() - } - return -1 -} - -// resolveMaxDistance determines the effective BFS distance cap by merging the -// server-side repository default with the per-request output config. +// resolveMaxDistance returns the effective BFS distance cap for filtering, or +// -1 when no filtering should be applied. // // Priority (highest first): -// 1. outputConfig.compute_distances is true → respect the client's setting -// (returns outputConfig.max_distance, which may be 0 = direct-only or >0 = limit). -// 2. repoConfig.MaxDistance > 0 → server default: enable distance trimming -// with this limit even when the client did not request it. -// 3. Neither set → return -1 (no distance trimming). +// 1. outputConfig.max_distance > 0 → use the client's explicit limit. +// 2. repoConfig.MaxDistance > 0 → server-side default. +// 3. Neither set → -1 (no distance filtering). +// +// Note: outputConfig.compute_distances controls whether the distance field is +// populated in the response and is independent of filtering. func resolveMaxDistance(repoConfig config.RepositoryConfig, outputConfig *pb.OutputConfig) int32 { - if outputConfig.GetComputeDistances() { + if outputConfig.GetMaxDistance() > 0 { return outputConfig.GetMaxDistance() } if repoConfig.MaxDistance > 0 { diff --git a/controller/distance_filter_test.go b/controller/distance_filter_test.go index 06ba46b..9059f45 100644 --- a/controller/distance_filter_test.go +++ b/controller/distance_filter_test.go @@ -22,25 +22,6 @@ import ( pb "github.com/uber/tango/tangopb" ) -func TestMaxDistanceFromOutputConfig(t *testing.T) { - tests := []struct { - name string - cfg *pb.OutputConfig - want int32 - }{ - {"nil config disables filter", nil, -1}, - {"compute_distances unset disables filter", &pb.OutputConfig{}, -1}, - {"compute_distances unset ignores max_distance", &pb.OutputConfig{MaxDistance: 5}, -1}, - {"compute_distances true returns max", &pb.OutputConfig{ComputeDistances: true, MaxDistance: 5}, 5}, - {"compute_distances true returns 0", &pb.OutputConfig{ComputeDistances: true, MaxDistance: 0}, 0}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.want, maxDistanceFromOutputConfig(tt.cfg)) - }) - } -} - func TestResolveMaxDistance(t *testing.T) { tests := []struct { name string @@ -49,37 +30,42 @@ func TestResolveMaxDistance(t *testing.T) { want int32 }{ { - name: "neither set: no trimming", + name: "neither set: no filtering", repoCfg: config.RepositoryConfig{}, want: -1, }, { - name: "repo config default applied when client has no compute_distances", + name: "repo config default applied", repoCfg: config.RepositoryConfig{MaxDistance: 3}, want: 3, }, { - name: "client compute_distances overrides repo config", + name: "client max_distance overrides repo config", repoCfg: config.RepositoryConfig{MaxDistance: 3}, - outputCfg: &pb.OutputConfig{ComputeDistances: true, MaxDistance: 5}, + outputCfg: &pb.OutputConfig{MaxDistance: 5}, want: 5, }, { - name: "client compute_distances=true with max_distance=0 overrides repo config", + name: "client max_distance=0 treated as unset, repo config applies", repoCfg: config.RepositoryConfig{MaxDistance: 3}, - outputCfg: &pb.OutputConfig{ComputeDistances: true, MaxDistance: 0}, - want: 0, + outputCfg: &pb.OutputConfig{MaxDistance: 0}, + want: 3, }, { - name: "client compute_distances=true, no repo config", - outputCfg: &pb.OutputConfig{ComputeDistances: true, MaxDistance: 2}, + name: "client max_distance set, no repo config", + outputCfg: &pb.OutputConfig{MaxDistance: 2}, want: 2, }, { - name: "repo config=0 means unset: no trimming", + name: "repo config=0 means unset: no filtering", repoCfg: config.RepositoryConfig{MaxDistance: 0}, want: -1, }, + { + name: "compute_distances alone does not enable filtering", + outputCfg: &pb.OutputConfig{ComputeDistances: true}, + want: -1, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/controller/getchangedtargets.go b/controller/getchangedtargets.go index a377f57..29b0801 100644 --- a/controller/getchangedtargets.go +++ b/controller/getchangedtargets.go @@ -238,7 +238,7 @@ func (c *controller) GetChangedTargets(request *pb.GetChangedTargetsRequest, str jobs[1].graphStreamChunks = nil compareStart := time.Now() - changedTargetsResponses, err := c.compareTargetGraphs(logger, firstGraph, secondGraph, maxDist) + changedTargetsResponses, err := c.compareTargetGraphs(logger, firstGraph, secondGraph, maxDist, request.GetOutputConfig().GetComputeDistances()) // Allow GC of raw graph data while the caching goroutine runs. firstGraph = nil secondGraph = nil @@ -285,7 +285,7 @@ func (c *controller) GetChangedTargets(request *pb.GetChangedTargetsRequest, str return nil } -func (c *controller) compareTargetGraphs(logger *zap.Logger, firstGraph, secondGraph []*pb.GetTargetGraphResponse, maxDist int32) ([]*pb.GetChangedTargetsResponse, error) { +func (c *controller) compareTargetGraphs(logger *zap.Logger, firstGraph, secondGraph []*pb.GetTargetGraphResponse, maxDist int32, outputDistances bool) ([]*pb.GetChangedTargetsResponse, error) { start := time.Now() scope := c.scope.SubScope("compare_target_graphs") logger.Info("compareTargetGraphs: Computing differences between target graphs") @@ -340,7 +340,7 @@ func (c *controller) compareTargetGraphs(logger *zap.Logger, firstGraph, secondG secondMetadata.GetAttributeStringValueMapping(), getTargetId, getRuleTypeId, getTagId, getAttrNameId, getAttrValId, ), - Distance: getDefaultDistance(maxDist, true), + Distance: getDefaultDistance(maxDist, outputDistances, true), } continue } @@ -384,7 +384,7 @@ func (c *controller) compareTargetGraphs(logger *zap.Logger, firstGraph, secondG ChangeType: initial, OldTarget: oldTarget, NewTarget: newTarget, - Distance: getDefaultDistance(maxDist, false), + Distance: getDefaultDistance(maxDist, outputDistances, false), } } diffScanDuration := time.Since(diffScanStart) @@ -427,8 +427,8 @@ func (c *controller) compareTargetGraphs(logger *zap.Logger, firstGraph, secondG classifyDuration := time.Since(classifyStart) scope.Timer("classify_duration").Record(classifyDuration) - // Compute BFS distances when distance trimming is active (maxDist >= 0). - if maxDist >= 0 { + // Compute BFS distances when filtering is active or the client requested distance output. + if maxDist >= 0 || outputDistances { distancesStart := time.Now() computeDistances(c.logger, changedByName, secondByName, secondMetadata, maxDist) distancesDuration := time.Since(distancesStart) @@ -878,8 +878,8 @@ func validateGetChangedTargetsRequest(request *pb.GetChangedTargetsRequest) erro return nil } -func getDefaultDistance(maxDist int32, forNewTarget bool) int32 { - if maxDist < 0 { +func getDefaultDistance(maxDist int32, outputDistances bool, forNewTarget bool) int32 { + if maxDist < 0 && !outputDistances { return -1 } // New targets are always CHANGE_TYPE_NEW → distance 0. diff --git a/controller/getchangedtargets_test.go b/controller/getchangedtargets_test.go index cc15997..8dfa125 100644 --- a/controller/getchangedtargets_test.go +++ b/controller/getchangedtargets_test.go @@ -137,7 +137,7 @@ func TestCompareTargetGraphs(t *testing.T) { }, } - response, err := c.compareTargetGraphs(zap.NewNop(), []*pb.GetTargetGraphResponse{firstGraph}, []*pb.GetTargetGraphResponse{secondGraph}, -1) + response, err := c.compareTargetGraphs(zap.NewNop(), []*pb.GetTargetGraphResponse{firstGraph}, []*pb.GetTargetGraphResponse{secondGraph}, -1, false) require.NoError(t, err) require.NotNil(t, response) } @@ -436,7 +436,7 @@ func TestCompareTargetGraphs_NewTarget_CanonicalIDs(t *testing.T) { }, }, } - res, err := c.compareTargetGraphs(zap.NewNop(), first, second, -1) + res, err := c.compareTargetGraphs(zap.NewNop(), first, second, -1, false) require.NoError(t, err) require.Len(t, res, 2) cs := res[0].GetChangedTargets() @@ -508,7 +508,7 @@ func TestCompareTargetGraphs_SourceFileDirectAndPropagation(t *testing.T) { }, }, } - res, err := c.compareTargetGraphs(zap.NewNop(), first, second, -1) + res, err := c.compareTargetGraphs(zap.NewNop(), first, second, -1, false) require.NoError(t, err) cs := res[0].GetChangedTargets() require.NotNil(t, cs) @@ -576,7 +576,7 @@ func TestCompareTargetGraphs_IndirectWhenNoSourceDep(t *testing.T) { }, }, } - res, err := c.compareTargetGraphs(zap.NewNop(), first, second, -1) + res, err := c.compareTargetGraphs(zap.NewNop(), first, second, -1, false) require.NoError(t, err) cs := res[0].GetChangedTargets() require.NotNil(t, cs) @@ -641,7 +641,7 @@ func TestCompareTargetGraphs_DirectWhenDependenciesChanged(t *testing.T) { }, }, } - res, err := c.compareTargetGraphs(zap.NewNop(), first, second, -1) + res, err := c.compareTargetGraphs(zap.NewNop(), first, second, -1, false) require.NoError(t, err) cs := res[0].GetChangedTargets() require.NotNil(t, cs) @@ -722,7 +722,7 @@ func TestCompareTargetGraphs_DirectWhenAttributesChanged(t *testing.T) { }, }, } - res, err := c.compareTargetGraphs(zap.NewNop(), first, second, -1) + res, err := c.compareTargetGraphs(zap.NewNop(), first, second, -1, false) require.NoError(t, err) cs := res[0].GetChangedTargets() require.NotNil(t, cs) @@ -802,7 +802,7 @@ func TestCompareTargetGraphs_DirectWhenNewAttributeAdded(t *testing.T) { }, }, } - res, err := c.compareTargetGraphs(zap.NewNop(), first, second, -1) + res, err := c.compareTargetGraphs(zap.NewNop(), first, second, -1, false) require.NoError(t, err) cs := res[0].GetChangedTargets() require.NotNil(t, cs) @@ -1048,7 +1048,7 @@ func TestCompareTargetGraphs_IndirectWhenOnlyHashChanged(t *testing.T) { }, }, } - res, err := c.compareTargetGraphs(zap.NewNop(), first, second, -1) + res, err := c.compareTargetGraphs(zap.NewNop(), first, second, -1, false) require.NoError(t, err) cs := res[0].GetChangedTargets() require.NotNil(t, cs) diff --git a/controller/getchangedtargetsandedges.go b/controller/getchangedtargetsandedges.go index 99e33f6..47c281d 100644 --- a/controller/getchangedtargetsandedges.go +++ b/controller/getchangedtargetsandedges.go @@ -215,7 +215,7 @@ func (c *controller) GetChangedTargetsAndEdges(request *pb.GetChangedTargetsAndE jobs[1].graphStreamChunks = nil compareStart := time.Now() - responses, err := c.compareTargetGraphsAndEdges(logger, firstGraph, secondGraph, maxDist) + responses, err := c.compareTargetGraphsAndEdges(logger, firstGraph, secondGraph, maxDist, request.GetOutputConfig().GetComputeDistances()) // Allow GC of raw graph data while the caching goroutine runs. firstGraph = nil secondGraph = nil @@ -259,7 +259,7 @@ func (c *controller) GetChangedTargetsAndEdges(request *pb.GetChangedTargetsAndE return nil } -func (c *controller) compareTargetGraphsAndEdges(logger *zap.Logger, firstGraph, secondGraph []*pb.GetTargetGraphResponse, maxDist int32) ([]*pb.GetChangedTargetsAndEdgesResponse, error) { +func (c *controller) compareTargetGraphsAndEdges(logger *zap.Logger, firstGraph, secondGraph []*pb.GetTargetGraphResponse, maxDist int32, outputDistances bool) ([]*pb.GetChangedTargetsAndEdgesResponse, error) { start := time.Now() scope := c.scope.SubScope("compare_target_graphs_and_edges") logger.Info("compareTargetGraphsAndEdges: Computing differences between target graphs") @@ -325,7 +325,7 @@ func (c *controller) compareTargetGraphsAndEdges(logger *zap.Logger, firstGraph, changedByName[name] = &pb.ChangedTarget{ ChangeType: pb.CHANGE_TYPE_NEW, NewTarget: transposed, - Distance: getDefaultDistance(maxDist, true), + Distance: getDefaultDistance(maxDist, outputDistances, true), } addedTargets = append(addedTargets, transposed) continue @@ -361,7 +361,7 @@ func (c *controller) compareTargetGraphsAndEdges(logger *zap.Logger, firstGraph, ChangeType: initial, OldTarget: oldTarget, NewTarget: newTarget, - Distance: getDefaultDistance(maxDist, false), + Distance: getDefaultDistance(maxDist, outputDistances, false), } } @@ -394,8 +394,8 @@ func (c *controller) compareTargetGraphsAndEdges(logger *zap.Logger, firstGraph, } } - // 5) Compute BFS distances when distance trimming is active (maxDist >= 0). - if maxDist >= 0 { + // 5) Compute BFS distances when filtering is active or the client requested distance output. + if maxDist >= 0 || outputDistances { computeDistances(logger, changedByName, secondByName, secondMetadata, maxDist) } diff --git a/controller/getchangedtargetsandedges_test.go b/controller/getchangedtargetsandedges_test.go index 630cdd8..365760d 100644 --- a/controller/getchangedtargetsandedges_test.go +++ b/controller/getchangedtargetsandedges_test.go @@ -258,7 +258,7 @@ func TestCompareTargetGraphsAndEdges_Empty(t *testing.T) { }} } - res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), emptyGraph(), emptyGraph(), -1) + res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), emptyGraph(), emptyGraph(), -1, false) require.NoError(t, err) require.Len(t, res, 2) cte, _ := collectCTEResponses(res) @@ -298,7 +298,7 @@ func TestCompareTargetGraphsAndEdges_AddedTarget(t *testing.T) { }, } - res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), first, second, -1) + res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), first, second, -1, false) require.NoError(t, err) cte, meta := collectCTEResponses(res) @@ -340,7 +340,7 @@ func TestCompareTargetGraphsAndEdges_RemovedTarget(t *testing.T) { }}, }} - res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), first, second, -1) + res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), first, second, -1, false) require.NoError(t, err) cte, meta := collectCTEResponses(res) @@ -395,7 +395,7 @@ func TestCompareTargetGraphsAndEdges_NewEdge(t *testing.T) { }, } - res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), first, second, -1) + res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), first, second, -1, false) require.NoError(t, err) cte, meta := collectCTEResponses(res) @@ -446,7 +446,7 @@ func TestCompareTargetGraphsAndEdges_RemovedEdge(t *testing.T) { }, } - res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), first, second, -1) + res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), first, second, -1, false) require.NoError(t, err) cte, meta := collectCTEResponses(res) @@ -499,7 +499,7 @@ func TestCompareTargetGraphsAndEdges_ChangedTargetClassification(t *testing.T) { }, } - res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), first, second, -1) + res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), first, second, -1, false) require.NoError(t, err) cte, meta := collectCTEResponses(res) @@ -546,7 +546,7 @@ func TestCompareTargetGraphsAndEdges_UnchangedTargetNotReturned(t *testing.T) { }, } - res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), first, second, -1) + res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), first, second, -1, false) require.NoError(t, err) cte, _ := collectCTEResponses(res) assert.Empty(t, cte.GetChangedTargets()) @@ -583,7 +583,7 @@ func TestCompareTargetGraphsAndEdges_EdgePreservedWhenTargetUnchanged(t *testing res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), mkGraph(1, 2, "hA", "hB"), mkGraph(10, 20, "hA", "hB"), - -1, + -1, false, ) require.NoError(t, err) cte, _ := collectCTEResponses(res) @@ -632,7 +632,7 @@ func TestCompareTargetGraphsAndEdges_CanonicalIDs(t *testing.T) { }, } - res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), first, second, -1) + res, err := c.compareTargetGraphsAndEdges(zap.NewNop(), first, second, -1, false) require.NoError(t, err) cte, meta := collectCTEResponses(res) diff --git a/proto/tango.proto b/proto/tango.proto index 942e662..a641d1f 100644 --- a/proto/tango.proto +++ b/proto/tango.proto @@ -55,10 +55,12 @@ message OutputConfig { bool include_hashes = 3; // Indicates if only root targets (i.e. targets that are not depended on by any other targets) should be included in the response bool roots_only = 4; - // Indicates if BFS distances from CHANGE_TYPE_DIRECT targets should be computed for each ChangedTarget + // Indicates if BFS distances from CHANGE_TYPE_DIRECT targets should be computed and included + // in each ChangedTarget's distance field. Independent of max_distance filtering. bool compute_distances = 5; - // Maximum BFS distance from a CHANGE_TYPE_DIRECT target to include. Only considered when compute_distances is true. - // 0 (default) means no limit. Positive values limit BFS: 1 = only DIRECT + immediate reverse deps, etc. + // Maximum BFS distance from a CHANGE_TYPE_DIRECT target to include in the response. + // 0 (default) means no distance filtering. Positive values limit results: 1 = only DIRECT + // targets, 2 = DIRECT + their immediate reverse deps, etc. Independent of compute_distances. int32 max_distance = 6; }