Skip to content

Commit b3deed6

Browse files
authored
Merge pull request #176 from mvdebolskiy/fix-fates-tag-fleximod
Fix fates tag fleximod
2 parents 9193e61 + 0f32da2 commit b3deed6

22 files changed

Lines changed: 1579 additions & 675 deletions

.gitmodules

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
[submodule "fates"]
2929
path = src/fates
3030
url = https://github.com/NorESMhub/fates
31-
fxtag = sci.1.88.6_api.42.0.0_14pft_nor_sci1_api1
31+
fxtag = sci.1.88.6_api.42.0.0_nor_sci1_api1
3232
fxrequired = AlwaysRequired
3333
# Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed
3434
fxDONOTUSEurl = https://github.com/NorESMhub/fates

.lib/git-fleximod/.github/workflows/pytest.yaml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,7 @@ jobs:
7373
- run: |
7474
git config --global user.name "${GITHUB_ACTOR}"
7575
git config --global user.email "${GITHUB_ACTOR_ID}+${GITHUB_ACTOR}@users.noreply.github.com"
76-
poetry run pytest
77-
- name: Setup tmate session
78-
if: ${{ failure() }}
79-
uses: mxschmitt/action-tmate@v3
80-
76+
poetry run pytest --doctest-modules
77+
# - name: Setup tmate session
78+
# if: ${{ failure() }}
79+
# uses: mxschmitt/action-tmate@v3

.lib/git-fleximod/.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ exclude: ^utils/.*$
22

33
repos:
44
- repo: https://github.com/pre-commit/pre-commit-hooks
5-
rev: v4.0.1
5+
rev: v6.0.0
66
hooks:
77
- id: end-of-file-fixer
88
- id: trailing-whitespace

.lib/git-fleximod/CODE_OF_CONDUCT.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Examples of behaviors that contribute to a positive environment include:
1717
* Communicate openly with respect for others, critiquing ideas rather than individuals and gracefully accepting criticism
1818
* Acknowledging the contributions of others
1919
* Avoid personal attacks directed toward other participants
20-
* Be mindful of your surroundings and of your fellow participants
20+
* Be mindful of your surroundings and of your fellow participants
2121
* Alert UCAR staff and suppliers/vendors if you notice a dangerous situation or someone in distress
2222
* Respect the rules and policies of the project and venue
2323

@@ -29,7 +29,7 @@ Examples of unacceptable behavior include, but are not limited to:
2929
* Personal attacks directed at other guests, members, participants, etc.
3030
* Publishing others' private information, such as a physical or electronic address, without explicit permission
3131
* Alarming, intimidating, threatening, or hostile comments or conduct
32-
* Inappropriate use of nudity and/or sexual images
32+
* Inappropriate use of nudity and/or sexual images
3333
* Threatening or stalking anyone, including a participant
3434
* Other conduct which could reasonably be considered inappropriate in a professional setting
3535

