@@ -127,19 +127,80 @@ def _filter(graph, targets, full):
127127 return new_graph
128128
129129
130- def _build (repo , target = None , full = False , outs = False ):
130+ def _is_foreach_matrix_stage (node , join_string ):
131+ if node .endswith (".dvc" ):
132+ return False
133+ return join_string in node
134+
135+
136+ def _collapse_foreach_matrix_get_nodes (graph ):
137+ from dvc .parsing import JOIN
138+
139+ new_nodes = set ()
140+ nodes_to_remove = set ()
141+ for _node in list (graph .nodes ):
142+ if not _is_foreach_matrix_stage (_node , JOIN ):
143+ continue
144+ nodes_to_remove .add (_node )
145+ new_nodes .add (_node .split (JOIN )[0 ])
146+ return new_nodes , nodes_to_remove
147+
148+
149+ def _collapse_foreach_matrix_get_edges (graph ):
150+ from dvc .parsing import JOIN
151+
152+ new_edges = set ()
153+ edges_to_remove = set ()
154+ for _e1 , _e2 in list (graph .edges ):
155+ _replace = False
156+ _new_e1 = _e1
157+ _new_e2 = _e2
158+ if _is_foreach_matrix_stage (_e1 , JOIN ):
159+ _new_e1 = _e1 .split (JOIN )[0 ]
160+ _replace = True
161+ if _is_foreach_matrix_stage (_e2 , JOIN ):
162+ _new_e2 = _e2 .split (JOIN )[0 ]
163+ _replace = True
164+ if _replace :
165+ edges_to_remove .add ((_e1 , _e2 ))
166+ new_edges .add ((_new_e1 , _new_e2 ))
167+ return new_edges , edges_to_remove
168+
169+
170+ def _collapse_foreach_matrix (graph ):
171+ new_nodes , nodes_to_remove = _collapse_foreach_matrix_get_nodes (graph )
172+ new_edges , edges_to_remove = _collapse_foreach_matrix_get_edges (graph )
173+ new_graph = graph .copy ()
174+ new_graph .remove_edges_from (edges_to_remove )
175+ new_graph .add_nodes_from (new_nodes )
176+ new_graph .add_edges_from (new_edges )
177+ new_graph .remove_nodes_from (nodes_to_remove )
178+ return new_graph
179+
180+
181+ def _build (repo , target = None , full = False , outs = False , collapse_foreach_matrix = False ):
131182 targets = _collect_targets (repo , target , outs )
132183 graph = _transform (repo .index , outs )
133- return _filter (graph , targets , full )
184+ filtered_graph = _filter (graph , targets , full )
185+ if collapse_foreach_matrix :
186+ filtered_graph = _collapse_foreach_matrix (filtered_graph )
187+ return filtered_graph
134188
135189
136190class CmdDAG (CmdBase ):
137191 def run (self ):
192+ from dvc .exceptions import InvalidArgumentError
193+
194+ if self .args .outs and self .args .collapse_foreach_matrix :
195+ raise InvalidArgumentError (
196+ "`--outs` and `--collapse-foreach-matrix` are mutually exclusive"
197+ )
138198 graph = _build (
139199 self .repo ,
140200 target = self .args .target ,
141201 full = self .args .full ,
142202 outs = self .args .outs ,
203+ collapse_foreach_matrix = self .args .collapse_foreach_matrix ,
143204 )
144205
145206 if self .args .dot :
@@ -181,6 +242,12 @@ def add_parser(subparsers, parent_parser):
181242 dest = "markdown" ,
182243 help = "Print DAG with mermaid format wrapped in Markdown block." ,
183244 )
245+ dag_parser .add_argument (
246+ "--collapse-foreach-matrix" ,
247+ action = "store_true" ,
248+ default = False ,
249+ help = "Collapse foreach/matrix stages to single stage." ,
250+ )
184251 dag_parser .add_argument (
185252 "--full" ,
186253 action = "store_true" ,
0 commit comments