Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .lib/git-fleximod/git_fleximod/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import argparse, os, sys
from git_fleximod import utils

__version__ = "1.0.3"
__version__ = "1.1.0"

class CustomArgumentParser(argparse.ArgumentParser):
def print_help(self, file=None):
Expand Down Expand Up @@ -116,6 +116,13 @@ def get_parser():
"verbosity level each time.",
)

parser.add_argument(
"--no-mods-details",
action="store_true",
default=False,
help="Suppress details on local mods in status output."
)

parser.add_argument(
"-V",
"--version",
Expand Down
32 changes: 20 additions & 12 deletions .lib/git-fleximod/git_fleximod/git_fleximod.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def commandline_arguments(args=None):
options.components,
options.exclude,
options.force,
options.no_mods_details,
action,
)

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

def submodules_status(gitmodules, root_dir, toplevel=False, depth=0):
def submodules_status(gitmodules, root_dir, toplevel=False, depth=0, no_mods_details=False):
testfails = 0
localmods = 0
needsupdate = 0
wrapper = textwrap.TextWrapper(initial_indent=' '*(depth*10), width=120,subsequent_indent=' '*(depth*20))
for name in gitmodules.sections():
submod = init_submodule_from_gitmodules(gitmodules, name, root_dir, logger)
result,n,l,t = submod.status()

result,n,l,t = submod.status(depth=depth, no_mods_details=no_mods_details)
if toplevel or not submod.toplevel():
print(wrapper.fill(result))
print(result)
testfails += t
localmods += l
needsupdate += n
subdir = os.path.join(root_dir, submod.path)
if os.path.exists(os.path.join(subdir, ".gitmodules")):
gsubmod = GitModules(logger, confpath=subdir)
t,l,n = submodules_status(gsubmod, subdir, depth=depth+1)
t, l, n = submodules_status(
gsubmod, subdir, depth=depth + 1, no_mods_details=no_mods_details
)
if toplevel or not submod.toplevel():
testfails += t
localmods += l
needsupdate += n

return testfails, localmods, needsupdate

def git_toplevelroot(root_dir, logger):
Expand Down Expand Up @@ -271,7 +273,7 @@ def local_mods_output():
"""
print(text)

def submodules_test(gitmodules, root_dir):
def submodules_test(gitmodules, root_dir, no_mods_details=False):
"""
This function tests the git submodules based on the provided parameters.

Expand All @@ -282,12 +284,15 @@ def submodules_test(gitmodules, root_dir):
Parameters:
gitmodules (ConfigParser): The gitmodules configuration.
root_dir (str): The root directory for the git operation.
no_mods_details (bool, optional): If True, suppress details on local mods in status output

Returns:
int: The number of test failures.
"""
# First check that fxtags are present and in sync with submodule hashes
testfails, localmods, needsupdate = submodules_status(gitmodules, root_dir)
testfails, localmods, needsupdate = submodules_status(
gitmodules, root_dir, no_mods_details=no_mods_details
)
print("")
# Then make sure that urls are consistant with fxurls (not forks and not ssh)
# and that sparse checkout files exist
Expand Down Expand Up @@ -315,14 +320,15 @@ def main():
includelist,
excludelist,
force,
no_mods_details,
action,
) = commandline_arguments()
# Get a logger for the package
global logger
logger = logging.getLogger(__name__)

logger.info("action is {} root_dir={} file_name={}".format(action, root_dir, file_name))

if not root_dir or not os.path.isfile(os.path.join(root_dir, file_name)):
if root_dir:
file_path = utils.find_upwards(root_dir, file_name)
Expand Down Expand Up @@ -352,15 +358,17 @@ def main():
if action == "update":
asyncio.run(submodules_update(gitmodules, root_dir, fxrequired, force))
elif action == "status":
tfails, lmods, updates = submodules_status(gitmodules, root_dir, toplevel=True)
tfails, lmods, updates = submodules_status(
gitmodules, root_dir, toplevel=True, no_mods_details=no_mods_details
)
if tfails + lmods + updates > 0:
print(
f" testfails = {tfails}, local mods = {lmods}, needs updates {updates}\n"
)
if lmods > 0:
local_mods_output()
elif action == "test":
retval = submodules_test(gitmodules, root_dir)
retval = submodules_test(gitmodules, root_dir, no_mods_details=no_mods_details)
else:
utils.fatal_error(f"unrecognized action request {action}")
return retval
Expand Down
90 changes: 74 additions & 16 deletions .lib/git-fleximod/git_fleximod/submodule.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,38 @@ def __init__(self, root_dir, name, path, url, fxtag=None, fxurl=None, fxsparse=N
self.fxrequired = "AlwaysRequired"
self.logger = logger

def status(self):
def status(self, depth=0, no_mods_details=False):
"""
Checks the status of the submodule and returns 4 parameters:
- result (str): The status of the submodule.
- needsupdate (bool): An indicator if the submodule needs to be updated.
- localmods (bool): An indicator if the submodule has local modifications.
- testfails (bool): An indicator if the submodule has failed a test, this is used for testing purposes.

