@@ -604,6 +604,9 @@ def do_add_parser(self, parser_adder):
604604 exiting with an error if there are issues''' )
605605 group .add_argument ('--path' , action = 'store_true' ,
606606 help = "print the top level manifest file's path" )
607+ group .add_argument ('--untracked' , action = 'store_true' ,
608+ help = '''print any files and folders not managed or
609+ tracked by west''' )
607610
608611 group = parser .add_argument_group ('options for --resolve and --freeze' )
609612 group .add_argument ('-o' , '--out' ,
@@ -624,6 +627,8 @@ def do_run(self, args, user_args):
624627 elif args .freeze :
625628 self ._die_if_manifest_project_filter ('freeze' )
626629 self ._dump (args , manifest .as_frozen_yaml (** dump_kwargs ))
630+ elif args .untracked :
631+ self ._untracked ()
627632 elif args .path :
628633 self .inf (manifest .path )
629634 else :
@@ -640,6 +645,51 @@ def _die_if_manifest_project_filter(self, action):
640645 'the manifest while projects are made inactive by the '
641646 'project filter.' )
642647
648+ def _untracked (self ):
649+ ppaths = []
650+ untracked = []
651+ for project in self ._projects (None ):
652+ # We do not check for self.manifest.is_active(project) because
653+ # inactive projects are still considered "tracked folders"
654+ ppaths .append (Path (project .abspath ).resolve (strict = False ))
655+
656+ def _find_untracked (folder ):
657+ self .dbg (f'_find_untracked in: { folder } ' )
658+ for e in [e .resolve () for e in folder .iterdir ()]:
659+ if e .is_dir ():
660+ self .dbg (f'processing folder: { e } ' )
661+ for ppath in ppaths :
662+ # We cannot use samefile() because it requires the file
663+ # to exist (not always the case with inactive projects)
664+ if ppath == e :
665+ # We hit a project root folder, skip it
666+ break
667+ elif e in ppath .parents :
668+ self .dbg (f'recursing into: { e } ' )
669+ _find_untracked (e )
670+ break
671+ else :
672+ # No match, untracked folder
673+ untracked .append (e )
674+ continue
675+ else :
676+ # Untracked file
677+ untracked .append (e )
678+
679+ # Since west supports nested projects (i.e. a project inside the folder
680+ # of another project) we must sort the project paths to ensure that we
681+ # hit the "enclosing" project first when iterating
682+ ppaths .sort ()
683+
684+ # Avoid using Path.walk() since that returns all files and folders under
685+ # a particular folder, which is overkill in our case. Instead, recurse
686+ # only when required.
687+ _find_untracked (Path (self .topdir ))
688+ # Sort the results for displaying to the user
689+ untracked .sort ()
690+ for u in untracked :
691+ self .inf (u .relative_to (Path .cwd (), walk_up = True ))
692+
643693 def _dump (self , args , to_dump ):
644694 if args .out :
645695 with open (args .out , 'w' ) as f :
0 commit comments