Skip to content

Commit 23f7dd4

Browse files
authored
Merge pull request #3556 from samsrabin/merge-master-20251016
Merge ctsm5.3.080 into b4b-dev
2 parents e1637a7 + a8d4c2c commit 23f7dd4

File tree

18 files changed

+316
-92
lines changed

18 files changed

+316
-92
lines changed

.gitmodules

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ fxDONOTUSEurl = https://github.com/NGEET/fates
3636
[submodule "cism"]
3737
path = components/cism
3838
url = https://github.com/ESCOMP/CISM-wrapper
39-
fxtag = cismwrap_2_2_006
39+
fxtag = cismwrap_2_2_010
4040
fxrequired = ToplevelRequired
4141
# Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed
4242
fxDONOTUSEurl = https://github.com/ESCOMP/CISM-wrapper
@@ -68,31 +68,31 @@ fxDONOTUSEurl = https://github.com/ESCOMP/mizuRoute
6868
[submodule "ccs_config"]
6969
path = ccs_config
7070
url = https://github.com/ESMCI/ccs_config_cesm.git
71-
fxtag = ccs_config_cesm1.0.56
71+
fxtag = ccs_config_cesm1.0.61
7272
fxrequired = ToplevelRequired
7373
# Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed
7474
fxDONOTUSEurl = https://github.com/ESMCI/ccs_config_cesm.git
7575

7676
[submodule "cime"]
7777
path = cime
7878
url = https://github.com/ESMCI/cime
79-
fxtag = cime6.1.113
79+
fxtag = cime6.1.128
8080
fxrequired = ToplevelRequired
8181
# Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed
8282
fxDONOTUSEurl = https://github.com/ESMCI/cime
8383

8484
[submodule "cmeps"]
8585
path = components/cmeps
8686
url = https://github.com/ESCOMP/CMEPS.git
87-
fxtag = cmeps1.1.5
87+
fxtag = cmeps1.1.20
8888
fxrequired = ToplevelRequired
8989
# Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed
9090
fxDONOTUSEurl = https://github.com/ESCOMP/CMEPS.git
9191

9292
[submodule "cdeps"]
9393
path = components/cdeps
9494
url = https://github.com/ESCOMP/CDEPS.git
95-
fxtag = cdeps1.0.79
95+
fxtag = cdeps1.0.81
9696
fxrequired = ToplevelRequired
9797
# Standard Fork to compare to with "git fleximod test" to ensure personal forks aren't committed
9898
fxDONOTUSEurl = https://github.com/ESCOMP/CDEPS.git

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

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,50 @@
11
from pathlib import Path
2-
import argparse
2+
import argparse, os, sys
33
from git_fleximod import utils
44

5-
__version__ = "0.9.3"
5+
__version__ = "1.0.2"
6+
7+
class CustomArgumentParser(argparse.ArgumentParser):
8+
def print_help(self, file=None):
9+
# First print the default help message
10+
super().print_help(file)
11+
12+
# Then append the contents of README.md
13+
candidate_paths = [
14+
Path(sys.prefix) / "share" / "git_fleximod" / "README.md",
15+
Path(__file__).resolve().parent.parent / "README.md", # fallback for dev
16+
]
17+
for path in candidate_paths:
18+
if os.path.exists(path):
19+
with open(path) as f:
20+
print( f.read(), file=file)
21+
return
22+
print( "README.md not found.", file=file)
623

724
def find_root_dir(filename=".gitmodules"):
825
""" finds the highest directory in tree
926
which contains a file called filename """
10-
try:
11-
root = utils.execute_subprocess(["git","rev-parse", "--show-toplevel"],
12-
output_to_caller=True ).rstrip()
13-
except:
14-
d = Path.cwd()
15-
root = Path(d.root)
16-
dirlist = []
17-
dl = d
18-
while dl != root:
19-
dirlist.append(dl)
20-
dl = dl.parent
21-
dirlist.append(root)
22-
dirlist.reverse()
23-
24-
for dl in dirlist:
25-
attempt = dl / filename
26-
if attempt.is_file():
27-
return str(dl)
28-
return None
29-
return Path(root)
27+
d = Path.cwd()
28+
root = Path(d.root)
29+
dirlist = []
30+
dl = d
31+
while dl != root:
32+
dirlist.append(dl)
33+
dl = dl.parent
34+
dirlist.append(root)
35+
dirlist.reverse()
36+
37+
for dl in dirlist:
38+
attempt = dl / filename
39+
if attempt.is_file():
40+
return str(dl)
41+
return None
3042

