@@ -38,20 +38,38 @@ def __init__(self, root_dir, name, path, url, fxtag=None, fxurl=None, fxsparse=N
3838 self .fxrequired = "AlwaysRequired"
3939 self .logger = logger
4040
41- def status (self ):
41+ def status (self , depth = 0 , no_mods_details = False ):
4242 """
4343 Checks the status of the submodule and returns 4 parameters:
4444 - result (str): The status of the submodule.
4545 - needsupdate (bool): An indicator if the submodule needs to be updated.
4646 - localmods (bool): An indicator if the submodule has local modifications.
4747 - testfails (bool): An indicator if the submodule has failed a test, this is used for testing purposes.
48+
49+ Args:
50+ depth (int, optional): depth of this submodule relative to root, used to
51+ indent output for nested submodules
52+ no_mods_details (bool, optional): if True, suppress details on local mods in
53+ status output
4854 """
4955
5056 smpath = os .path .join (self .root_dir , self .path )
5157 testfails = False
5258 localmods = False
5359 needsupdate = False
5460 ahash = None
61+
62+ # The following prefix gives a tree-like output:
63+ tree_prefix_spaces = 3
64+ if depth == 0 :
65+ tree_prefix = ""
66+ else :
67+ tree_prefix = " " * tree_prefix_spaces * (depth - 1 ) + "└─ "
68+
69+ full_name = tree_prefix + self .name
70+ name_width = 20
71+ full_width = name_width + len (tree_prefix )
72+
5573 optional = ""
5674 if "Optional" in self .fxrequired :
5775 optional = " (optional)"
@@ -78,28 +96,28 @@ def status(self):
7896 if hhash and atag :
7997 break
8098 if self .fxtag and (ahash == hhash or atag == self .fxtag ):
81- result = f"e { self . name :>20 } not checked out, aligned at tag { self .fxtag } { optional } "
99+ result = f"e { full_name :<{ full_width } } not checked out, aligned at tag { self .fxtag } { optional } "
82100 needsupdate = True
83101 elif self .fxtag :
84102 status , ahash = rootgit .git_operation (
85103 "submodule" , "status" , "{}" .format (self .path )
86104 )
87105 ahash = ahash [1 : len (self .fxtag ) + 1 ]
88106 if self .fxtag == ahash :
89- result = f"e { self . name :>20 } not checked out, aligned at hash { ahash } { optional } "
107+ result = f"e { full_name :<{ full_width } } not checked out, aligned at hash { ahash } { optional } "
90108 else :
91- result = f"e { self . name :>20 } not checked out, out of sync at tag { atag } , expected tag is { self .fxtag } { optional } "
109+ result = f"e { full_name :<{ full_width } } not checked out, out of sync at tag { atag } , expected tag is { self .fxtag } { optional } "
92110 testfails = True
93111 needsupdate = True
94112 else :
95- result = f"e { self . name :>20 } has no fxtag defined in .gitmodules{ optional } "
113+ result = f"e { full_name :<{ full_width } } has no fxtag defined in .gitmodules{ optional } "
96114 testfails = False
97115 else :
98116 with utils .pushd (smpath ):
99117 git = GitInterface (smpath , self .logger )
100118 status , remote = git .git_operation ("remote" )
101119 if remote == '' :
102- result = f"e { self . name :>20 } has no associated remote"
120+ result = f"e { full_name :<{ full_width } } has no associated remote"
103121 testfails = True
104122 needsupdate = True
105123 return result , needsupdate , localmods , testfails
@@ -123,36 +141,76 @@ def status(self):
123141 if rurl != self .url :
124142 remote = self ._add_remote (git )
125143 git .git_operation ("fetch" , remote )
144+
145+ mod_char = " "
146+ _ , status_output = git .git_operation ("status" , "--ignore-submodules" , "-uno" )
147+ if "nothing to commit" not in status_output :
148+ localmods = True
149+ mod_char = "M"
150+
126151 # Asked for a tag and found that tag
127152 if self .fxtag and atag == self .fxtag :
128- result = f" { self . name :>20 } at tag { self .fxtag } "
153+ result = f" { mod_char } { full_name :<{ full_width } } at tag { self .fxtag } "
129154 recurse = True
130155 testfails = False
131156 # Asked for and found a hash
132157 elif self .fxtag and (ahash [: len (self .fxtag )] == self .fxtag or (self .fxtag .find (ahash )== 0 )):
133- result = f" { self . name :>20 } at hash { ahash } "
158+ result = f" { mod_char } { full_name :<{ full_width } } at hash { ahash } "
134159 recurse = True
135160 testfails = False
136161 # Asked for and found a hash
137162 elif atag == ahash :
138- result = f" { self . name :>20 } at hash { ahash } "
163+ result = f" { mod_char } { full_name :<{ full_width } } at hash { ahash } "
139164 recurse = True
140165 # Did not find requested tag or hash
141166 elif self .fxtag :
142- result = f"s { self . name :>20 } { atag } { ahash } is out of sync with .gitmodules { self .fxtag } "
167+ result = f"s{ mod_char } { full_name :<{ full_width } } { atag } { ahash } is out of sync with .gitmodules { self .fxtag } "
143168 testfails = True
144169 needsupdate = True
145170 else :
146171 if atag :
147- result = f"e { self . name :>20 } has no fxtag defined in .gitmodules, module at { atag } "
172+ result = f"e{ mod_char } { full_name :<{ full_width } } has no fxtag defined in .gitmodules, module at { atag } "
148173 else :
149- result = f"e { self . name :>20 } has no fxtag defined in .gitmodules, module at { ahash } "
174+ result = f"e{ mod_char } { full_name :<{ full_width } } has no fxtag defined in .gitmodules, module at { ahash } "
150175 testfails = False
151176
152- status , output = git .git_operation ("status" , "--ignore-submodules" , "-uno" )
153- if "nothing to commit" not in output :
154- localmods = True
155- result = "M" + textwrap .indent (output , " " )
177+ if localmods and not no_mods_details :
178+ # Print details about the local mods, indented below the other
179+ # information about this submodule.
180+ #
181+ # We use a vertical bar to help with the visual alignment of child
182+ # submodules. There are two main goals of the spacing details here:
183+ # 1. If there is a child of this submodule, the vertical bar used here
184+ # should connect with the vertical part of the tree_prefix.
185+ # 2. The details about any local mods should be indented an additional
186+ # 4 spaces beyond the start of the text like "at tag ..."
187+ #
188+ # Here are details on how we accomplish these goals:
189+ # - leading_spaces: This is key for accomplishing the first goal. We
190+ # need 3 spaces for the first three characters in the output (two
191+ # status characters and a space), plus an additional number of
192+ # spaces matching the number of spaces that would be used in the
193+ # tree_prefix of any *child* of this submodule.
194+ # - total_indent: This is the total indent needed to achieve the
195+ # second goal. The first addition of 4 aligns the output with the
196+ # status (e.g., "at tag ..."), accounting for the 3 leading
197+ # characters before the name and the 1 trailing space after the
198+ # name. The second addition of 4 indents the details about local
199+ # mods an additional 4 spaces.
200+ # - trailing_spaces: This gives the correct total indentation given
201+ # that we already have some leading spaces plus a vertical bar
202+ # character.
203+ leading_spaces = " " * (3 + depth * tree_prefix_spaces )
204+ total_indent = full_width + 4 + 4
205+ trailing_spaces = " " * (total_indent - len (leading_spaces ) - 1 )
206+ result = result + "\n " + textwrap .indent (status_output ,
207+ leading_spaces + "│" + trailing_spaces ,
208+ # The following predicate
209+ # makes the vertical bar
210+ # appear even for blank
211+ # lines:
212+ predicate = lambda _ : True )
213+
156214# print(f"result {result} needsupdate {needsupdate} localmods {localmods} testfails {testfails}")
157215 return result , needsupdate , localmods , testfails
158216
0 commit comments