@@ -21,6 +21,7 @@ import (
2121 "github.com/gruntwork-io/terragrunt/internal/worktrees"
2222 "github.com/gruntwork-io/terragrunt/options"
2323 "github.com/gruntwork-io/terragrunt/pkg/log"
24+ "github.com/gruntwork-io/terragrunt/telemetry"
2425 "golang.org/x/sync/errgroup"
2526)
2627
@@ -69,75 +70,117 @@ func (wd *WorktreeDiscovery) Discover(
6970 return component.Components {}, nil
7071 }
7172
72- discoveredComponents := component .NewThreadSafeComponents (component.Components {})
73-
74- // Run from and to discovery concurrently for each expression
75- discoveryGroup , discoveryCtx := errgroup .WithContext (ctx )
76- discoveryGroup .SetLimit (wd .numWorkers )
73+ var result component.Components
74+
75+ // Wrap entire worktree discovery with telemetry
76+ err := filter .TraceGitWorktreeDiscovery (ctx , len (w .WorktreePairs ), func (ctx context.Context ) error {
77+ discoveredComponents := component .NewThreadSafeComponents (component.Components {})
78+
79+ // Run from and to discovery concurrently for each expression
80+ discoveryGroup , discoveryCtx := errgroup .WithContext (ctx )
81+ discoveryGroup .SetLimit (wd .numWorkers )
82+
83+ for _ , pair := range w .WorktreePairs {
84+ discoveryGroup .Go (func () error {
85+ // Track filter expansion with telemetry
86+ var fromFilters , toFilters filter.Filters
87+
88+ diffs := pair .Diffs
89+
90+ expandErr := filter .TraceGitFilterExpand (
91+ discoveryCtx ,
92+ pair .GitExpression .FromRef ,
93+ pair .GitExpression .ToRef ,
94+ len (diffs .Added ),
95+ len (diffs .Removed ),
96+ len (diffs .Changed ),
97+ func (ctx context.Context ) error {
98+ fromFilters , toFilters = pair .Expand ()
99+ return nil
100+ },
101+ )
102+ if expandErr != nil {
103+ return expandErr
104+ }
105+
106+ // Run from and to discovery concurrently
107+ fromToG , fromToCtx := errgroup .WithContext (discoveryCtx )
108+
109+ // We only kick off from/to discovery if there any filters expanded from the git expression.
110+ // This ensures that we don't discover anything when using a Git filter that doesn't match anything.
111+
112+ if len (fromFilters ) > 0 {
113+ fromToG .Go (func () error {
114+ components , err := wd .discoverInWorktree (fromToCtx , l , opts , pair .FromWorktree , fromFilters , fromWorktreeKind )
115+ if err != nil {
116+ return err
117+ }
118+
119+ for _ , c := range components {
120+ discoveredComponents .EnsureComponent (c )
121+ }
122+
123+ return nil
124+ })
125+ }
126+
127+ if len (toFilters ) > 0 {
128+ fromToG .Go (func () error {
129+ components , err := wd .discoverInWorktree (fromToCtx , l , opts , pair .ToWorktree , toFilters , toWorktreeKind )
130+ if err != nil {
131+ return err
132+ }
133+
134+ for _ , c := range components {
135+ discoveredComponents .EnsureComponent (c )
136+ }
137+
138+ return nil
139+ })
140+ }
141+
142+ return fromToG .Wait ()
143+ })
144+ }
77145
78- for _ , pair := range w .WorktreePairs {
79146 discoveryGroup .Go (func () error {
80- fromFilters , toFilters := pair .Expand ()
81-
82- // Run from and to discovery concurrently
83- fromToG , fromToCtx := errgroup .WithContext (discoveryCtx )
84-
85- // We only kick off from/to discovery if there any filters expanded from the git expression.
86- // This ensures that we don't discover anything when using a Git filter that doesn't match anything.
87-
88- if len (fromFilters ) > 0 {
89- fromToG .Go (func () error {
90- components , err := wd .discoverInWorktree (fromToCtx , l , opts , pair .FromWorktree , fromFilters , fromWorktreeKind )
91- if err != nil {
92- return err
93- }
94-
95- for _ , c := range components {
96- discoveredComponents .EnsureComponent (c )
97- }
98-
99- return nil
100- })
147+ components , err := wd .discoverChangesInWorktreeStacks (ctx , l , opts , w )
148+ if err != nil {
149+ return err
101150 }
102151
103- if len (toFilters ) > 0 {
104- fromToG .Go (func () error {
105- components , err := wd .discoverInWorktree (fromToCtx , l , opts , pair .ToWorktree , toFilters , toWorktreeKind )
106- if err != nil {
107- return err
108- }
109-
110- for _ , c := range components {
111- discoveredComponents .EnsureComponent (c )
112- }
113-
114- return nil
115- })
152+ // Run directly in worktree paths - no translation needed
153+ for _ , c := range components {
154+ discoveredComponents .EnsureComponent (c )
116155 }
117156
118- return fromToG . Wait ()
157+ return nil
119158 })
120- }
121159
122- discoveryGroup .Go (func () error {
123- components , err := wd .discoverChangesInWorktreeStacks (ctx , l , opts , w )
124- if err != nil {
160+ if err := discoveryGroup .Wait (); err != nil {
125161 return err
126162 }
127163
128- // Run directly in worktree paths - no translation needed
129- for _ , c := range components {
130- discoveredComponents . EnsureComponent ( c )
131- }
164+ result = discoveredComponents . ToComponents ()
165+
166+ // Record result count metric
167+ recordWorktreeDiscoveryMetrics ( ctx , len ( w . WorktreePairs ), len ( result ))
132168
133169 return nil
134170 })
135171
136- if err := discoveryGroup .Wait (); err != nil {
137- return nil , err
172+ return result , err
173+ }
174+
175+ // recordWorktreeDiscoveryMetrics records telemetry metrics for worktree discovery.
176+ func recordWorktreeDiscoveryMetrics (ctx context.Context , pairCount , componentCount int ) {
177+ telemeter := telemetry .TelemeterFromContext (ctx )
178+ if telemeter == nil || telemeter .Meter == nil {
179+ return
138180 }
139181
140- return discoveredComponents .ToComponents (), nil
182+ telemeter .Count (ctx , "git_worktree_discovery_worktree_pairs" , int64 (pairCount ))
183+ telemeter .Count (ctx , "git_worktree_discovery_components" , int64 (componentCount ))
141184}
142185
143186// discoverInWorktree discovers components in a single worktree.
@@ -252,6 +295,34 @@ func (wd *WorktreeDiscovery) walkChangedStack(
252295 originalDiscovery * Discovery ,
253296 fromStack * component.Stack ,
254297 toStack * component.Stack ,
298+ ) (component.Components , error ) {
299+ var result component.Components
300+
301+ // Wrap stack walking with telemetry
302+ err := filter .TraceGitWorktreeStackWalk (
303+ ctx ,
304+ fromStack .DiscoveryContext ().Ref ,
305+ toStack .DiscoveryContext ().Ref ,
306+ func (ctx context.Context ) error {
307+ var walkErr error
308+
309+ result , walkErr = wd .walkChangedStackInternal (ctx , l , opts , originalDiscovery , fromStack , toStack )
310+
311+ return walkErr
312+ },
313+ )
314+
315+ return result , err
316+ }
317+
318+ // walkChangedStackInternal is the internal implementation of walkChangedStack.
319+ func (wd * WorktreeDiscovery ) walkChangedStackInternal (
320+ ctx context.Context ,
321+ l log.Logger ,
322+ opts * options.TerragruntOptions ,
323+ originalDiscovery * Discovery ,
324+ fromStack * component.Stack ,
325+ toStack * component.Stack ,
255326) (component.Components , error ) {
256327 fromDiscovery := * originalDiscovery
257328
0 commit comments