@@ -60,7 +60,7 @@ the [Attribution](#attribution) section.
6060
## Reporting
6161
Instances of unacceptable behavior can be brought to the attention of the project administrator(s) who may take any action as
6262
outlined in the [Consequences](#consequences) section below.
63-
However, making a report to a project administrator is not considered an 'official report' to UCAR.
63+
However, making a report to a project administrator is not considered an 'official report' to UCAR.
6464

6565
Instances of unacceptable behavior may also be reported directly to UCAR pursuant to [UCAR's Harassment Reporting and Complaint
6666
Procedure](https://www2.fin.ucar.edu/procedures/hr/harassment-reporting-and-complaint-procedure), or anonymously through [UCAR's

.lib/git-fleximod/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Git-fleximod is a Python-based tool that extends Git's submodule and sparse chec
2525

2626
## Supported .gitmodules Variables
2727

28-
fxtag: Specify a specific tag or branch to checkout for a submodule.
28+
fxtag: Specify a specific tag or hash to checkout for a submodule. Branches are not acceptable.
2929
fxrequired: Mark a submodule's checkout behavior, with allowed values:
3030
- ToplevelRequired: Top-level and required (checked out only when this is the Toplevel module).
3131
- ToplevelOptional: Top-level and optional (checked out with --optional flag if this is the Toplevel module).

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

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from pathlib import Path
22
import argparse, os, sys
3-
from git_fleximod import utils
43

5-
__version__ = "1.0.2"
4+
__version__ = "1.1.1"
5+
66

77
class CustomArgumentParser(argparse.ArgumentParser):
88
def print_help(self, file=None):
@@ -17,13 +17,28 @@ def print_help(self, file=None):
1717
for path in candidate_paths:
1818
if os.path.exists(path):
1919
with open(path) as f:
20-
print( f.read(), file=file)
20+
print(f.read(), file=file)
2121
return
22-
print( "README.md not found.", file=file)
22+
print("README.md not found.", file=file)
23+
2324

2425
def find_root_dir(filename=".gitmodules"):
25-
""" finds the highest directory in tree
26-
which contains a file called filename """
26+
"""
27+
Finds the highest directory in tree which contains a file called filename.
28+
29+
>>> import tempfile, os
30+
>>> cwd = os.getcwd()
31+
>>> with tempfile.TemporaryDirectory() as tmp:
32+
... subdir = Path(tmp) / 'subdir'
33+
... subdir.mkdir()
34+
... f = Path(tmp) / '.gitmodules'
35+
... _ = f.write_text('')
36+
... os.chdir(subdir)
37+
... result = find_root_dir('.gitmodules') == str(tmp)
38+
... os.chdir(cwd)
39+
... result
40+
True
41+
"""
2742
d = Path.cwd()
2843
root = Path(d.root)
2944
dirlist = []
@@ -40,6 +55,7 @@ def find_root_dir(filename=".gitmodules"):
4055
return str(dl)
4156
return None
4257

58+
4359
def get_parser():
4460
description = """
4561
%(prog)s manages checking out groups of gitsubmodules with additional support for Earth System Models
@@ -116,6 +132,13 @@ def get_parser():
116132
"verbosity level each time.",
117133
)
118134

135+
parser.add_argument(
136+
"--no-mods-details",
137+
action="store_true",
138+
default=False,
139+
help="Suppress details on local mods in status output.",
140+
)
141+
119142
parser.add_argument(
120143
"-V",
121144
"--version",

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

Lines changed: 69 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,27 @@
77

88
import os
99
import shutil
10-
import logging
11-
import textwrap
1210
import asyncio
11+
import logging
12+
from configparser import NoOptionError
1313
from git_fleximod import utils
1414
from git_fleximod import cli
1515
from git_fleximod.gitinterface import GitInterface
1616
from git_fleximod.gitmodules import GitModules
1717
from git_fleximod.submodule import Submodule
1818

19-
# logger variable is global
20-
logger = None
19+
logger = logging.getLogger(__name__)
2120

2221

2322
def fxrequired_allowed_values():
24-
return ["ToplevelRequired", "ToplevelOptional", "AlwaysRequired", "AlwaysOptional", "TopLevelRequired", "TopLevelOptional"]
23+
return [
24+
"ToplevelRequired",
25+
"ToplevelOptional",
26+
"AlwaysRequired",
27+
"AlwaysOptional",
28+
"TopLevelRequired",
29+
"TopLevelOptional",
30+
]
2531

2632

2733
def commandline_arguments(args=None):
@@ -69,6 +75,7 @@ def commandline_arguments(args=None):
6975
options.components,
7076
options.exclude,
7177
options.force,
78+
options.no_mods_details,
7279
action,
7380
)
7481

@@ -151,8 +158,8 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master
151158

152159
if os.path.isdir(os.path.join(root_dir, path, ".git")):
153160
with utils.pushd(sprep_repo):
154-
if os.path.isdir(os.path.join(topgit,".git")):
155-
shutil.rmtree(os.path.join(topgit,".git"))
161+
if os.path.isdir(os.path.join(topgit, ".git")):
162+
shutil.rmtree(os.path.join(topgit, ".git"))
156163
shutil.move(".git", topgit)
157164
with open(".git", "w") as f:
158165
f.write("gitdir: " + os.path.relpath(topgit))
@@ -167,7 +174,6 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master
167174
with utils.pushd(sprep_repo):
168175
if os.path.isfile(sparsefile):
169176
shutil.copy(sparsefile, gitsparse)
170-
171177

172178
# Finally checkout the repo
173179
sprepo_git.git_operation("fetch", "origin", "--tags")
@@ -177,7 +183,8 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master
177183
rgit.config_set_value(f'submodule "{name}"', "active", "true")
178184
rgit.config_set_value(f'submodule "{name}"', "url", url)
179185

180-
def init_submodule_from_gitmodules(gitmodules, name, root_dir, logger):
186+
187+
def init_submodule_from_gitmodules(gitmodules, name, root_dir, llogger):
181188
path = gitmodules.get(name, "path")
182189
url = gitmodules.get(name, "url")
183190
assert path and url, f"Malformed .gitmodules file {path} {url}"
@@ -187,60 +194,73 @@ def init_submodule_from_gitmodules(gitmodules, name, root_dir, logger):
187194
fxurl = gitmodules.get(name, "fxDONOTUSEurl")
188195
fxsparse = gitmodules.get(name, "fxsparse")
189196
fxrequired = gitmodules.get(name, "fxrequired")
190-
return Submodule(root_dir, name, path, url, fxtag=tag, fxurl=fxurl, fxsparse=fxsparse, fxrequired=fxrequired, logger=logger)
197+
return Submodule(
198+
root_dir,
199+
name,
200+
path,
201+
url,
202+
fxtag=tag,
203+
fxurl=fxurl,
204+
fxsparse=fxsparse,
205+
fxrequired=fxrequired,
206+
logger=llogger,
207+
)
191208

192-
def submodules_status(gitmodules, root_dir, toplevel=False, depth=0):
209+
210+
def submodules_status(
211+
gitmodules, root_dir, toplevel=False, depth=0, no_mods_details=False
212+
):
193213
testfails = 0
194214
localmods = 0
195215
needsupdate = 0
196-
wrapper = textwrap.TextWrapper(initial_indent=' '*(depth*10), width=120,subsequent_indent=' '*(depth*20))
197216
for name in gitmodules.sections():
198217
submod = init_submodule_from_gitmodules(gitmodules, name, root_dir, logger)
199-
200-
result,n,l,t = submod.status()
218+
219+
result, n, l, t = submod.status(depth=depth, no_mods_details=no_mods_details)
201220
if toplevel or not submod.toplevel():
202-
print(wrapper.fill(result))
221+
print(result)
203222
testfails += t
204223
localmods += l
205224
needsupdate += n
206225
subdir = os.path.join(root_dir, submod.path)
207226
if os.path.exists(os.path.join(subdir, ".gitmodules")):
208227
gsubmod = GitModules(logger, confpath=subdir)
209-
t,l,n = submodules_status(gsubmod, subdir, depth=depth+1)
228+
t, l, n = submodules_status(
229+
gsubmod, subdir, depth=depth + 1, no_mods_details=no_mods_details
230+
)
210231
if toplevel or not submod.toplevel():
211232
testfails += t
212233
localmods += l
213234
needsupdate += n
214-
235+
215236
return testfails, localmods, needsupdate
216237

217-
def git_toplevelroot(root_dir, logger):
218-
rgit = GitInterface(root_dir, logger)
238+
239+
def git_toplevelroot(root_dir, llogger):
240+
rgit = GitInterface(root_dir, llogger)
219241
_, superroot = rgit.git_operation("rev-parse", "--show-superproject-working-tree")
220242
return superroot
221243

244+
222245
async def submodules_update(gitmodules, root_dir, requiredlist, force):
223246
async def update_submodule(name, requiredlist, force):
224247
submod = init_submodule_from_gitmodules(gitmodules, name, root_dir, logger)
225248

226-
_, needsupdate, localmods, testfails = submod.status()
227249
if not submod.fxrequired:
228250
submod.fxrequired = "AlwaysRequired"
229-
fxrequired = submod.fxrequired
251+
fxrequired = submod.fxrequired
230252
allowedvalues = fxrequired_allowed_values()
231253
assert fxrequired in allowedvalues
232254

233255
superroot = git_toplevelroot(root_dir, logger)
234-
235-
if (
236-
fxrequired
237-
and ((superroot and "Toplevel" in fxrequired)
238-
or fxrequired not in requiredlist)
256+
257+
if fxrequired and (
258+
(superroot and "Toplevel" in fxrequired) or fxrequired not in requiredlist
239259
):
240260
if "Optional" in fxrequired and "Optional" not in requiredlist:
241261
if fxrequired.startswith("Always"):
242262
print(f"Skipping optional component {name:>20}")
243-
return # continue to next submodule
263+
return # continue to next submodule
244264
optional = "AlwaysOptional" in requiredlist
245265

246266
if fxrequired in requiredlist:
@@ -253,11 +273,16 @@ async def update_submodule(name, requiredlist, force):
253273
newrequiredlist = ["AlwaysRequired"]
254274
if optional:
255275
newrequiredlist.append("AlwaysOptional")
256-
await submodules_update(gitsubmodules, repodir, newrequiredlist, force=force)
276+
await submodules_update(
277+
gitsubmodules, repodir, newrequiredlist, force=force
278+
)
257279

258-
tasks = [update_submodule(name, requiredlist, force) for name in gitmodules.sections()]
280+
tasks = [
281+
update_submodule(name, requiredlist, force) for name in gitmodules.sections()
282+
]
259283
await asyncio.gather(*tasks)
260284

285+
261286
def local_mods_output():
262287
text = """\
263288
The submodules labeled with 'M' above are not in a clean state.
@@ -271,7 +296,8 @@ def local_mods_output():
271296
"""
272297
print(text)
273298

274-
def submodules_test(gitmodules, root_dir):
299+
300+
def submodules_test(gitmodules, root_dir, no_mods_details=False):
275301
"""
276302
This function tests the git submodules based on the provided parameters.
277303
@@ -282,12 +308,15 @@ def submodules_test(gitmodules, root_dir):
282308
Parameters:
283309
gitmodules (ConfigParser): The gitmodules configuration.
284310
root_dir (str): The root directory for the git operation.
311+
no_mods_details (bool, optional): If True, suppress details on local mods in status output
285312
286313
Returns:
287314
int: The number of test failures.
288315
"""
289316
# First check that fxtags are present and in sync with submodule hashes
290-
testfails, localmods, needsupdate = submodules_status(gitmodules, root_dir)
317+
testfails, localmods, needsupdate = submodules_status(
318+
gitmodules, root_dir, no_mods_details=no_mods_details
319+
)
291320
print("")
292321
# Then make sure that urls are consistant with fxurls (not forks and not ssh)
293322
# and that sparse checkout files exist
@@ -315,14 +344,17 @@ def main():
315344
includelist,
316345
excludelist,
317346
force,
347+
no_mods_details,
318348
action,
319349
) = commandline_arguments()
320350
# Get a logger for the package
321351
global logger
322352
logger = logging.getLogger(__name__)
323353

324-
logger.info("action is {} root_dir={} file_name={}".format(action, root_dir, file_name))
325-
354+
logger.info(
355+
"action is {} root_dir={} file_name={}".format(action, root_dir, file_name)
356+
)
357+
326358
if not root_dir or not os.path.isfile(os.path.join(root_dir, file_name)):
327359
if root_dir:
328360
file_path = utils.find_upwards(root_dir, file_name)
@@ -352,15 +384,17 @@ def main():
352384
if action == "update":
353385
asyncio.run(submodules_update(gitmodules, root_dir, fxrequired, force))
354386
elif action == "status":
355-
tfails, lmods, updates = submodules_status(gitmodules, root_dir, toplevel=True)
387+
tfails, lmods, updates = submodules_status(
388+
gitmodules, root_dir, toplevel=True, no_mods_details=no_mods_details
389+
)
356390
if tfails + lmods + updates > 0:
357391
print(
358392
f" testfails = {tfails}, local mods = {lmods}, needs updates {updates}\n"
359393
)
360394
if lmods > 0:
361395
local_mods_output()
362396
elif action == "test":
363-
retval = submodules_test(gitmodules, root_dir)
397+
retval = submodules_test(gitmodules, root_dir, no_mods_details=no_mods_details)
364398
else:
365399
utils.fatal_error(f"unrecognized action request {action}")
366400
return retval

0 commit comments

Comments
 (0)