Skip to content

Commit d422310

Browse files
committed
Merge commit '95f80118ca994631bc6e260ac3285ceafef57cf8' into cesm3.0-alphabranch
2 parents bcee806 + 95f8011 commit d422310

File tree

10 files changed

+168
-79
lines changed

10 files changed

+168
-79
lines changed

.lib/git-fleximod/git_fleximod/cli.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import argparse, os, sys
33
from git_fleximod import utils
44

5-
__version__ = "1.0.3"
5+
__version__ = "1.1.0"
66

77
class CustomArgumentParser(argparse.ArgumentParser):
88
def print_help(self, file=None):
@@ -116,6 +116,13 @@ def get_parser():
116116
"verbosity level each time.",
117117
)
118118

119+
parser.add_argument(
120+
"--no-mods-details",
121+
action="store_true",
122+
default=False,
123+
help="Suppress details on local mods in status output."
124+
)
125+
119126
parser.add_argument(
120127
"-V",
121128
"--version",

.lib/git-fleximod/git_fleximod/git_fleximod.py

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def commandline_arguments(args=None):
6969
options.components,
7070
options.exclude,
7171
options.force,
72+
options.no_mods_details,
7273
action,
7374
)
7475

@@ -189,29 +190,30 @@ def init_submodule_from_gitmodules(gitmodules, name, root_dir, logger):
189190
fxrequired = gitmodules.get(name, "fxrequired")
190191
return Submodule(root_dir, name, path, url, fxtag=tag, fxurl=fxurl, fxsparse=fxsparse, fxrequired=fxrequired, logger=logger)
191192

192-
def submodules_status(gitmodules, root_dir, toplevel=False, depth=0):
193+
def submodules_status(gitmodules, root_dir, toplevel=False, depth=0, no_mods_details=False):
193194
testfails = 0
194195
localmods = 0
195196
needsupdate = 0
196-
wrapper = textwrap.TextWrapper(initial_indent=' '*(depth*10), width=120,subsequent_indent=' '*(depth*20))
197197
for name in gitmodules.sections():
198198
submod = init_submodule_from_gitmodules(gitmodules, name, root_dir, logger)
199-
200-
result,n,l,t = submod.status()
199+
200+
result,n,l,t = submod.status(depth=depth, no_mods_details=no_mods_details)
201201
if toplevel or not submod.toplevel():
202-
print(wrapper.fill(result))
202+
print(result)
203203
testfails += t
204204
localmods += l
205205
needsupdate += n
206206
subdir = os.path.join(root_dir, submod.path)
207207
if os.path.exists(os.path.join(subdir, ".gitmodules")):
208208
gsubmod = GitModules(logger, confpath=subdir)
209-
t,l,n = submodules_status(gsubmod, subdir, depth=depth+1)
209+
t, l, n = submodules_status(
210+
gsubmod, subdir, depth=depth + 1, no_mods_details=no_mods_details
211+
)
210212
if toplevel or not submod.toplevel():
211213
testfails += t
212214
localmods += l
213215
needsupdate += n
214-
216+
215217
return testfails, localmods, needsupdate
216218

