Skip to content

Commit e06d649

Browse files
committed
app: manifest: Add the ability to display "untracked" files and folders
This new command-line switch implements something similar to `git status`'s untracked files display. The code examines the workspace and prints out all files and folders that are not inside of a west project. Signed-off-by: Carles Cufi <[email protected]>
1 parent b75b86b commit e06d649

File tree

1 file changed

+78
-1
lines changed

1 file changed

+78
-1
lines changed

src/west/app/project.py

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,13 @@ def __init__(self):
584584
If this file uses imports, it will not contain all the
585585
manifest data.
586586
587+
- --untracked print all files and folders inside the workspace that
588+
are not tracked or managed by west. This effectively means any
589+
file or folder that lays outside all of the projects' folders on
590+
disk. This is similar to `git status` for untracked files. The
591+
output format is relative to the current working directory and is
592+
stable and suitable as input for scripting.
593+
587594
If the manifest file does not use imports, and all project
588595
revisions are SHAs, the --freeze and --resolve output will
589596
be identical after a "west update".
@@ -604,6 +611,9 @@ def do_add_parser(self, parser_adder):
604611
exiting with an error if there are issues''')
605612
group.add_argument('--path', action='store_true',
606613
help="print the top level manifest file's path")
614+
group.add_argument('--untracked', action='store_true',
615+
help='''print any files and folders not managed or
616+
tracked by west''')
607617

608618
group = parser.add_argument_group('options for --resolve and --freeze')
609619
group.add_argument('-o', '--out',
@@ -624,6 +634,8 @@ def do_run(self, args, user_args):
624634
elif args.freeze:
625635
self._die_if_manifest_project_filter('freeze')
626636
self._dump(args, manifest.as_frozen_yaml(**dump_kwargs))
637+
elif args.untracked:
638+
self._untracked()
627639
elif args.path:
628640
self.inf(manifest.path)
629641
else:
@@ -640,6 +652,68 @@ def _die_if_manifest_project_filter(self, action):
640652
'the manifest while projects are made inactive by the '
641653
'project filter.')
642654

655+
def _untracked(self):
656+
''' "Performs a top-down search of the west topdir,
657+
pruned every time we hit a west project.
658+
'''
659+
ppaths = []
660+
untracked = []
661+
for project in self._projects(None):
662+
# We do not check for self.manifest.is_active(project) because
663+
# inactive projects are still considered "tracked folders".
664+
ppaths.append(Path(project.abspath).resolve(strict=False))
665+
666+
# Since west tolerates nested projects (i.e. a project inside the folder
667+
# of another project) we must sort the project paths to ensure that we
668+
# hit the "enclosing" project first when iterating.
669+
ppaths.sort()
670+
671+
def _find_untracked(folder):
672+
'''There are three cases for each element in a folder:
673+
- It's a project -> Do nothing, prune the search.
674+
- There is zero project inside -> add to untracked list. Prune the search.
675+
- It's not a project but there are some projects inside -> recurse.
676+
The folder argument cannot be inside a project, otherwise all bets are off.
677+
'''
678+
self.dbg(f'_find_untracked in: {folder}')
679+
for e in [e.resolve() for e in folder.iterdir()]:
680+
if not e.is_dir():
681+
untracked.append(e)
682+
continue
683+
self.dbg(f'processing folder: {e}')
684+
for ppath in ppaths:
685+
# We cannot use samefile() because it requires the file
686+
# to exist (not always the case with inactive or even
687+
# uncloned projects).
688+
if ppath == e:
689+
# We hit a project root folder, skip it.
690+
break
691+
elif e in ppath.parents:
692+
self.dbg(f'recursing into: {e}')
693+
_find_untracked(e)
694+
break
695+
else:
696+
# There is not a project and there is no project inside.
697+
# Add to untracked elements.
698+
untracked.append(e)
699+
continue
700+
701+
# Avoid using Path.walk() since that returns all files and folders under
702+
# a particular folder, which is overkill in our case. Instead, recurse
703+
# only when required.
704+
_find_untracked(Path(self.topdir))
705+
706+
# Remove the .west folder, which is maintained by west
707+
try:
708+
untracked.remove((Path(self.topdir) / Path(WEST_DIR)).resolve())
709+
except ValueError:
710+
self.die(f'Folder {WEST_DIR} not found in workspace')
711+
712+
# Sort the results for displaying to the user.
713+
untracked.sort()
714+
for u in untracked:
715+
self.inf(u.relative_to(Path.cwd(), walk_up=True))
716+
643717
def _dump(self, args, to_dump):
644718
if args.out:
645719
with open(args.out, 'w') as f:
@@ -881,7 +955,10 @@ def __init__(self):
881955
'status',
882956
'"git status" for one or more projects',
883957
'''Runs "git status" for each of the specified projects.
884-
Unknown arguments are passed to "git status".''',
958+
Unknown arguments are passed to "git status".
959+
960+
Note: If you are looking to find untracked files and folders
961+
in the workspace use "west manifest --untracked".''',
885962
accepts_unknown_args=True,
886963
)
887964

0 commit comments

Comments
 (0)