3143
def get_parser():
3244
description = """
3345
%(prog)s manages checking out groups of gitsubmodules with additional support for Earth System Models
3446
"""
35-
parser = argparse.ArgumentParser(
47+
parser = CustomArgumentParser(
3648
description=description, formatter_class=argparse.RawDescriptionHelpFormatter
3749
)
3850

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

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import shutil
1010
import logging
1111
import textwrap
12+
import asyncio
1213
from git_fleximod import utils
1314
from git_fleximod import cli
1415
from git_fleximod.gitinterface import GitInterface
@@ -181,6 +182,8 @@ def init_submodule_from_gitmodules(gitmodules, name, root_dir, logger):
181182
url = gitmodules.get(name, "url")
182183
assert path and url, f"Malformed .gitmodules file {path} {url}"
183184
tag = gitmodules.get(name, "fxtag")
185+
if not tag:
186+
tag = gitmodules.get(name, "hash")
184187
fxurl = gitmodules.get(name, "fxDONOTUSEurl")
185188
fxsparse = gitmodules.get(name, "fxsparse")
186189
fxrequired = gitmodules.get(name, "fxrequired")
@@ -216,10 +219,10 @@ def git_toplevelroot(root_dir, logger):
216219
_, superroot = rgit.git_operation("rev-parse", "--show-superproject-working-tree")
217220
return superroot
218221

219-
def submodules_update(gitmodules, root_dir, requiredlist, force):
220-
for name in gitmodules.sections():
222+
async def submodules_update(gitmodules, root_dir, requiredlist, force):
223+
async def update_submodule(name, requiredlist, force):
221224
submod = init_submodule_from_gitmodules(gitmodules, name, root_dir, logger)
222-
225+
223226
_, needsupdate, localmods, testfails = submod.status()
224227
if not submod.fxrequired:
225228
submod.fxrequired = "AlwaysRequired"
@@ -237,11 +240,11 @@ def submodules_update(gitmodules, root_dir, requiredlist, force):
237240
if "Optional" in fxrequired and "Optional" not in requiredlist:
238241
if fxrequired.startswith("Always"):
239242
print(f"Skipping optional component {name:>20}")
240-
continue
243+
return # continue to next submodule
241244
optional = "AlwaysOptional" in requiredlist
242245

243246
if fxrequired in requiredlist:
244-
submod.update()
247+
await submod.update()
245248
repodir = os.path.join(root_dir, submod.path)
246249
if os.path.exists(os.path.join(repodir, ".gitmodules")):
247250
# recursively handle this checkout
@@ -250,8 +253,10 @@ def submodules_update(gitmodules, root_dir, requiredlist, force):
250253
newrequiredlist = ["AlwaysRequired"]
251254
if optional:
252255
newrequiredlist.append("AlwaysOptional")
256+
await submodules_update(gitsubmodules, repodir, newrequiredlist, force=force)
253257

254-
submodules_update(gitsubmodules, repodir, newrequiredlist, force=force)
258+
tasks = [update_submodule(name, requiredlist, force) for name in gitmodules.sections()]
259+
await asyncio.gather(*tasks)
255260

256261
def local_mods_output():
257262
text = """\
@@ -345,7 +350,7 @@ def main():
345350
sys.exit(f"No submodule components found, root_dir={root_dir}")
346351
retval = 0
347352
if action == "update":
348-
submodules_update(gitmodules, root_dir, fxrequired, force)
353+
asyncio.run(submodules_update(gitmodules, root_dir, fxrequired, force))
349354
elif action == "status":
350355
tfails, lmods, updates = submodules_status(gitmodules, root_dir, toplevel=True)
351356
if tfails + lmods + updates > 0:

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import sys
33
from . import utils
44
from pathlib import Path
5+
import asyncio
56

67
class GitInterface:
78
def __init__(self, repo_path, logger):
@@ -47,6 +48,16 @@ def _init_git_repo(self):
4748
command = ("git", "-C", str(self.repo_path), "init")
4849
utils.execute_subprocess(command)
4950

51+
def _git_operation_command(self, operation, args):
52+
newargs = []
53+
for a in args:
54+
# Do not use ssh interface
55+
if isinstance(a, str):
56+
a = a.replace("[email protected]:", "https://github.com/")
57+
newargs.append(a)
58+
59+
return self._git_command(operation, *newargs)
60+
5061
# pylint: disable=unused-argument
5162
def git_operation(self, operation, *args, **kwargs):
5263
newargs = []
@@ -66,6 +77,25 @@ def git_operation(self, operation, *args, **kwargs):
6677
else:
6778
return 0, command
6879

80+
# pylint: disable=unused-argument
81+
async def git_operation_async(self, operation, *args, **kwargs):
82+
command = self._git_operation_command(operation, args)
83+
if isinstance(command, list):
84+
try:
85+
process = await asyncio.create_subprocess_exec(
86+
*command,
87+
stdout=asyncio.subprocess.PIPE,
88+
stderr=asyncio.subprocess.PIPE
89+
)
90+
stdout, stderr = await process.communicate()
91+
status = process.returncode
92+
output = stdout.decode().strip() if stdout else stderr.decode().strip()
93+
return status, output
94+
except Exception as e:
95+
sys.exit(e)
96+
else:
97+
return 0, command
98+
6999
def config_get_value(self, section, name):
70100
if self._use_module:
71101
config = self.repo.config_reader()

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

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -119,27 +119,25 @@ def status(self):
119119
atag = atag[:-1]
120120
if atag == self.fxtag:
121121
break
122-
123-
124-
#print(f"line is {line} ahash is {ahash} atag is {atag} {parts}")
125-
# atag = git.git_operation("describe", "--tags", "--always")
126-
# ahash = git.git_operation("rev-list", "HEAD").partition("\n")[0]
127-
128122
recurse = False
129123
if rurl != self.url:
130124
remote = self._add_remote(git)
131125
git.git_operation("fetch", remote)
126+
# Asked for a tag and found that tag
132127
if self.fxtag and atag == self.fxtag:
133128
result = f" {self.name:>20} at tag {self.fxtag}"
134129
recurse = True
135130
testfails = False
131+
# Asked for and found a hash
136132
elif self.fxtag and (ahash[: len(self.fxtag)] == self.fxtag or (self.fxtag.find(ahash)==0)):
137133
result = f" {self.name:>20} at hash {ahash}"
138134
recurse = True
139135
testfails = False
136+
# Asked for and found a hash
140137
elif atag == ahash:
141138
result = f" {self.name:>20} at hash {ahash}"
142139
recurse = True
140+
# Did not find requested tag or hash
143141
elif self.fxtag:
144142
result = f"s {self.name:>20} {atag} {ahash} is out of sync with .gitmodules {self.fxtag}"
145143
testfails = True
@@ -284,17 +282,18 @@ def sparse_checkout(self):
284282
if not os.path.isdir(infodir):
285283
os.makedirs(infodir)
286284
gitsparse = os.path.abspath(os.path.join(infodir, "sparse-checkout"))
287-
if os.path.isfile(gitsparse):
288-
self.logger.warning(
289-
"submodule {} is already initialized {}".format(self.name, rootdotgit)
290-
)
291-
return
292-
293-
with utils.pushd(sprep_repo):
285+
if os.path.isfile(gitsparse):
286+
self.logger.warning(
287+
"submodule {} is already initialized {}".format(self.name, rootdotgit)
288+
)
289+
os.remove(gitsparse)
290+
294291
if os.path.isfile(self.fxsparse):
295-
296292
shutil.copy(self.fxsparse, gitsparse)
297-
293+
else:
294+
self.logger.warning(
295+
"submodule {} could not find {}".format(self.name, self.fxsparse)
296+
)
298297

299298
# Finally checkout the repo
300299
sprepo_git.git_operation("fetch", "origin", "--tags")
@@ -303,11 +302,18 @@ def sparse_checkout(self):
303302
print(f"Error checking out {self.name:>20} at {self.fxtag}")
304303
else:
305304
print(f"Successfully checked out {self.name:>20} at {self.fxtag}")
305+
status,f = sprepo_git.git_operation("status")
306+
# Restore any files deleted from sandbox
307+
for line in f.splitlines():
308+
if "deleted:" in line:
309+
deleted_file = line.split("deleted:")[1].strip()
310+
sprepo_git.git_operation("checkout", deleted_file)
311+
306312
rgit.config_set_value('submodule.' + self.name, "active", "true")
307313
rgit.config_set_value('submodule.' + self.name, "url", self.url)
308314
rgit.config_set_value('submodule.' + self.name, "path", self.path)
309315

310-
def update(self):
316+
async def update(self):
311317
"""
312318
Updates the submodule to the latest or specified version.
313319
@@ -341,6 +347,9 @@ def update(self):
341347
# Look for a .gitmodules file in the newly checkedout repo
342348
if self.fxsparse:
343349
print(f"Sparse checkout {self.name} fxsparse {self.fxsparse}")
350+
if not os.path.isfile(self.fxsparse):
351+
self.logger.info("Submodule {} fxsparse file not found".format(self.name))
352+
344353
self.sparse_checkout()
345354
else:
346355
if not repo_exists and self.url:
@@ -378,19 +387,26 @@ def update(self):
378387
git.git_operation("submodule", "add", "--name", self.name, "--", self.url, self.path)
379388

380389
if not repo_exists:
381-
git.git_operation("submodule", "update", "--init", "--", self.path)
390+
git.git_operation("submodule", "init", "--", self.path)
391+
await git.git_operation_async("submodule", "update", "--", self.path)
382392

383393
if self.fxtag:
384394
smgit = GitInterface(repodir, self.logger)
385395
newremote = self._add_remote(smgit)
386396
# Trying to distingush a tag from a hash
387-
allowed = set(string.digits + 'abcdef')
397+
allowed = set(string.digits + 'abcdef')
398+
status = 0
388399
if not set(self.fxtag) <= allowed:
389400
# This is a tag
390401
tag = f"refs/tags/{self.fxtag}:refs/tags/{self.fxtag}"
391-
smgit.git_operation("fetch", newremote, tag)
392-
smgit.git_operation("checkout", self.fxtag)
393-
402+
status,_ = smgit.git_operation("fetch", newremote, tag)
403+
if status == 0:
404+
status,_ = smgit.git_operation("checkout", self.fxtag)
405+
if status:
406+
utils.fatal_error(
407+
f"Failed to checkout {self.name} at tag or hash {self.fxtag} from {repodir}"
408+
)
409+
394410
if not os.path.exists(os.path.join(repodir, ".git")):
395411
utils.fatal_error(
396412
f"Failed to checkout {self.name} {repo_exists} {repodir} {self.path}"
@@ -408,6 +424,18 @@ def update(self):
408424
if fxtag and fxtag not in tags:
409425
git.git_operation("fetch", newremote, "--tags")
410426
status, atag = git.git_operation("describe", "--tags", "--always")
427+
status, files = git.git_operation("diff", "--name-only", "-z")
428+
modfiles = []
429+
moddirs = []
430+
if files:
431+
for f in files.split('\0'):
432+
if f:
433+
if os.path.exists(f):
434+
git.git_operation("checkout",f)
435+
elif os.path.isdir(f):
436+
moddirs.append(f)
437+
else:
438+
modfiles.append(f)
411439
if fxtag and fxtag != atag:
412440
try:
413441
status, _ = git.git_operation("checkout", fxtag)
@@ -419,6 +447,10 @@ def update(self):
419447

420448
elif not fxtag:
421449
print(f"No fxtag found for submodule {self.name:>20}")
450+
elif modfiles:
451+
print(f"{self.name:>20} has modified files: {modfiles}")
452+
elif moddirs:
453+
print(f"{self.name:>20} has modified directories: {moddirs}")
422454
else:
423455
print(f"{self.name:>20} up to date.")
424456

0 commit comments

Comments
 (0)