Args:
depth (int, optional): depth of this submodule relative to root, used to
indent output for nested submodules
no_mods_details (bool, optional): if True, suppress details on local mods in
status output
"""

smpath = os.path.join(self.root_dir, self.path)
testfails = False
localmods = False
needsupdate = False
ahash = None

# The following prefix gives a tree-like output:
tree_prefix_spaces = 3
if depth == 0:
tree_prefix = ""
else:
tree_prefix = " " * tree_prefix_spaces * (depth - 1) + "└─ "

full_name = tree_prefix + self.name
name_width = 20
full_width = name_width + len(tree_prefix)

optional = ""
if "Optional" in self.fxrequired:
optional = " (optional)"
Expand All @@ -78,28 +96,28 @@ def status(self):
if hhash and atag:
break
if self.fxtag and (ahash == hhash or atag == self.fxtag):
result = f"e {self.name:>20} not checked out, aligned at tag {self.fxtag}{optional}"
result = f"e {full_name:<{full_width}} not checked out, aligned at tag {self.fxtag}{optional}"
needsupdate = True
elif self.fxtag:
status, ahash = rootgit.git_operation(
"submodule", "status", "{}".format(self.path)
)
ahash = ahash[1 : len(self.fxtag) + 1]
if self.fxtag == ahash:
result = f"e {self.name:>20} not checked out, aligned at hash {ahash}{optional}"
result = f"e {full_name:<{full_width}} not checked out, aligned at hash {ahash}{optional}"
else:
result = f"e {self.name:>20} not checked out, out of sync at tag {atag}, expected tag is {self.fxtag}{optional}"
result = f"e {full_name:<{full_width}} not checked out, out of sync at tag {atag}, expected tag is {self.fxtag}{optional}"
testfails = True
needsupdate = True
else:
result = f"e {self.name:>20} has no fxtag defined in .gitmodules{optional}"
result = f"e {full_name:<{full_width}} has no fxtag defined in .gitmodules{optional}"
testfails = False
else:
with utils.pushd(smpath):
git = GitInterface(smpath, self.logger)
status, remote = git.git_operation("remote")
if remote == '':
result = f"e {self.name:>20} has no associated remote"
result = f"e {full_name:<{full_width}} has no associated remote"
testfails = True
needsupdate = True
return result, needsupdate, localmods, testfails
Expand All @@ -123,36 +141,76 @@ def status(self):
if rurl != self.url:
remote = self._add_remote(git)
git.git_operation("fetch", remote)

mod_char = " "
_, status_output = git.git_operation("status", "--ignore-submodules", "-uno")
if "nothing to commit" not in status_output:
localmods = True
mod_char = "M"

# Asked for a tag and found that tag
if self.fxtag and atag == self.fxtag:
result = f" {self.name:>20} at tag {self.fxtag}"
result = f" {mod_char} {full_name:<{full_width}} at tag {self.fxtag}"
recurse = True
testfails = False
# Asked for and found a hash
elif self.fxtag and (ahash[: len(self.fxtag)] == self.fxtag or (self.fxtag.find(ahash)==0)):
result = f" {self.name:>20} at hash {ahash}"
result = f" {mod_char} {full_name:<{full_width}} at hash {ahash}"
recurse = True
testfails = False
# Asked for and found a hash
elif atag == ahash:
result = f" {self.name:>20} at hash {ahash}"
result = f" {mod_char} {full_name:<{full_width}} at hash {ahash}"
recurse = True
# Did not find requested tag or hash
elif self.fxtag:
result = f"s {self.name:>20} {atag} {ahash} is out of sync with .gitmodules {self.fxtag}"
result = f"s{mod_char} {full_name:<{full_width}} {atag} {ahash} is out of sync with .gitmodules {self.fxtag}"
testfails = True
needsupdate = True
else:
if atag:
result = f"e {self.name:>20} has no fxtag defined in .gitmodules, module at {atag}"
result = f"e{mod_char} {full_name:<{full_width}} has no fxtag defined in .gitmodules, module at {atag}"
else:
result = f"e {self.name:>20} has no fxtag defined in .gitmodules, module at {ahash}"
result = f"e{mod_char} {full_name:<{full_width}} has no fxtag defined in .gitmodules, module at {ahash}"
testfails = False

status, output = git.git_operation("status", "--ignore-submodules", "-uno")
if "nothing to commit" not in output:
localmods = True
result = "M" + textwrap.indent(output, " ")
if localmods and not no_mods_details:
# Print details about the local mods, indented below the other
# information about this submodule.
#
# We use a vertical bar to help with the visual alignment of child
# submodules. There are two main goals of the spacing details here:
# 1. If there is a child of this submodule, the vertical bar used here
# should connect with the vertical part of the tree_prefix.
# 2. The details about any local mods should be indented an additional
# 4 spaces beyond the start of the text like "at tag ..."
#
# Here are details on how we accomplish these goals:
# - leading_spaces: This is key for accomplishing the first goal. We
# need 3 spaces for the first three characters in the output (two
# status characters and a space), plus an additional number of
# spaces matching the number of spaces that would be used in the
# tree_prefix of any *child* of this submodule.
# - total_indent: This is the total indent needed to achieve the
# second goal. The first addition of 4 aligns the output with the
# status (e.g., "at tag ..."), accounting for the 3 leading
# characters before the name and the 1 trailing space after the
# name. The second addition of 4 indents the details about local
# mods an additional 4 spaces.
# - trailing_spaces: This gives the correct total indentation given
# that we already have some leading spaces plus a vertical bar
# character.
leading_spaces = " " * (3 + depth * tree_prefix_spaces)
total_indent = full_width + 4 + 4
trailing_spaces = " " * (total_indent - len(leading_spaces) - 1)
result = result + "\n" + textwrap.indent(status_output,
leading_spaces + "│" + trailing_spaces,
# The following predicate
# makes the vertical bar
# appear even for blank
# lines:
predicate = lambda _: True)

# print(f"result {result} needsupdate {needsupdate} localmods {localmods} testfails {testfails}")
return result, needsupdate, localmods, testfails

Expand Down
2 changes: 1 addition & 1 deletion .lib/git-fleximod/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "git-fleximod"
version = "1.0.3"
version = "1.1.0"
description = "Extended support for git-submodule and git-sparse-checkout"
authors = ["Jim Edwards <[email protected]>"]
maintainers = ["Jim Edwards <[email protected]>"]
Expand Down
2 changes: 1 addition & 1 deletion .lib/git-fleximod/tbump.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
github_url = "https://github.com/jedwards4b/git-fleximod/"

[version]
current = "1.0.3"
current = "1.1.0"

# Example of a semver regexp.
# Make sure this matches current_version before
Expand Down
12 changes: 6 additions & 6 deletions .lib/git-fleximod/tests/test_b_update.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pytest
from pathlib import Path

from tests.utils_for_tests import normalize_whitespace

def test_basic_checkout(git_fleximod, test_repo, shared_repos):
# Prepare a simple .gitmodules
gm = shared_repos['gitmodules_content']
Expand All @@ -9,7 +10,7 @@ def test_basic_checkout(git_fleximod, test_repo, shared_repos):
repo_path = shared_repos["subrepo_path"]

file_path.write_text(gm)

# Run the command
result = git_fleximod(test_repo, f"update {repo_name}")

Expand All @@ -19,8 +20,7 @@ def test_basic_checkout(git_fleximod, test_repo, shared_repos):
if "sparse" in repo_name:
assert Path(test_repo / f"{repo_path}/m4").exists() # Did the submodule sparse directory get created?
assert not Path(test_repo / f"{repo_path}/README").exists() # Did only the submodule sparse directory get created?

status = git_fleximod(test_repo, f"status {repo_name}")

assert shared_repos["status2"] in status.stdout


assert shared_repos["status2"] in normalize_whitespace(status.stdout)
9 changes: 5 additions & 4 deletions .lib/git-fleximod/tests/test_c_required.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import pytest
import re
from pathlib import Path

from tests.utils_for_tests import normalize_whitespace

def test_required(git_fleximod, test_repo, shared_repos):
file_path = (test_repo / ".gitmodules")
gm = shared_repos["gitmodules_content"]
Expand All @@ -20,15 +21,15 @@ def test_required(git_fleximod, test_repo, shared_repos):
result = git_fleximod(test_repo, "update")
assert result.returncode == 0
status = git_fleximod(test_repo, f"status {repo_name}")
assert shared_repos["status3"] in status.stdout
assert shared_repos["status3"] in normalize_whitespace(status.stdout)
status = git_fleximod(test_repo, f"update --optional")
assert result.returncode == 0
status = git_fleximod(test_repo, f"status {repo_name}")
assert shared_repos["status4"] in status.stdout
assert shared_repos["status4"] in normalize_whitespace(status.stdout)
status = git_fleximod(test_repo, f"update {repo_name}")
assert result.returncode == 0
status = git_fleximod(test_repo, f"status {repo_name}")
assert shared_repos["status4"] in status.stdout
assert shared_repos["status4"] in normalize_whitespace(status.stdout)

text = file_path.read_text()
new_value = "somethingelse"
Expand Down
Loading