217219
def git_toplevelroot(root_dir, logger):
@@ -271,7 +273,7 @@ def local_mods_output():
271273
"""
272274
print(text)
273275

274-
def submodules_test(gitmodules, root_dir):
276+
def submodules_test(gitmodules, root_dir, no_mods_details=False):
275277
"""
276278
This function tests the git submodules based on the provided parameters.
277279
@@ -282,12 +284,15 @@ def submodules_test(gitmodules, root_dir):
282284
Parameters:
283285
gitmodules (ConfigParser): The gitmodules configuration.
284286
root_dir (str): The root directory for the git operation.
287+
no_mods_details (bool, optional): If True, suppress details on local mods in status output
285288
286289
Returns:
287290
int: The number of test failures.
288291
"""
289292
# First check that fxtags are present and in sync with submodule hashes
290-
testfails, localmods, needsupdate = submodules_status(gitmodules, root_dir)
293+
testfails, localmods, needsupdate = submodules_status(
294+
gitmodules, root_dir, no_mods_details=no_mods_details
295+
)
291296
print("")
292297
# Then make sure that urls are consistant with fxurls (not forks and not ssh)
293298
# and that sparse checkout files exist
@@ -315,14 +320,15 @@ def main():
315320
includelist,
316321
excludelist,
317322
force,
323+
no_mods_details,
318324
action,
319325
) = commandline_arguments()
320326
# Get a logger for the package
321327
global logger
322328
logger = logging.getLogger(__name__)
323329

324330
logger.info("action is {} root_dir={} file_name={}".format(action, root_dir, file_name))
325-
331+
326332
if not root_dir or not os.path.isfile(os.path.join(root_dir, file_name)):
327333
if root_dir:
328334
file_path = utils.find_upwards(root_dir, file_name)
@@ -352,15 +358,17 @@ def main():
352358
if action == "update":
353359
asyncio.run(submodules_update(gitmodules, root_dir, fxrequired, force))
354360
elif action == "status":
355-
tfails, lmods, updates = submodules_status(gitmodules, root_dir, toplevel=True)
361+
tfails, lmods, updates = submodules_status(
362+
gitmodules, root_dir, toplevel=True, no_mods_details=no_mods_details
363+
)
356364
if tfails + lmods + updates > 0:
357365
print(
358366
f" testfails = {tfails}, local mods = {lmods}, needs updates {updates}\n"
359367
)
360368
if lmods > 0:
361369
local_mods_output()
362370
elif action == "test":
363-
retval = submodules_test(gitmodules, root_dir)
371+
retval = submodules_test(gitmodules, root_dir, no_mods_details=no_mods_details)
364372
else:
365373
utils.fatal_error(f"unrecognized action request {action}")
366374
return retval

.lib/git-fleximod/git_fleximod/submodule.py

Lines changed: 74 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -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

.lib/git-fleximod/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "git-fleximod"
3-
version = "1.0.3"
3+
version = "1.1.0"
44
description = "Extended support for git-submodule and git-sparse-checkout"
55
authors = ["Jim Edwards <[email protected]>"]
66
maintainers = ["Jim Edwards <[email protected]>"]

.lib/git-fleximod/tbump.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
github_url = "https://github.com/jedwards4b/git-fleximod/"
33

44
[version]
5-
current = "1.0.3"
5+
current = "1.1.0"
66

77
# Example of a semver regexp.
88
# Make sure this matches current_version before
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
22
from pathlib import Path
3-
3+
from tests.utils_for_tests import normalize_whitespace
4+
45
def test_basic_checkout(git_fleximod, test_repo, shared_repos):
56
# Prepare a simple .gitmodules
67
gm = shared_repos['gitmodules_content']
@@ -9,7 +10,7 @@ def test_basic_checkout(git_fleximod, test_repo, shared_repos):
910
repo_path = shared_repos["subrepo_path"]
1011

1112
file_path.write_text(gm)
12-
13+
1314
# Run the command
1415
result = git_fleximod(test_repo, f"update {repo_name}")
1516

@@ -19,8 +20,7 @@ def test_basic_checkout(git_fleximod, test_repo, shared_repos):
1920
if "sparse" in repo_name:
2021
assert Path(test_repo / f"{repo_path}/m4").exists() # Did the submodule sparse directory get created?
2122
assert not Path(test_repo / f"{repo_path}/README").exists() # Did only the submodule sparse directory get created?
22-
23+
2324
status = git_fleximod(test_repo, f"status {repo_name}")
24-
25-
assert shared_repos["status2"] in status.stdout
26-
25+
26+
assert shared_repos["status2"] in normalize_whitespace(status.stdout)

.lib/git-fleximod/tests/test_c_required.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import pytest
22
import re
33
from pathlib import Path
4-
4+
from tests.utils_for_tests import normalize_whitespace
5+
56
def test_required(git_fleximod, test_repo, shared_repos):
67
file_path = (test_repo / ".gitmodules")
78
gm = shared_repos["gitmodules_content"]
@@ -20,15 +21,15 @@ def test_required(git_fleximod, test_repo, shared_repos):
2021
result = git_fleximod(test_repo, "update")
2122
assert result.returncode == 0
2223
status = git_fleximod(test_repo, f"status {repo_name}")
23-
assert shared_repos["status3"] in status.stdout
24+
assert shared_repos["status3"] in normalize_whitespace(status.stdout)
2425
status = git_fleximod(test_repo, f"update --optional")
2526
assert result.returncode == 0
2627
status = git_fleximod(test_repo, f"status {repo_name}")
27-
assert shared_repos["status4"] in status.stdout
28+
assert shared_repos["status4"] in normalize_whitespace(status.stdout)
2829
status = git_fleximod(test_repo, f"update {repo_name}")
2930
assert result.returncode == 0
3031
status = git_fleximod(test_repo, f"status {repo_name}")
31-
assert shared_repos["status4"] in status.stdout
32+
assert shared_repos["status4"] in normalize_whitespace(status.stdout)
3233

3334
text = file_path.read_text()
3435
new_value = "somethingelse"

0 commit comments

Comments
 (0)