From f093a6ab2e0b101d01f8e2fdb51b94432fa69c68 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Tue, 2 Jan 2024 10:57:43 -0700 Subject: [PATCH 001/159] start with basics --- git_setup | 91 +++++++++++++++++++++++++++++++++++++++++++++ git_sparse_checkout | 69 ++++++++++++++++++++++++++++++++++ show-tags | 20 ++++++++++ 3 files changed, 180 insertions(+) create mode 100755 git_setup create mode 100755 git_sparse_checkout create mode 100755 show-tags diff --git a/git_setup b/git_setup new file mode 100755 index 00000000..52901897 --- /dev/null +++ b/git_setup @@ -0,0 +1,91 @@ +#!/bin/bash +# Script to fetch submodules for CAM +# if argument -internal-only is supplied then only checkout CAM internal submodules, otherwise checkout everything needed for +# cam as a top level repository +# +set -e +script="install" +function usage { + echo -e "\nusage: $script [-i/--internal-only]\n" +} +# Set default arguments +internal_only=0 + +# Process arguments +while [ "$1" != "" ]; +do + case $1 in + + # Only checkout CISM internal submodules + -i | -internal-only | --internal-only) + internal_only=1 + ;; + + *) + echo "$script: illegal option $1" + usage + exit 1 + ;; + esac + shift +done + +######################## + +# Start with sparse checkouts +if ! test -f bash-scripts/bin/git_sparse_checkout; then + echo "Getting git_sparse_checkout script" + git clone -n https://github.com/frgomes/bash-scripts --depth 1 + pushd bash-scripts 1>/dev/null + git checkout HEAD -- bin/git_sparse_checkout + popd 1>/dev/null +fi + + + + + + +./bash-scripts/bin/git_sparse_checkout https://github.com/larson-group/clubb_release clubb_4ncar_20221129_59cb19f_branch src/physics/clubb -- src/CLUBB_core/ src/SILHS/ +git restore src/physics/clubb + +./bash-scripts/bin/git_sparse_checkout https://github.com/CFMIP/COSPv2.0 master src/physics/cosp2/src -- src/ +git restore src/physics/cosp2/src + +./bash-scripts/bin/git_sparse_checkout https://github.com/MPAS-Dev/MPAS-Model.git develop src/dynamics/mpas/dycore -- src/external/ src/operators/ src/tools/ \ + src/core_atmosphere/ src/framework/ +git restore src/dynamics/mpas/dycore + +submodules=('chem_proc' 'src/physics/carma/base' 'src/physics/pumas' 'src/physics/pumas-frozen' 'src/physics/ali_arms' 'src/atmos_phys' 'src/dynamics/fv3/atmos_cubed_sphere' 'src/hemco') +for mod in "${submodules[@]}" +do + echo "Initializing $mod" + git submodule update --init $mod +done + +if [ ${internal_only} -eq 1 ] + then + exit 0 +fi + + +submodules=('ccs_config' 'components/cice5' 'components/cice' 'components/cmeps' \ + 'components/cdeps' 'components/cpl7' 'share' 'libraries/mct' \ + 'libraries/parallelio' 'cime' 'libraries/FMS' 'components/mosart' \ + 'components/rtm') + +for mod in "${submodules[@]}" +do + echo "Initializing $mod" + git submodule update --init --recursive $mod +done + +sparse_submodules=('components/clm' 'components/cism') +for mod in "${sparse_submodules[@]}" +do + echo "Initializing $mod" + git submodule update --init $mod + pushd $mod 1>/dev/null + bash ./install -internal-only + popd 1>/dev/null +done diff --git a/git_sparse_checkout b/git_sparse_checkout new file mode 100755 index 00000000..ec80069c --- /dev/null +++ b/git_sparse_checkout @@ -0,0 +1,69 @@ +#!/bin/bash -eu + +# see also: git_origin and git_clone + +function git_sparse_checkout { + local self=$(readlink -f "${BASH_SOURCE[0]}") + local app=$(basename $self) + local usage=\ +"USAGE: ${app} repository-URL [branch] [project-directory] [[--] [list-of-files-or-directories]] + where: + 'repository-URL' is a valid URL pointing to a Git repository. + 'branch' is a branch, atag or a commit id. Default: master. + 'project-directory' is a folder to be created and populated. Default: the project name. + 'list-of-files-or-directories' is a list of file names or directories separated by spaces. + Examples: + ${app} http://github.com/frgomes/bash-scripts -- README.md + ${app} http://github.com/frgomes/bash-scripts develop -- README.md bin/ + ${app} http://github.com/frgomes/bash-scripts develop tmpdir -- README.md bin/ docs/" + + # obtain repository-URL, e.g.: http://github.com/frgomes/bash-scripts + [[ $# != 0 ]] || (echo "${usage}" 1>&2 ; return 1) + local arg=${1} + [[ "${arg}" != "--" ]] || (echo "${usage}" 1>&2 ; return 1) + local url="${arg}" + [[ $# == 0 ]] || shift + + # obtain branch, which the default is master for historical reasons + if [[ "${arg}" != "--" ]] ;then arg="${1:-master}" ;fi + if [[ "${arg}" == "--" ]] ;then + local tag=master + else + local tag="${arg}" + [[ $# == 0 ]] || shift + fi + + # obtain the project directory, which defaults to the repository name + local prj=$(echo "$url" | sed 's:/:\n:g' | tail -1) + + if [[ "${arg}" != "--" ]] ;then arg="${1:-.}" ;fi + if [[ "${arg}" == "--" || "${arg}" == "." ]] ;then + local dir=$(readlink -f "./${prj}") + else + local dir=$(readlink -f "${arg}") + [[ $# == 0 ]] || shift + fi + + if [[ "${arg}" == "--" ]] ;then [[ $# == 0 ]] || shift; fi + if [[ "${1:-}" == "--" ]] ;then [[ $# == 0 ]] || shift; fi + + # Note: any remaining arguments after these above are considered as a + # list of files or directories to be downloaded. Names of directories + # must be followed by a slash /. + + local sparse=true + local opts='--depth=1' + + # now perform the sparse checkout + mkdir -p "${dir}" + git -C "${dir}" init + git -C "${dir}" config core.sparseCheckout ${sparse} + for path in $* ;do + echo "${path}" >> ${dir}/.git/info/sparse-checkout + done + git -C "${dir}" remote add origin ${url} + git -C "${dir}" fetch ${opts} origin ${tag} + git -C "${dir}" checkout ${tag} +} + +git_sparse_checkout $@ diff --git a/show-tags b/show-tags new file mode 100755 index 00000000..56c7d9c5 --- /dev/null +++ b/show-tags @@ -0,0 +1,20 @@ +#!/bin/bash + +this_dir=$(pwd) +printf "\nSubmodule status\n" +printf "(currently checked out commit for each submodule)\n" +printf "(when the submodule is initialized and a tag exists, the commit is shown as: 'most recent tag-commits since tag-commit hash')\n" +printf "(when the submodule is not initialized, only the checked out commit is shown)\n\n" +grep path .gitmodules | sed 's/.*= //' | while read x +do + cd "$this_dir" + printf "$x\n - current commit: " + if [ "$(ls -A $x)" ] ; then + cd "$x" + git describe --tags --always + else + git submodule status $x | sed 's/^-//' | awk '{ print $1 }' + fi +done +printf "\n" + From f530e88acee6e959e9345b35a7e00b71d81641bf Mon Sep 17 00:00:00 2001 From: James Edwards Date: Wed, 3 Jan 2024 17:20:30 -0700 Subject: [PATCH 002/159] module based method, sparse working --- git_setup | 37 ++--- git_setup.py | 104 +++++++++++++ git_sparse_checkout | 10 +- modules/__init__.py | 0 modules/lstripreader.py | 44 ++++++ modules/utils.py | 325 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 501 insertions(+), 19 deletions(-) create mode 100644 git_setup.py create mode 100644 modules/__init__.py create mode 100644 modules/lstripreader.py create mode 100644 modules/utils.py diff --git a/git_setup b/git_setup index 52901897..a5b27b13 100755 --- a/git_setup +++ b/git_setup @@ -1,10 +1,10 @@ #!/bin/bash -# Script to fetch submodules for CAM -# if argument -internal-only is supplied then only checkout CAM internal submodules, otherwise checkout everything needed for -# cam as a top level repository +# Script to fetch submodules for esm applications +# if argument -internal-only is supplied then only checkout internal submodules, otherwise checkout everything needed for +# a top level repository # set -e -script="install" +script=$0 function usage { echo -e "\nusage: $script [-i/--internal-only]\n" } @@ -31,28 +31,31 @@ do done ######################## +declare -A submods +submod_list=$(git config -f .gitmodules --list) + +while IFS= read -r line; +do + readarray -d. -t strarr <<< "$line" + if [[ "strarr[3]" == "path" ]]; then + echo "path is $strarr[4]" + fi +done <<< "$submod_list" +exit 0 # Start with sparse checkouts -if ! test -f bash-scripts/bin/git_sparse_checkout; then +if ! test -f bin/git_sparse_checkout; then echo "Getting git_sparse_checkout script" - git clone -n https://github.com/frgomes/bash-scripts --depth 1 - pushd bash-scripts 1>/dev/null - git checkout HEAD -- bin/git_sparse_checkout - popd 1>/dev/null + git clone https://github.com/jedwards4b/gitscripts --depth 1 bin fi - - - - - -./bash-scripts/bin/git_sparse_checkout https://github.com/larson-group/clubb_release clubb_4ncar_20221129_59cb19f_branch src/physics/clubb -- src/CLUBB_core/ src/SILHS/ +./bin/git_sparse_checkout https://github.com/larson-group/clubb_release clubb_4ncar_20221129_59cb19f_branch src/physics/clubb -- src/CLUBB_core/ src/SILHS/ git restore src/physics/clubb -./bash-scripts/bin/git_sparse_checkout https://github.com/CFMIP/COSPv2.0 master src/physics/cosp2/src -- src/ +./bin/git_sparse_checkout https://github.com/CFMIP/COSPv2.0 master src/physics/cosp2/src -- src/ git restore src/physics/cosp2/src -./bash-scripts/bin/git_sparse_checkout https://github.com/MPAS-Dev/MPAS-Model.git develop src/dynamics/mpas/dycore -- src/external/ src/operators/ src/tools/ \ +./bin/git_sparse_checkout https://github.com/MPAS-Dev/MPAS-Model.git develop src/dynamics/mpas/dycore -- src/external/ src/operators/ src/tools/ \ src/core_atmosphere/ src/framework/ git restore src/dynamics/mpas/dycore diff --git a/git_setup.py b/git_setup.py new file mode 100644 index 00000000..5d70486d --- /dev/null +++ b/git_setup.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python +import os +import shutil +import logging +from modules import utils +from configparser import ConfigParser +from modules.lstripreader import LstripReader + +def parse_submodules_desc_section(section, section_items): + """Create a dict for this submodule description""" + desc = {} + esmrequired_options = ("T:T", "I:T", "I:F", "T:F") + for item in section_items: + name = item[0].strip().lower() + desc[name] = item[1].strip() + if not "esmrequired" in desc: + desc["esmrequired"] = "I:T" + + if desc["esmrequired"] not in esmrequired_options: + val = desc["esmrequired"] + utils.fatal_error(f"esmrequired set to {val} which is not a supported option {esmrequired_options}") + return desc + + +def submodule_sparse_checkout(name, url, path, sparsefile, tag="master"): + # first create the module directory + if not os.path.isdir(path): + os.makedirs(path) + # Check first if the module is already defined + # and the sparse-checkout file exists + cmd = ("git", "rev-parse", "--show-toplevel") + topdir = utils.execute_subprocess(cmd, output_to_caller=True).rstrip() + topgit = os.path.join(topdir, ".git", "modules") + gitsparse = os.path.join(topgit, name, "info","sparse-checkout") + if os.path.isfile(gitsparse): + logging.warning(f"submodule {name} is already initialized") + return + + #initialize a new git repo and set the sparse checkout flag + cmd = ("git", "-C", path, "init") + status = utils.execute_subprocess(cmd, status_to_caller=True) + cmd = ("git", "-C", path, "config", "core.sparseCheckout","true") + status = utils.execute_subprocess(cmd, status_to_caller=True) + # set the repository remote + cmd = ("git", "-C", path, "remote", "add", "origin", url) + status = utils.execute_subprocess(cmd, status_to_caller=True) + + if not os.path.isdir(topgit): + os.makedirs(topgit) + topgit = os.path.join(topgit,name) + + shutil.move(os.path.join(path, ".git"), topgit) + + shutil.copy(os.path.join(path,sparsefile), gitsparse) + + with open(os.path.join(path, ".git"), "w") as f: + f.write("gitdir: " + os.path.relpath(topgit, path)) + + #Finally checkout the repo + cmd = ("git", "-C", path, "fetch", "--depth=1", "origin", "--tags") + status = utils.execute_subprocess(cmd, status_to_caller=True) + cmd = ("git", "-C", path, "checkout", tag) + status = utils.execute_subprocess(cmd, status_to_caller=True) + print(f"Successfully checked out {name}") + +def read_gitmodules_file(root_dir, esmrequired, file_name=".gitmodules"): + root_dir = os.path.abspath(root_dir) + + msg = 'In directory : {0}'.format(root_dir) + logging.info(msg) + + file_path = os.path.join(root_dir, file_name) + if not os.path.exists(file_name): + msg = ('ERROR: submodules description file, "{0}", does not ' + 'exist in dir:\n {1}'.format(file_name, root_dir)) + utils.fatal_error(msg) + config = ConfigParser() + config.read_file(LstripReader(file_path), source=file_name) + for section in config.sections(): + name = section[11:-1] + submodule_desc = parse_submodules_desc_section(section,config.items(section)) + if submodule_desc["esmrequired"] not in esmrequired: + if "T:F" in esmrequired or submodule_desc["esmrequired"].startswith("I:"): + print(f"Skipping optional component {section}") + # TODO change to logging + # logging.info(f"Skipping optional component {section}") + continue + if "esmsparse" in submodule_desc: + if "esmtag" in submodule_desc: + tag = submodule_desc["esmtag"] + else: + tag = "master" + submodule_sparse_checkout(name, submodule_desc["url"], submodule_desc["path"], + submodule_desc["esmsparse"], tag) + + + + + +esmrequired = ("I:T", "T:T") +root_dir = os.getcwd() +gitmodules = read_gitmodules_file(root_dir, esmrequired) + + diff --git a/git_sparse_checkout b/git_sparse_checkout index ec80069c..9b52bb29 100755 --- a/git_sparse_checkout +++ b/git_sparse_checkout @@ -55,13 +55,19 @@ function git_sparse_checkout { local opts='--depth=1' # now perform the sparse checkout + mkdir -p "${dir}" git -C "${dir}" init git -C "${dir}" config core.sparseCheckout ${sparse} + git -C "${dir}" remote add origin ${url} + super=$(git rev-parse --show-toplevel) + pathtodotgit=$(realpath -m --relative-to=${dir} ${super})/.git + relpathfromsuper=$(realpath -m --relative-to=${super} ${dir}) + mv ${dir}/.git ${super}/.git/modules/${relpathfromsuper} + echo "gitdir: ${pathtodotgit}/modules/${relpathfromsuper}" > ${dir}/.git for path in $* ;do - echo "${path}" >> ${dir}/.git/info/sparse-checkout + echo "${path}" >> ${super}/.git/modules/${relpathfromsuper}/info/sparse-checkout done - git -C "${dir}" remote add origin ${url} git -C "${dir}" fetch ${opts} origin ${tag} git -C "${dir}" checkout ${tag} } diff --git a/modules/__init__.py b/modules/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/modules/lstripreader.py b/modules/lstripreader.py new file mode 100644 index 00000000..530abd29 --- /dev/null +++ b/modules/lstripreader.py @@ -0,0 +1,44 @@ + +class LstripReader(object): + "LstripReader formats .gitmodules files to be acceptable for configparser" + def __init__(self, filename): + with open(filename, 'r') as infile: + lines = infile.readlines() + self._lines = list() + self._num_lines = len(lines) + self._index = 0 + for line in lines: + self._lines.append(line.lstrip()) + + def readlines(self): + """Return all the lines from this object's file""" + return self._lines + + def readline(self, size=-1): + """Format and return the next line or raise StopIteration""" + try: + line = self.next() + except StopIteration: + line = '' + + if (size > 0) and (len(line) < size): + return line[0:size] + + return line + + def __iter__(self): + """Begin an iteration""" + self._index = 0 + return self + + def next(self): + """Return the next line or raise StopIteration""" + if self._index >= self._num_lines: + raise StopIteration + + self._index = self._index + 1 + return self._lines[self._index - 1] + + def __next__(self): + return self.next() + diff --git a/modules/utils.py b/modules/utils.py new file mode 100644 index 00000000..82715387 --- /dev/null +++ b/modules/utils.py @@ -0,0 +1,325 @@ +#!/usr/bin/env python3 +""" +Common public utilities for manic package + +""" + +import logging +import os +import subprocess +import sys +from threading import Timer + +LOCAL_PATH_INDICATOR = '.' +# --------------------------------------------------------------------- +# +# screen and logging output and functions to massage text for output +# +# --------------------------------------------------------------------- + + +def log_process_output(output): + """Log each line of process output at debug level so it can be + filtered if necessary. By default, output is a single string, and + logging.debug(output) will only put log info heading on the first + line. This makes it hard to filter with grep. + + """ + output = output.split('\n') + for line in output: + logging.debug(line) + + +def printlog(msg, **kwargs): + """Wrapper script around print to ensure that everything printed to + the screen also gets logged. + + """ + logging.info(msg) + if kwargs: + print(msg, **kwargs) + else: + print(msg) + sys.stdout.flush() + + +def last_n_lines(the_string, n_lines, truncation_message=None): + """Returns the last n lines of the given string + + Args: + the_string: str + n_lines: int + truncation_message: str, optional + + Returns a string containing the last n lines of the_string + + If truncation_message is provided, the returned string begins with + the given message if and only if the string is greater than n lines + to begin with. + """ + + lines = the_string.splitlines(True) + if len(lines) <= n_lines: + return_val = the_string + else: + lines_subset = lines[-n_lines:] + str_truncated = ''.join(lines_subset) + if truncation_message: + str_truncated = truncation_message + '\n' + str_truncated + return_val = str_truncated + + return return_val + + +def indent_string(the_string, indent_level): + """Indents the given string by a given number of spaces + + Args: + the_string: str + indent_level: int + + Returns a new string that is the same as the_string, except that + each line is indented by 'indent_level' spaces. + + In python3, this can be done with textwrap.indent. + """ + + lines = the_string.splitlines(True) + padding = ' ' * indent_level + lines_indented = [padding + line for line in lines] + return ''.join(lines_indented) + +# --------------------------------------------------------------------- +# +# error handling +# +# --------------------------------------------------------------------- + + +def fatal_error(message): + """ + Error output function + """ + logging.error(message) + raise RuntimeError("{0}ERROR: {1}".format(os.linesep, message)) + + +# --------------------------------------------------------------------- +# +# Data conversion / manipulation +# +# --------------------------------------------------------------------- +def str_to_bool(bool_str): + """Convert a sting representation of as boolean into a true boolean. + + Conversion should be case insensitive. + """ + value = None + str_lower = bool_str.lower() + if str_lower in ('true', 't'): + value = True + elif str_lower in ('false', 'f'): + value = False + if value is None: + msg = ('ERROR: invalid boolean string value "{0}". ' + 'Must be "true" or "false"'.format(bool_str)) + fatal_error(msg) + return value + + +REMOTE_PREFIXES = ['http://', 'https://', 'ssh://', 'git@'] + + +def is_remote_url(url): + """check if the user provided a local file path instead of a + remote. If so, it must be expanded to an absolute + path. + + """ + remote_url = False + for prefix in REMOTE_PREFIXES: + if url.startswith(prefix): + remote_url = True + return remote_url + + +def split_remote_url(url): + """check if the user provided a local file path or a + remote. If remote, try to strip off protocol info. + + """ + remote_url = is_remote_url(url) + if not remote_url: + return url + + for prefix in REMOTE_PREFIXES: + url = url.replace(prefix, '') + + if '@' in url: + url = url.split('@')[1] + + if ':' in url: + url = url.split(':')[1] + + return url + + +def expand_local_url(url, field): + """check if the user provided a local file path instead of a + remote. If so, it must be expanded to an absolute + path. + + Note: local paths of LOCAL_PATH_INDICATOR have special meaning and + represent local copy only, don't work with the remotes. + + """ + remote_url = is_remote_url(url) + if not remote_url: + if url.strip() == LOCAL_PATH_INDICATOR: + pass + else: + url = os.path.expandvars(url) + url = os.path.expanduser(url) + if not os.path.isabs(url): + msg = ('WARNING: Externals description for "{0}" contains a ' + 'url that is not remote and does not expand to an ' + 'absolute path. Version control operations may ' + 'fail.\n\nurl={1}'.format(field, url)) + printlog(msg) + else: + url = os.path.normpath(url) + return url + + +# --------------------------------------------------------------------- +# +# subprocess +# +# --------------------------------------------------------------------- + +# Give the user a helpful message if we detect that a command seems to +# be hanging. +_HANGING_SEC = 300 + + +def _hanging_msg(working_directory, command): + print(""" + +Command '{command}' +from directory {working_directory} +has taken {hanging_sec} seconds. It may be hanging. + +The command will continue to run, but you may want to abort +manage_externals with ^C and investigate. A possible cause of hangs is +when svn or git require authentication to access a private +repository. On some systems, svn and git requests for authentication +information will not be displayed to the user. In this case, the program +will appear to hang. Ensure you can run svn and git manually and access +all repositories without entering your authentication information. + +""".format(command=command, + working_directory=working_directory, + hanging_sec=_HANGING_SEC)) + + +def execute_subprocess(commands, status_to_caller=False, + output_to_caller=False): + """Wrapper around subprocess.check_output to handle common + exceptions. + + check_output runs a command with arguments and waits + for it to complete. + + check_output raises an exception on a nonzero return code. if + status_to_caller is true, execute_subprocess returns the subprocess + return code, otherwise execute_subprocess treats non-zero return + status as an error and raises an exception. + + """ + cwd = os.getcwd() + msg = 'In directory: {0}\nexecute_subprocess running command:'.format(cwd) + logging.info(msg) + commands_str = ' '.join(commands) + logging.info(commands_str) + return_to_caller = status_to_caller or output_to_caller + status = -1 + output = '' + hanging_timer = Timer(_HANGING_SEC, _hanging_msg, + kwargs={"working_directory": cwd, + "command": commands_str}) + hanging_timer.start() + try: + output = subprocess.check_output(commands, stderr=subprocess.STDOUT, + universal_newlines=True) + log_process_output(output) + status = 0 + except OSError as error: + msg = failed_command_msg( + 'Command execution failed. Does the executable exist?', + commands) + logging.error(error) + fatal_error(msg) + except ValueError as error: + msg = failed_command_msg( + 'DEV_ERROR: Invalid arguments trying to run subprocess', + commands) + logging.error(error) + fatal_error(msg) + except subprocess.CalledProcessError as error: + # Only report the error if we are NOT returning to the + # caller. If we are returning to the caller, then it may be a + # simple status check. If returning, it is the callers + # responsibility determine if an error occurred and handle it + # appropriately. + if not return_to_caller: + msg_context = ('Process did not run successfully; ' + 'returned status {0}'.format(error.returncode)) + msg = failed_command_msg(msg_context, commands, + output=error.output) + logging.error(error) + logging.error(msg) + log_process_output(error.output) + fatal_error(msg) + status = error.returncode + finally: + hanging_timer.cancel() + + if status_to_caller and output_to_caller: + ret_value = (status, output) + elif status_to_caller: + ret_value = status + elif output_to_caller: + ret_value = output + else: + ret_value = None + + return ret_value + + +def failed_command_msg(msg_context, command, output=None): + """Template for consistent error messages from subprocess calls. + + If 'output' is given, it should provide the output from the failed + command + """ + + if output: + output_truncated = last_n_lines(output, 20, + truncation_message='[... Output truncated for brevity ...]') + errmsg = ('Failed with output:\n' + + indent_string(output_truncated, 4) + + '\nERROR: ') + else: + errmsg = '' + + command_str = ' '.join(command) + errmsg += """In directory + {cwd} +{context}: + {command} +""".format(cwd=os.getcwd(), context=msg_context, command=command_str) + + if output: + errmsg += 'See above for output from failed command.\n' + + return errmsg From e0cf1ff87ad4ff09ad950d39d8777b2df377456e Mon Sep 17 00:00:00 2001 From: James Edwards Date: Thu, 4 Jan 2024 09:29:53 -0700 Subject: [PATCH 003/159] now works two ways --- git_setup.py | 24 ++++++++--------- modules/gitinterface.py | 58 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 12 deletions(-) create mode 100644 modules/gitinterface.py diff --git a/git_setup.py b/git_setup.py index 5d70486d..56e3d2e3 100644 --- a/git_setup.py +++ b/git_setup.py @@ -5,6 +5,7 @@ from modules import utils from configparser import ConfigParser from modules.lstripreader import LstripReader +from modules.gitinterface import GitInterface def parse_submodules_desc_section(section, section_items): """Create a dict for this submodule description""" @@ -28,8 +29,10 @@ def submodule_sparse_checkout(name, url, path, sparsefile, tag="master"): os.makedirs(path) # Check first if the module is already defined # and the sparse-checkout file exists - cmd = ("git", "rev-parse", "--show-toplevel") - topdir = utils.execute_subprocess(cmd, output_to_caller=True).rstrip() + fullpath = os.path.join(os.getcwd(),path) + git = GitInterface(os.getcwd()) + topdir = git.git_operation("rev-parse", "--show-toplevel").rstrip() + topgit = os.path.join(topdir, ".git", "modules") gitsparse = os.path.join(topgit, name, "info","sparse-checkout") if os.path.isfile(gitsparse): @@ -37,13 +40,12 @@ def submodule_sparse_checkout(name, url, path, sparsefile, tag="master"): return #initialize a new git repo and set the sparse checkout flag - cmd = ("git", "-C", path, "init") - status = utils.execute_subprocess(cmd, status_to_caller=True) - cmd = ("git", "-C", path, "config", "core.sparseCheckout","true") - status = utils.execute_subprocess(cmd, status_to_caller=True) + sprepo_git = GitInterface(os.path.join(topdir,path)) + sprepo_git.config_set_value("core", "sparseCheckout", "true") + # set the repository remote - cmd = ("git", "-C", path, "remote", "add", "origin", url) - status = utils.execute_subprocess(cmd, status_to_caller=True) + cmd = ("remote", "add", "origin", url) + sprepo_git.git_operation("remote", "add", "origin", url) if not os.path.isdir(topgit): os.makedirs(topgit) @@ -57,10 +59,8 @@ def submodule_sparse_checkout(name, url, path, sparsefile, tag="master"): f.write("gitdir: " + os.path.relpath(topgit, path)) #Finally checkout the repo - cmd = ("git", "-C", path, "fetch", "--depth=1", "origin", "--tags") - status = utils.execute_subprocess(cmd, status_to_caller=True) - cmd = ("git", "-C", path, "checkout", tag) - status = utils.execute_subprocess(cmd, status_to_caller=True) + sprepo_git.git_operation( "fetch", "--depth=1", "origin", "--tags") + sprepo_git.git_operation( "checkout", tag) print(f"Successfully checked out {name}") def read_gitmodules_file(root_dir, esmrequired, file_name=".gitmodules"): diff --git a/modules/gitinterface.py b/modules/gitinterface.py new file mode 100644 index 00000000..105acd3d --- /dev/null +++ b/modules/gitinterface.py @@ -0,0 +1,58 @@ +import os +from modules import utils + +class GitInterface: + def __init__(self, repo_path): + self.repo_path = repo_path + + try: + import git + self._use_module = True + try: + self.repo = git.Repo(repo_path) # Initialize GitPython repo + except git.exc.InvalidGitRepositoryError: + self.git = git + self._init_git_repo() + except ImportError: + self._use_module = False + if not os.path.exists(os.path.join(repo_path,".git")): + self._init_git_repo() + + def _git_command(self, operation, *args): + if self._use_module: + return getattr(self.repo.git, operation)(*args) + else: + return ["git", "-C",self.repo_path, operation] + list(args) + + def _init_git_repo(self): + if self._use_module: + self.repo = self.git.Repo.init(self.repo_path) + else: + command = ("git", "-C", self.repo_path, "init") + utils.execute_subprocess(command) + + + def git_operation(self, operation, *args, **kwargs): + command = self._git_command(operation, *args) + if isinstance(command, list): + return utils.execute_subprocess(command, output_to_caller=True) + else: + return command + + def config_get_value(self, section, name): + if self._use_module: + config = self.repo.config_reader() + return config.get_value(section, name) + else: + cmd = ("git","-C",self.repo_path,"config", "--get", f"{section}.{name}") + output = utils.execute_subprocess(cmd, output_to_caller=True) + return output.strip() + + def config_set_value(self, section, name, value): + if self._use_module: + with self.repo.config_writer() as writer: + writer.set_value(section, name, value) + writer.release() # Ensure changes are saved + else: + cmd = ("git","-C",self.repo_path,"config", f"{section}.{name}", value) + utils.execute_subprocess(cmd, output_to_caller=True) From f36bc9a68a578667f83b59dbc339d96e6fdc6995 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Thu, 4 Jan 2024 14:03:45 -0700 Subject: [PATCH 004/159] complete the tools --- git_setup.py | 22 ++++++++++++++++++++-- modules/gitinterface.py | 7 ++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/git_setup.py b/git_setup.py index 56e3d2e3..319e68cd 100644 --- a/git_setup.py +++ b/git_setup.py @@ -14,6 +14,9 @@ def parse_submodules_desc_section(section, section_items): for item in section_items: name = item[0].strip().lower() desc[name] = item[1].strip() + # e3sm needs to have ssh protocol urls, we don't + if name == "url" and desc[name].startswith("git@github"): + desc[name] = desc[name].replace("git@github.com:","https://github.com/") if not "esmrequired" in desc: desc["esmrequired"] = "I:T" @@ -29,7 +32,6 @@ def submodule_sparse_checkout(name, url, path, sparsefile, tag="master"): os.makedirs(path) # Check first if the module is already defined # and the sparse-checkout file exists - fullpath = os.path.join(os.getcwd(),path) git = GitInterface(os.getcwd()) topdir = git.git_operation("rev-parse", "--show-toplevel").rstrip() @@ -62,6 +64,17 @@ def submodule_sparse_checkout(name, url, path, sparsefile, tag="master"): sprepo_git.git_operation( "fetch", "--depth=1", "origin", "--tags") sprepo_git.git_operation( "checkout", tag) print(f"Successfully checked out {name}") + +def submodule_checkout(name, url, path, tag, esmrequired): + git = GitInterface(os.cwd()) + topdir = git.git_operation("rev-parse", "--show-toplevel").rstrip() + repodir = os.path.join(topdir, path) + git.git_operation("submodule","update","--init") + # Look for a .gitmodules file in the newly checkedout repo + if os.path.exists(os.path.join(repodir,".gitmodules")): + # recursively handle this checkout + read_gitmodules_file(repodir, esmrequired) + return def read_gitmodules_file(root_dir, esmrequired, file_name=".gitmodules"): root_dir = os.path.abspath(root_dir) @@ -92,7 +105,12 @@ def read_gitmodules_file(root_dir, esmrequired, file_name=".gitmodules"): tag = "master" submodule_sparse_checkout(name, submodule_desc["url"], submodule_desc["path"], submodule_desc["esmsparse"], tag) - + continue + Iesmrequired = [] + for setting in esmrequired: + if setting.startswith("I:"): + Iesmrequired.append(setting) + submodule_checkout(name, submodule_desc["url"], submodule_desc["path"], tag, Iesmrequired) diff --git a/modules/gitinterface.py b/modules/gitinterface.py index 105acd3d..06203bb9 100644 --- a/modules/gitinterface.py +++ b/modules/gitinterface.py @@ -1,4 +1,5 @@ import os +import logging from modules import utils class GitInterface: @@ -13,12 +14,16 @@ def __init__(self, repo_path): except git.exc.InvalidGitRepositoryError: self.git = git self._init_git_repo() + msg = "Using GitPython interface to git" except ImportError: self._use_module = False if not os.path.exists(os.path.join(repo_path,".git")): self._init_git_repo() - + msg = "Using shell interface to git" + logging.info(msg) + def _git_command(self, operation, *args): + logging.info(operation) if self._use_module: return getattr(self.repo.git, operation)(*args) else: From 2beceaa56b02c1d607696a3d4259a54338214e35 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Thu, 4 Jan 2024 15:45:11 -0700 Subject: [PATCH 005/159] more fixes --- git_setup.py | 13 +++++++------ modules/gitinterface.py | 1 + 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/git_setup.py b/git_setup.py index 319e68cd..82c044cf 100644 --- a/git_setup.py +++ b/git_setup.py @@ -66,14 +66,15 @@ def submodule_sparse_checkout(name, url, path, sparsefile, tag="master"): print(f"Successfully checked out {name}") def submodule_checkout(name, url, path, tag, esmrequired): - git = GitInterface(os.cwd()) + git = GitInterface(os.getcwd()) topdir = git.git_operation("rev-parse", "--show-toplevel").rstrip() repodir = os.path.join(topdir, path) - git.git_operation("submodule","update","--init") + git.git_operation("submodule","update","--init", name) # Look for a .gitmodules file in the newly checkedout repo if os.path.exists(os.path.join(repodir,".gitmodules")): # recursively handle this checkout read_gitmodules_file(repodir, esmrequired) + print(f"Successfully checked out {name}") return def read_gitmodules_file(root_dir, esmrequired, file_name=".gitmodules"): @@ -98,11 +99,11 @@ def read_gitmodules_file(root_dir, esmrequired, file_name=".gitmodules"): # TODO change to logging # logging.info(f"Skipping optional component {section}") continue + if "esmtag" in submodule_desc: + tag = submodule_desc["esmtag"] + else: + tag = "master" if "esmsparse" in submodule_desc: - if "esmtag" in submodule_desc: - tag = submodule_desc["esmtag"] - else: - tag = "master" submodule_sparse_checkout(name, submodule_desc["url"], submodule_desc["path"], submodule_desc["esmsparse"], tag) continue diff --git a/modules/gitinterface.py b/modules/gitinterface.py index 06203bb9..baf98f35 100644 --- a/modules/gitinterface.py +++ b/modules/gitinterface.py @@ -40,6 +40,7 @@ def _init_git_repo(self): def git_operation(self, operation, *args, **kwargs): command = self._git_command(operation, *args) if isinstance(command, list): + print(command) return utils.execute_subprocess(command, output_to_caller=True) else: return command From a3fcaf20dad5a7bcbd5c4e3dc0ac916911172494 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Thu, 4 Jan 2024 15:59:04 -0700 Subject: [PATCH 006/159] submodule needs path not name --- git_setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/git_setup.py b/git_setup.py index 82c044cf..79ee8f55 100644 --- a/git_setup.py +++ b/git_setup.py @@ -69,10 +69,11 @@ def submodule_checkout(name, url, path, tag, esmrequired): git = GitInterface(os.getcwd()) topdir = git.git_operation("rev-parse", "--show-toplevel").rstrip() repodir = os.path.join(topdir, path) - git.git_operation("submodule","update","--init", name) + git.git_operation("submodule","update","--init", path) # Look for a .gitmodules file in the newly checkedout repo if os.path.exists(os.path.join(repodir,".gitmodules")): # recursively handle this checkout + print( read_gitmodules_file(repodir, esmrequired) print(f"Successfully checked out {name}") return From eed05bd0e63cd08b9d8d6d388cdfc65764ba8204 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Thu, 4 Jan 2024 16:06:35 -0700 Subject: [PATCH 007/159] improve error checking --- git_setup.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/git_setup.py b/git_setup.py index 79ee8f55..fc3616c2 100644 --- a/git_setup.py +++ b/git_setup.py @@ -73,9 +73,11 @@ def submodule_checkout(name, url, path, tag, esmrequired): # Look for a .gitmodules file in the newly checkedout repo if os.path.exists(os.path.join(repodir,".gitmodules")): # recursively handle this checkout - print( read_gitmodules_file(repodir, esmrequired) - print(f"Successfully checked out {name}") + if os.path.exists(os.path.join(repodir,".git")): + print(f"Successfully checked out {name}") + else: + utils.fatal_error(f"Failed to checkout {name}") return def read_gitmodules_file(root_dir, esmrequired, file_name=".gitmodules"): From 20431710b064b0cf4876de54065a38d962facb23 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Thu, 4 Jan 2024 16:14:05 -0700 Subject: [PATCH 008/159] add more logging --- git_setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/git_setup.py b/git_setup.py index fc3616c2..daf4181d 100644 --- a/git_setup.py +++ b/git_setup.py @@ -73,6 +73,7 @@ def submodule_checkout(name, url, path, tag, esmrequired): # Look for a .gitmodules file in the newly checkedout repo if os.path.exists(os.path.join(repodir,".gitmodules")): # recursively handle this checkout + print(f"Recursively checking out submodules of {name}") read_gitmodules_file(repodir, esmrequired) if os.path.exists(os.path.join(repodir,".git")): print(f"Successfully checked out {name}") From 9c35b88bcf355d48a262e82d04daba36ed4071aa Mon Sep 17 00:00:00 2001 From: James Edwards Date: Thu, 4 Jan 2024 16:34:46 -0700 Subject: [PATCH 009/159] submodule always usees shell --- modules/gitinterface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/gitinterface.py b/modules/gitinterface.py index baf98f35..2fc7a131 100644 --- a/modules/gitinterface.py +++ b/modules/gitinterface.py @@ -24,7 +24,7 @@ def __init__(self, repo_path): def _git_command(self, operation, *args): logging.info(operation) - if self._use_module: + if self._use_module and operation != "submodule": return getattr(self.repo.git, operation)(*args) else: return ["git", "-C",self.repo_path, operation] + list(args) From 8c5dfcdc0e7eba4cf369e15e40f19a68de42f6a5 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Thu, 4 Jan 2024 16:42:31 -0700 Subject: [PATCH 010/159] fix toplevel --- git_setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/git_setup.py b/git_setup.py index daf4181d..c22fa135 100644 --- a/git_setup.py +++ b/git_setup.py @@ -67,7 +67,8 @@ def submodule_sparse_checkout(name, url, path, sparsefile, tag="master"): def submodule_checkout(name, url, path, tag, esmrequired): git = GitInterface(os.getcwd()) - topdir = git.git_operation("rev-parse", "--show-toplevel").rstrip() + topdir = os.getcwd() +# topdir = git.git_operation("rev-parse", "--show-toplevel").rstrip() repodir = os.path.join(topdir, path) git.git_operation("submodule","update","--init", path) # Look for a .gitmodules file in the newly checkedout repo From d431e3b7cc23672a7cd081c795ee0607e17beb3b Mon Sep 17 00:00:00 2001 From: James Edwards Date: Thu, 4 Jan 2024 16:44:33 -0700 Subject: [PATCH 011/159] fix toplevel --- git_setup.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/git_setup.py b/git_setup.py index c22fa135..5355b210 100644 --- a/git_setup.py +++ b/git_setup.py @@ -65,11 +65,10 @@ def submodule_sparse_checkout(name, url, path, sparsefile, tag="master"): sprepo_git.git_operation( "checkout", tag) print(f"Successfully checked out {name}") -def submodule_checkout(name, url, path, tag, esmrequired): +def submodule_checkout(root, name, url, path, tag, esmrequired): git = GitInterface(os.getcwd()) - topdir = os.getcwd() # topdir = git.git_operation("rev-parse", "--show-toplevel").rstrip() - repodir = os.path.join(topdir, path) + repodir = os.path.join(root, path) git.git_operation("submodule","update","--init", path) # Look for a .gitmodules file in the newly checkedout repo if os.path.exists(os.path.join(repodir,".gitmodules")): @@ -116,7 +115,7 @@ def read_gitmodules_file(root_dir, esmrequired, file_name=".gitmodules"): for setting in esmrequired: if setting.startswith("I:"): Iesmrequired.append(setting) - submodule_checkout(name, submodule_desc["url"], submodule_desc["path"], tag, Iesmrequired) + submodule_checkout(root_dir, name, submodule_desc["url"], submodule_desc["path"], tag, Iesmrequired) From b6ee263ce6ee009f40288275866f45b471b8b6c8 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Thu, 4 Jan 2024 16:48:32 -0700 Subject: [PATCH 012/159] try again --- git_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git_setup.py b/git_setup.py index 5355b210..3be8e3df 100644 --- a/git_setup.py +++ b/git_setup.py @@ -73,7 +73,7 @@ def submodule_checkout(root, name, url, path, tag, esmrequired): # Look for a .gitmodules file in the newly checkedout repo if os.path.exists(os.path.join(repodir,".gitmodules")): # recursively handle this checkout - print(f"Recursively checking out submodules of {name}") + print(f"Recursively checking out submodules of {name} {repodir}") read_gitmodules_file(repodir, esmrequired) if os.path.exists(os.path.join(repodir,".git")): print(f"Successfully checked out {name}") From 996dfb6323dfd25e93c3a55ed1efb18d1f0b148a Mon Sep 17 00:00:00 2001 From: James Edwards Date: Fri, 5 Jan 2024 09:04:48 -0700 Subject: [PATCH 013/159] add command line options and support for update --- git_setup.py | 190 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 159 insertions(+), 31 deletions(-) diff --git a/git_setup.py b/git_setup.py index 3be8e3df..8361fe9a 100644 --- a/git_setup.py +++ b/git_setup.py @@ -1,12 +1,119 @@ #!/usr/bin/env python +import sys import os import shutil import logging +import argparse from modules import utils from configparser import ConfigParser from modules.lstripreader import LstripReader from modules.gitinterface import GitInterface +from contextlib import contextmanager + + +@contextmanager +def pushd(new_dir): + previous_dir = os.getcwd() + os.chdir(new_dir) + try: + yield + finally: + os.chdir(previous_dir) + +def commandline_arguments(args=None): + description = ''' + %(prog)s manages checking out groups of gitsubmodules with addtional support for Earth System Models + ''' + parser = argparse.ArgumentParser( + description=description, + formatter_class=argparse.RawDescriptionHelpFormatter) + + # + # user options + # + parser.add_argument("components", nargs="*", + help="Specific component(s) to checkout. By default, " + "all required submodules are checked out.") + + parser.add_argument('-C', '--path', default=os.getcwd(), + help='Toplevel repository directory. Defaults to current directory.') + + parser.add_argument('-x', '--exclude', nargs='*', + help='Component(s) listed in the gitmodules file which should be ignored.') + + parser.add_argument('-o', '--optional', action='store_true', default=False, + help='By default only the required submodules ' + 'are checked out. This flag will also checkout the ' + 'optional submodules relative to the toplevel directory.') + + parser.add_argument('-S', '--status', action='store_true', default=False, + help='Output the status of the repositories managed by ' + '%(prog)s. By default only summary information ' + 'is provided. Use the verbose option to see details.') + + parser.add_argument('-u', '--update', action='store_true', default=False, + help='Update submodules to the tags defined in .gitmodules.') + + parser.add_argument('-v', '--verbose', action='count', default=0, + help='Output additional information to ' + 'the screen and log file. This flag can be ' + 'used up to two times, increasing the ' + 'verbosity level each time.') + + parser.add_argument('-V', '--version', action='store_true', default=False, + help='Print manage_externals version and exit.') + + # + # developer options + # + parser.add_argument('--backtrace', action='store_true', + help='DEVELOPER: show exception backtraces as extra ' + 'debugging output') + + parser.add_argument('-d', '--debug', action='store_true', default=False, + help='DEVELOPER: output additional debugging ' + 'information to the screen and log file.') + + logging_group = parser.add_mutually_exclusive_group() + + logging_group.add_argument('--logging', dest='do_logging', + action='store_true', + help='DEVELOPER: enable logging.') + logging_group.add_argument('--no-logging', dest='do_logging', + action='store_false', default=False, + help='DEVELOPER: disable logging ' + '(this is the default)') + if args: + options = parser.parse_args(args) + else: + options = parser.parse_args() + + if options.optional: + esmrequired = 'T:' + else: + esmrequired = 'T:T' + + if options.status: + action = 'status' + elif options.update: + action = 'update' + else: + action = 'install' + + if options.version: + version_info = '' + version_file_path = os.path.join(os.path.dirname(__file__),'version.txt') + with open(version_file_path) as f: + version_info = f.readlines()[0].strip() + print(version_info) + sys.exit(0) + + + + return options.rootdir, esmrequired, options.components, options.exclude, options.verbose, action + + def parse_submodules_desc_section(section, section_items): """Create a dict for this submodule description""" desc = {} @@ -66,8 +173,7 @@ def submodule_sparse_checkout(name, url, path, sparsefile, tag="master"): print(f"Successfully checked out {name}") def submodule_checkout(root, name, url, path, tag, esmrequired): - git = GitInterface(os.getcwd()) -# topdir = git.git_operation("rev-parse", "--show-toplevel").rstrip() + git = GitInterface(root) repodir = os.path.join(root, path) git.git_operation("submodule","update","--init", path) # Look for a .gitmodules file in the newly checkedout repo @@ -80,15 +186,26 @@ def submodule_checkout(root, name, url, path, tag, esmrequired): else: utils.fatal_error(f"Failed to checkout {name}") return - -def read_gitmodules_file(root_dir, esmrequired, file_name=".gitmodules"): + +def submodule_update(root_dir, url, tag): + with pushd(root_dir): + git = GitInterface(root_dir) + # first make sure the url is correct + upstream = git.git_operation("ls-remote","--git-url") + if upstream != url: + # TODO - this needs to be a unique name + git.git_operation("remote","add","newbranch",url) + git.git_operation("checkout", tag) + + +def read_gitmodules_file(root_dir, esmrequired, file_name=".gitmodules", gitmodulelist=None, action='install'): root_dir = os.path.abspath(root_dir) msg = 'In directory : {0}'.format(root_dir) logging.info(msg) file_path = os.path.join(root_dir, file_name) - if not os.path.exists(file_name): + if not os.path.exists(file_path): msg = ('ERROR: submodules description file, "{0}", does not ' 'exist in dir:\n {1}'.format(file_name, root_dir)) utils.fatal_error(msg) @@ -97,31 +214,42 @@ def read_gitmodules_file(root_dir, esmrequired, file_name=".gitmodules"): for section in config.sections(): name = section[11:-1] submodule_desc = parse_submodules_desc_section(section,config.items(section)) - if submodule_desc["esmrequired"] not in esmrequired: - if "T:F" in esmrequired or submodule_desc["esmrequired"].startswith("I:"): - print(f"Skipping optional component {section}") - # TODO change to logging - # logging.info(f"Skipping optional component {section}") - continue - if "esmtag" in submodule_desc: - tag = submodule_desc["esmtag"] - else: - tag = "master" - if "esmsparse" in submodule_desc: - submodule_sparse_checkout(name, submodule_desc["url"], submodule_desc["path"], - submodule_desc["esmsparse"], tag) - continue - Iesmrequired = [] - for setting in esmrequired: - if setting.startswith("I:"): - Iesmrequired.append(setting) - submodule_checkout(root_dir, name, submodule_desc["url"], submodule_desc["path"], tag, Iesmrequired) - - - - -esmrequired = ("I:T", "T:T") -root_dir = os.getcwd() -gitmodules = read_gitmodules_file(root_dir, esmrequired) + if action == 'install': + # Recursively install submodules, honering esm tags in .gitmodules + if submodule_desc["esmrequired"] not in esmrequired: + if "T:F" in esmrequired or submodule_desc["esmrequired"].startswith("I:"): + print(f"Skipping optional component {section}") + # TODO change to logging + # logging.info(f"Skipping optional component {section}") + continue + if "esmtag" in submodule_desc: + tag = submodule_desc["esmtag"] + else: + tag = "master" + if "esmsparse" in submodule_desc: + submodule_sparse_checkout(name, submodule_desc["url"], submodule_desc["path"], + submodule_desc["esmsparse"], tag) + continue + Iesmrequired = [] + for setting in esmrequired: + if setting.startswith("I:"): + Iesmrequired.append(setting) + submodule_checkout(root_dir, name, submodule_desc["url"], submodule_desc["path"], tag, Iesmrequired) + + if action == 'update': + # update the submodules to the tags defined in .gitmodules + if "esmtag" in submodule_desc: + submod_dir = os.path.join(root_dir,submodule_desc['path']) + if os.path.exists(os.path.join(submod_dir,".git")): + submodule_update(submod_dir, submodule_desc['url'], submodule_desc["esmtag"]) + + + + +if __name__ == '__main__': + root_dir, esmrequired, includelist, excludelist, verbose, action = commandline_arguments() + esmrequired = ("I:T", "T:T") + root_dir = os.getcwd() + read_gitmodules_file(root_dir, esmrequired, gitmodulelist, action, verbose) From 647a8deea48c13ad9dbbb55fbcf294ff6eec6546 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Fri, 5 Jan 2024 10:23:07 -0700 Subject: [PATCH 014/159] fix new issues --- git_setup.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/git_setup.py b/git_setup.py index 8361fe9a..82100a09 100644 --- a/git_setup.py +++ b/git_setup.py @@ -39,6 +39,11 @@ def commandline_arguments(args=None): parser.add_argument('-C', '--path', default=os.getcwd(), help='Toplevel repository directory. Defaults to current directory.') + parser.add_argument('-g', '--gitmodules', nargs='?', + default='.gitmodules', + help='The submodule description filename. ' + 'Default: %(default)s.') + parser.add_argument('-x', '--exclude', nargs='*', help='Component(s) listed in the gitmodules file which should be ignored.') @@ -109,9 +114,7 @@ def commandline_arguments(args=None): print(version_info) sys.exit(0) - - - return options.rootdir, esmrequired, options.components, options.exclude, options.verbose, action + return options.path, options.gitmodules, esmrequired, options.components, options.exclude, options.verbose, action def parse_submodules_desc_section(section, section_items): @@ -191,14 +194,15 @@ def submodule_update(root_dir, url, tag): with pushd(root_dir): git = GitInterface(root_dir) # first make sure the url is correct - upstream = git.git_operation("ls-remote","--git-url") + upstream = git.git_operation("ls-remote","--get-url").rstrip() + print(f"Here {upstream} and {url}") if upstream != url: # TODO - this needs to be a unique name git.git_operation("remote","add","newbranch",url) git.git_operation("checkout", tag) -def read_gitmodules_file(root_dir, esmrequired, file_name=".gitmodules", gitmodulelist=None, action='install'): +def read_gitmodules_file(root_dir, esmrequired, file_name=".gitmodules", includelist=None, excludelist=None, action='install'): root_dir = os.path.abspath(root_dir) msg = 'In directory : {0}'.format(root_dir) @@ -213,6 +217,10 @@ def read_gitmodules_file(root_dir, esmrequired, file_name=".gitmodules", gitmodu config.read_file(LstripReader(file_path), source=file_name) for section in config.sections(): name = section[11:-1] + if includelist and name not in includelist: + continue + if excludelist and name in excludelist: + continue submodule_desc = parse_submodules_desc_section(section,config.items(section)) if action == 'install': @@ -248,8 +256,6 @@ def read_gitmodules_file(root_dir, esmrequired, file_name=".gitmodules", gitmodu if __name__ == '__main__': - root_dir, esmrequired, includelist, excludelist, verbose, action = commandline_arguments() - esmrequired = ("I:T", "T:T") - root_dir = os.getcwd() - read_gitmodules_file(root_dir, esmrequired, gitmodulelist, action, verbose) + root_dir, file_name, esmrequired, includelist, excludelist, verbose, action = commandline_arguments() + read_gitmodules_file(root_dir, file_name=file_name, esmrequired=esmrequired, includelist=includelist, excludelist=excludelist, action=action) From fbb8eaf70bd134f8ec5de662294f033c1d18b8ed Mon Sep 17 00:00:00 2001 From: James Edwards Date: Fri, 5 Jan 2024 10:44:48 -0700 Subject: [PATCH 015/159] update esmrequired variable --- git_setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git_setup.py b/git_setup.py index 82100a09..baacdb3d 100644 --- a/git_setup.py +++ b/git_setup.py @@ -95,9 +95,9 @@ def commandline_arguments(args=None): options = parser.parse_args() if options.optional: - esmrequired = 'T:' + esmrequired = ('T:T','T:F','I:T') else: - esmrequired = 'T:T' + esmrequired = ('T:T','I:T') if options.status: action = 'status' From e22d95b589470d1d4f93970951b555b1fdfa7462 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Fri, 5 Jan 2024 11:34:38 -0700 Subject: [PATCH 016/159] fix recursive issue --- git_setup.py | 19 +++++++------------ modules/gitinterface.py | 1 - 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/git_setup.py b/git_setup.py index baacdb3d..d8b02753 100644 --- a/git_setup.py +++ b/git_setup.py @@ -183,7 +183,7 @@ def submodule_checkout(root, name, url, path, tag, esmrequired): if os.path.exists(os.path.join(repodir,".gitmodules")): # recursively handle this checkout print(f"Recursively checking out submodules of {name} {repodir}") - read_gitmodules_file(repodir, esmrequired) + read_gitmodules_file(repodir, ("I:T")) if os.path.exists(os.path.join(repodir,".git")): print(f"Successfully checked out {name}") else: @@ -195,7 +195,6 @@ def submodule_update(root_dir, url, tag): git = GitInterface(root_dir) # first make sure the url is correct upstream = git.git_operation("ls-remote","--get-url").rstrip() - print(f"Here {upstream} and {url}") if upstream != url: # TODO - this needs to be a unique name git.git_operation("remote","add","newbranch",url) @@ -226,11 +225,10 @@ def read_gitmodules_file(root_dir, esmrequired, file_name=".gitmodules", include if action == 'install': # Recursively install submodules, honering esm tags in .gitmodules if submodule_desc["esmrequired"] not in esmrequired: - if "T:F" in esmrequired or submodule_desc["esmrequired"].startswith("I:"): - print(f"Skipping optional component {section}") - # TODO change to logging - # logging.info(f"Skipping optional component {section}") - continue + print(f"Skipping optional component {section}") + # TODO change to logging + # logging.info(f"Skipping optional component {section}") + continue if "esmtag" in submodule_desc: tag = submodule_desc["esmtag"] else: @@ -239,11 +237,8 @@ def read_gitmodules_file(root_dir, esmrequired, file_name=".gitmodules", include submodule_sparse_checkout(name, submodule_desc["url"], submodule_desc["path"], submodule_desc["esmsparse"], tag) continue - Iesmrequired = [] - for setting in esmrequired: - if setting.startswith("I:"): - Iesmrequired.append(setting) - submodule_checkout(root_dir, name, submodule_desc["url"], submodule_desc["path"], tag, Iesmrequired) + + submodule_checkout(root_dir, name, submodule_desc["url"], submodule_desc["path"], tag, esmrequired) if action == 'update': # update the submodules to the tags defined in .gitmodules diff --git a/modules/gitinterface.py b/modules/gitinterface.py index 2fc7a131..a4e25292 100644 --- a/modules/gitinterface.py +++ b/modules/gitinterface.py @@ -40,7 +40,6 @@ def _init_git_repo(self): def git_operation(self, operation, *args, **kwargs): command = self._git_command(operation, *args) if isinstance(command, list): - print(command) return utils.execute_subprocess(command, output_to_caller=True) else: return command From 99d5439757f52b01fa0ae9f0924c472b255083eb Mon Sep 17 00:00:00 2001 From: James Edwards Date: Fri, 5 Jan 2024 13:52:33 -0700 Subject: [PATCH 017/159] add status flag support --- git_setup.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/git_setup.py b/git_setup.py index d8b02753..f5334fae 100644 --- a/git_setup.py +++ b/git_setup.py @@ -200,7 +200,17 @@ def submodule_update(root_dir, url, tag): git.git_operation("remote","add","newbranch",url) git.git_operation("checkout", tag) - +def submodule_status(root_dir, name, url, path, tag): + with pushd(path): + git = GitInterface(os.path.join(root_dir,path)) + atag = git.git_operation("describe","--tags","--always").rstrip() + if tag and atag != tag: + print(f"Submodule {name} {atag} is out of sync with .gitmodules {tag}") + elif tag: + print(f"Submodule {name} at tag {tag}") + else: + print(f"Submodule {name} has no tag defined in .gitmodules") + def read_gitmodules_file(root_dir, esmrequired, file_name=".gitmodules", includelist=None, excludelist=None, action='install'): root_dir = os.path.abspath(root_dir) @@ -222,10 +232,19 @@ def read_gitmodules_file(root_dir, esmrequired, file_name=".gitmodules", include continue submodule_desc = parse_submodules_desc_section(section,config.items(section)) + if action == 'status': + if "esmtag" in submodule_desc: + tag = submodule_desc["esmtag"] + else: + tag = None + submodule_status(root_dir, name, submodule_desc["url"], submodule_desc["path"], tag) + + if action == 'install': # Recursively install submodules, honering esm tags in .gitmodules if submodule_desc["esmrequired"] not in esmrequired: - print(f"Skipping optional component {section}") + if 'T:F' in submodule_desc["esmrequired"]: + print(f"Skipping optional component {section}") # TODO change to logging # logging.info(f"Skipping optional component {section}") continue @@ -252,5 +271,6 @@ def read_gitmodules_file(root_dir, esmrequired, file_name=".gitmodules", include if __name__ == '__main__': root_dir, file_name, esmrequired, includelist, excludelist, verbose, action = commandline_arguments() + print(f"action is {action}") read_gitmodules_file(root_dir, file_name=file_name, esmrequired=esmrequired, includelist=includelist, excludelist=excludelist, action=action) From 671bb1df07b611db05947f63572e38c2f6286a9a Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sat, 6 Jan 2024 09:28:06 -0700 Subject: [PATCH 018/159] add a gitmodules class --- git-fleximod.py | 302 ++++++++++++++++++++++++++++++++++++++++++ git_setup | 94 ------------- git_setup.py | 276 -------------------------------------- git_sparse_checkout | 75 ----------- modules/gitmodules.py | 53 ++++++++ modules/utils.py | 137 +++++++++++-------- show-tags | 20 --- 7 files changed, 437 insertions(+), 520 deletions(-) create mode 100644 git-fleximod.py delete mode 100755 git_setup delete mode 100644 git_setup.py delete mode 100755 git_sparse_checkout create mode 100644 modules/gitmodules.py delete mode 100755 show-tags diff --git a/git-fleximod.py b/git-fleximod.py new file mode 100644 index 00000000..ded13eb9 --- /dev/null +++ b/git-fleximod.py @@ -0,0 +1,302 @@ +#!/usr/bin/env python +import sys +import os +import shutil +import logging +import argparse +from modules import utils +from modules.gitinterface import GitInterface +from modules.gitmodules import GitModules + + +def commandline_arguments(args=None): + description = """ + %(prog)s manages checking out groups of gitsubmodules with addtional support for Earth System Models + """ + parser = argparse.ArgumentParser( + description=description, formatter_class=argparse.RawDescriptionHelpFormatter + ) + + # + # user options + # + parser.add_argument( + "components", + nargs="*", + help="Specific component(s) to checkout. By default, " + "all required submodules are checked out.", + ) + + parser.add_argument( + "-C", + "--path", + default=os.getcwd(), + help="Toplevel repository directory. Defaults to current directory.", + ) + + parser.add_argument( + "-g", + "--gitmodules", + nargs="?", + default=".gitmodules", + help="The submodule description filename. " "Default: %(default)s.", + ) + + parser.add_argument( + "-x", + "--exclude", + nargs="*", + help="Component(s) listed in the gitmodules file which should be ignored.", + ) + + parser.add_argument( + "-o", + "--optional", + action="store_true", + default=False, + help="By default only the required submodules " + "are checked out. This flag will also checkout the " + "optional submodules relative to the toplevel directory.", + ) + + parser.add_argument( + "-S", + "--status", + action="store_true", + default=False, + help="Output the status of the repositories managed by " + "%(prog)s. By default only summary information " + "is provided. Use the verbose option to see details.", + ) + + parser.add_argument( + "-u", + "--update", + action="store_true", + default=False, + help="Update submodules to the tags defined in .gitmodules.", + ) + + parser.add_argument( + "-v", + "--verbose", + action="count", + default=0, + help="Output additional information to " + "the screen and log file. This flag can be " + "used up to two times, increasing the " + "verbosity level each time.", + ) + + parser.add_argument( + "-V", + "--version", + action="store_true", + default=False, + help="Print manage_externals version and exit.", + ) + + # + # developer options + # + parser.add_argument( + "--backtrace", + action="store_true", + help="DEVELOPER: show exception backtraces as extra " "debugging output", + ) + + parser.add_argument( + "-d", + "--debug", + action="store_true", + default=False, + help="DEVELOPER: output additional debugging " + "information to the screen and log file.", + ) + + logging_group = parser.add_mutually_exclusive_group() + + logging_group.add_argument( + "--logging", + dest="do_logging", + action="store_true", + help="DEVELOPER: enable logging.", + ) + logging_group.add_argument( + "--no-logging", + dest="do_logging", + action="store_false", + default=False, + help="DEVELOPER: disable logging " "(this is the default)", + ) + if args: + options = parser.parse_args(args) + else: + options = parser.parse_args() + + if options.optional: + esmrequired = ("T:T", "T:F", "I:T") + else: + esmrequired = ("T:T", "I:T") + + if options.status: + action = "status" + elif options.update: + action = "update" + else: + action = "install" + + if options.version: + version_info = "" + version_file_path = os.path.join(os.path.dirname(__file__), "version.txt") + with open(version_file_path) as f: + version_info = f.readlines()[0].strip() + print(version_info) + sys.exit(0) + + return ( + options.path, + options.gitmodules, + esmrequired, + options.components, + options.exclude, + options.verbose, + action, + ) + + +def submodule_sparse_checkout(name, url, path, sparsefile, tag="master"): + # first create the module directory + if not os.path.isdir(path): + os.makedirs(path) + # Check first if the module is already defined + # and the sparse-checkout file exists + git = GitInterface(os.getcwd()) + topdir = git.git_operation("rev-parse", "--show-toplevel").rstrip() + + topgit = os.path.join(topdir, ".git", "modules") + gitsparse = os.path.join(topgit, name, "info", "sparse-checkout") + if os.path.isfile(gitsparse): + logging.warning("submodule {} is already initialized".format(name)) + return + + # initialize a new git repo and set the sparse checkout flag + sprepo_git = GitInterface(os.path.join(topdir, path)) + sprepo_git.config_set_value("core", "sparseCheckout", "true") + + # set the repository remote + sprepo_git.git_operation("remote", "add", "origin", url) + + if not os.path.isdir(topgit): + os.makedirs(topgit) + topgit = os.path.join(topgit, name) + + shutil.move(os.path.join(path, ".git"), topgit) + + shutil.copy(os.path.join(path, sparsefile), gitsparse) + + with open(os.path.join(path, ".git"), "w") as f: + f.write("gitdir: " + os.path.relpath(topgit, path)) + + # Finally checkout the repo + sprepo_git.git_operation("fetch", "--depth=1", "origin", "--tags") + sprepo_git.git_operation("checkout", tag) + print(f"Successfully checked out {name}") + + +def submodule_checkout(root, name, path): + git = GitInterface(root) + repodir = os.path.join(root, path) + git.git_operation("submodule", "update", "--init", path) + # Look for a .gitmodules file in the newly checkedout repo + if os.path.exists(os.path.join(repodir, ".gitmodules")): + # recursively handle this checkout + print(f"Recursively checking out submodules of {name} {repodir}") + gitmodules = GitModules(repodir) + submodules_install(gitmodules, repodir, ("I:T")) + if os.path.exists(os.path.join(repodir, ".git")): + print(f"Successfully checked out {name}") + else: + utils.fatal_error(f"Failed to checkout {name}") + return + + +def submodules_status(gitmodules, root_dir): + for name in gitmodules.sections(): + path = gitmodules.get(name, "path") + tag = gitmodules.git(name, "esmtag") + with utils.pushd(path): + git = GitInterface(os.path.join(root_dir, path)) + atag = git.git_operation("describe", "--tags", "--always").rstrip() + if tag and atag != tag: + print(f"Submodule {name} {atag} is out of sync with .gitmodules {tag}") + elif tag: + print(f"Submodule {name} at tag {tag}") + else: + print(f"Submodule {name} has no tag defined in .gitmodules") + + +def submodules_update(gitmodules, root_dir): + for name in gitmodules.sections(): + esmtag = gitmodules.get(name, "esmtag") + path = gitmodules.get(name, "path") + url = gitmodules.get(name, "url") + if os.path.exists(os.path.join(path, ".git")): + with utils.pushd(root_dir): + git = GitInterface(root_dir) + # first make sure the url is correct + upstream = git.git_operation("ls-remote", "--get-url").rstrip() + if upstream != url: + # TODO - this needs to be a unique name + git.git_operation("remote", "add", "newbranch", url) + git.git_operation("checkout", esmtag) + + +def submodules_install(gitmodules, root_dir, requiredlist): + for name in gitmodules.sections(): + esmrequired = gitmodules.get(name, "esmrequired") + esmsparse = gitmodules.get(name, "esmsparse") + esmtag = gitmodules.get(name, "esmtag") + path = gitmodules.get(name, "path") + url = gitmodules.get(name, "url") + if esmrequired not in requiredlist: + if "T:F" in esmrequired: + print("Skipping optional component {}".format(name)) + continue + if esmsparse: + submodule_sparse_checkout(name, url, path, esmsparse, tag=esmtag) + else: + submodule_checkout(root_dir, name, path) + + +def _main_func(): + ( + root_dir, + file_name, + esmrequired, + includelist, + excludelist, + verbose, + action, + ) = commandline_arguments() + if verbose: + print(f"action is {action}") + gitmodules = GitModules( + confpath=root_dir, + conffile=file_name, + includelist=includelist, + excludelist=excludelist, + ) + + if action == "update": + submodules_update(gitmodules, root_dir) + elif action == "install": + submodules_install(gitmodules, root_dir, esmrequired) + elif action == "status": + submodules_status(gitmodules, root_dir) + else: + utils.fatal_error(f"unrecognized action request {action}") + + +if __name__ == "__main__": + _main_func() diff --git a/git_setup b/git_setup deleted file mode 100755 index a5b27b13..00000000 --- a/git_setup +++ /dev/null @@ -1,94 +0,0 @@ -#!/bin/bash -# Script to fetch submodules for esm applications -# if argument -internal-only is supplied then only checkout internal submodules, otherwise checkout everything needed for -# a top level repository -# -set -e -script=$0 -function usage { - echo -e "\nusage: $script [-i/--internal-only]\n" -} -# Set default arguments -internal_only=0 - -# Process arguments -while [ "$1" != "" ]; -do - case $1 in - - # Only checkout CISM internal submodules - -i | -internal-only | --internal-only) - internal_only=1 - ;; - - *) - echo "$script: illegal option $1" - usage - exit 1 - ;; - esac - shift -done - -######################## -declare -A submods -submod_list=$(git config -f .gitmodules --list) - -while IFS= read -r line; -do - readarray -d. -t strarr <<< "$line" - if [[ "strarr[3]" == "path" ]]; then - echo "path is $strarr[4]" - fi -done <<< "$submod_list" -exit 0 - -# Start with sparse checkouts -if ! test -f bin/git_sparse_checkout; then - echo "Getting git_sparse_checkout script" - git clone https://github.com/jedwards4b/gitscripts --depth 1 bin -fi - -./bin/git_sparse_checkout https://github.com/larson-group/clubb_release clubb_4ncar_20221129_59cb19f_branch src/physics/clubb -- src/CLUBB_core/ src/SILHS/ -git restore src/physics/clubb - -./bin/git_sparse_checkout https://github.com/CFMIP/COSPv2.0 master src/physics/cosp2/src -- src/ -git restore src/physics/cosp2/src - -./bin/git_sparse_checkout https://github.com/MPAS-Dev/MPAS-Model.git develop src/dynamics/mpas/dycore -- src/external/ src/operators/ src/tools/ \ - src/core_atmosphere/ src/framework/ -git restore src/dynamics/mpas/dycore - -submodules=('chem_proc' 'src/physics/carma/base' 'src/physics/pumas' 'src/physics/pumas-frozen' 'src/physics/ali_arms' 'src/atmos_phys' 'src/dynamics/fv3/atmos_cubed_sphere' 'src/hemco') -for mod in "${submodules[@]}" -do - echo "Initializing $mod" - git submodule update --init $mod -done - -if [ ${internal_only} -eq 1 ] - then - exit 0 -fi - - -submodules=('ccs_config' 'components/cice5' 'components/cice' 'components/cmeps' \ - 'components/cdeps' 'components/cpl7' 'share' 'libraries/mct' \ - 'libraries/parallelio' 'cime' 'libraries/FMS' 'components/mosart' \ - 'components/rtm') - -for mod in "${submodules[@]}" -do - echo "Initializing $mod" - git submodule update --init --recursive $mod -done - -sparse_submodules=('components/clm' 'components/cism') -for mod in "${sparse_submodules[@]}" -do - echo "Initializing $mod" - git submodule update --init $mod - pushd $mod 1>/dev/null - bash ./install -internal-only - popd 1>/dev/null -done diff --git a/git_setup.py b/git_setup.py deleted file mode 100644 index f5334fae..00000000 --- a/git_setup.py +++ /dev/null @@ -1,276 +0,0 @@ -#!/usr/bin/env python -import sys -import os -import shutil -import logging -import argparse -from modules import utils -from configparser import ConfigParser -from modules.lstripreader import LstripReader -from modules.gitinterface import GitInterface - -from contextlib import contextmanager - - -@contextmanager -def pushd(new_dir): - previous_dir = os.getcwd() - os.chdir(new_dir) - try: - yield - finally: - os.chdir(previous_dir) - -def commandline_arguments(args=None): - description = ''' - %(prog)s manages checking out groups of gitsubmodules with addtional support for Earth System Models - ''' - parser = argparse.ArgumentParser( - description=description, - formatter_class=argparse.RawDescriptionHelpFormatter) - - # - # user options - # - parser.add_argument("components", nargs="*", - help="Specific component(s) to checkout. By default, " - "all required submodules are checked out.") - - parser.add_argument('-C', '--path', default=os.getcwd(), - help='Toplevel repository directory. Defaults to current directory.') - - parser.add_argument('-g', '--gitmodules', nargs='?', - default='.gitmodules', - help='The submodule description filename. ' - 'Default: %(default)s.') - - parser.add_argument('-x', '--exclude', nargs='*', - help='Component(s) listed in the gitmodules file which should be ignored.') - - parser.add_argument('-o', '--optional', action='store_true', default=False, - help='By default only the required submodules ' - 'are checked out. This flag will also checkout the ' - 'optional submodules relative to the toplevel directory.') - - parser.add_argument('-S', '--status', action='store_true', default=False, - help='Output the status of the repositories managed by ' - '%(prog)s. By default only summary information ' - 'is provided. Use the verbose option to see details.') - - parser.add_argument('-u', '--update', action='store_true', default=False, - help='Update submodules to the tags defined in .gitmodules.') - - parser.add_argument('-v', '--verbose', action='count', default=0, - help='Output additional information to ' - 'the screen and log file. This flag can be ' - 'used up to two times, increasing the ' - 'verbosity level each time.') - - parser.add_argument('-V', '--version', action='store_true', default=False, - help='Print manage_externals version and exit.') - - # - # developer options - # - parser.add_argument('--backtrace', action='store_true', - help='DEVELOPER: show exception backtraces as extra ' - 'debugging output') - - parser.add_argument('-d', '--debug', action='store_true', default=False, - help='DEVELOPER: output additional debugging ' - 'information to the screen and log file.') - - logging_group = parser.add_mutually_exclusive_group() - - logging_group.add_argument('--logging', dest='do_logging', - action='store_true', - help='DEVELOPER: enable logging.') - logging_group.add_argument('--no-logging', dest='do_logging', - action='store_false', default=False, - help='DEVELOPER: disable logging ' - '(this is the default)') - if args: - options = parser.parse_args(args) - else: - options = parser.parse_args() - - if options.optional: - esmrequired = ('T:T','T:F','I:T') - else: - esmrequired = ('T:T','I:T') - - if options.status: - action = 'status' - elif options.update: - action = 'update' - else: - action = 'install' - - if options.version: - version_info = '' - version_file_path = os.path.join(os.path.dirname(__file__),'version.txt') - with open(version_file_path) as f: - version_info = f.readlines()[0].strip() - print(version_info) - sys.exit(0) - - return options.path, options.gitmodules, esmrequired, options.components, options.exclude, options.verbose, action - - -def parse_submodules_desc_section(section, section_items): - """Create a dict for this submodule description""" - desc = {} - esmrequired_options = ("T:T", "I:T", "I:F", "T:F") - for item in section_items: - name = item[0].strip().lower() - desc[name] = item[1].strip() - # e3sm needs to have ssh protocol urls, we don't - if name == "url" and desc[name].startswith("git@github"): - desc[name] = desc[name].replace("git@github.com:","https://github.com/") - if not "esmrequired" in desc: - desc["esmrequired"] = "I:T" - - if desc["esmrequired"] not in esmrequired_options: - val = desc["esmrequired"] - utils.fatal_error(f"esmrequired set to {val} which is not a supported option {esmrequired_options}") - return desc - - -def submodule_sparse_checkout(name, url, path, sparsefile, tag="master"): - # first create the module directory - if not os.path.isdir(path): - os.makedirs(path) - # Check first if the module is already defined - # and the sparse-checkout file exists - git = GitInterface(os.getcwd()) - topdir = git.git_operation("rev-parse", "--show-toplevel").rstrip() - - topgit = os.path.join(topdir, ".git", "modules") - gitsparse = os.path.join(topgit, name, "info","sparse-checkout") - if os.path.isfile(gitsparse): - logging.warning(f"submodule {name} is already initialized") - return - - #initialize a new git repo and set the sparse checkout flag - sprepo_git = GitInterface(os.path.join(topdir,path)) - sprepo_git.config_set_value("core", "sparseCheckout", "true") - - # set the repository remote - cmd = ("remote", "add", "origin", url) - sprepo_git.git_operation("remote", "add", "origin", url) - - if not os.path.isdir(topgit): - os.makedirs(topgit) - topgit = os.path.join(topgit,name) - - shutil.move(os.path.join(path, ".git"), topgit) - - shutil.copy(os.path.join(path,sparsefile), gitsparse) - - with open(os.path.join(path, ".git"), "w") as f: - f.write("gitdir: " + os.path.relpath(topgit, path)) - - #Finally checkout the repo - sprepo_git.git_operation( "fetch", "--depth=1", "origin", "--tags") - sprepo_git.git_operation( "checkout", tag) - print(f"Successfully checked out {name}") - -def submodule_checkout(root, name, url, path, tag, esmrequired): - git = GitInterface(root) - repodir = os.path.join(root, path) - git.git_operation("submodule","update","--init", path) - # Look for a .gitmodules file in the newly checkedout repo - if os.path.exists(os.path.join(repodir,".gitmodules")): - # recursively handle this checkout - print(f"Recursively checking out submodules of {name} {repodir}") - read_gitmodules_file(repodir, ("I:T")) - if os.path.exists(os.path.join(repodir,".git")): - print(f"Successfully checked out {name}") - else: - utils.fatal_error(f"Failed to checkout {name}") - return - -def submodule_update(root_dir, url, tag): - with pushd(root_dir): - git = GitInterface(root_dir) - # first make sure the url is correct - upstream = git.git_operation("ls-remote","--get-url").rstrip() - if upstream != url: - # TODO - this needs to be a unique name - git.git_operation("remote","add","newbranch",url) - git.git_operation("checkout", tag) - -def submodule_status(root_dir, name, url, path, tag): - with pushd(path): - git = GitInterface(os.path.join(root_dir,path)) - atag = git.git_operation("describe","--tags","--always").rstrip() - if tag and atag != tag: - print(f"Submodule {name} {atag} is out of sync with .gitmodules {tag}") - elif tag: - print(f"Submodule {name} at tag {tag}") - else: - print(f"Submodule {name} has no tag defined in .gitmodules") - -def read_gitmodules_file(root_dir, esmrequired, file_name=".gitmodules", includelist=None, excludelist=None, action='install'): - root_dir = os.path.abspath(root_dir) - - msg = 'In directory : {0}'.format(root_dir) - logging.info(msg) - - file_path = os.path.join(root_dir, file_name) - if not os.path.exists(file_path): - msg = ('ERROR: submodules description file, "{0}", does not ' - 'exist in dir:\n {1}'.format(file_name, root_dir)) - utils.fatal_error(msg) - config = ConfigParser() - config.read_file(LstripReader(file_path), source=file_name) - for section in config.sections(): - name = section[11:-1] - if includelist and name not in includelist: - continue - if excludelist and name in excludelist: - continue - submodule_desc = parse_submodules_desc_section(section,config.items(section)) - - if action == 'status': - if "esmtag" in submodule_desc: - tag = submodule_desc["esmtag"] - else: - tag = None - submodule_status(root_dir, name, submodule_desc["url"], submodule_desc["path"], tag) - - - if action == 'install': - # Recursively install submodules, honering esm tags in .gitmodules - if submodule_desc["esmrequired"] not in esmrequired: - if 'T:F' in submodule_desc["esmrequired"]: - print(f"Skipping optional component {section}") - # TODO change to logging - # logging.info(f"Skipping optional component {section}") - continue - if "esmtag" in submodule_desc: - tag = submodule_desc["esmtag"] - else: - tag = "master" - if "esmsparse" in submodule_desc: - submodule_sparse_checkout(name, submodule_desc["url"], submodule_desc["path"], - submodule_desc["esmsparse"], tag) - continue - - submodule_checkout(root_dir, name, submodule_desc["url"], submodule_desc["path"], tag, esmrequired) - - if action == 'update': - # update the submodules to the tags defined in .gitmodules - if "esmtag" in submodule_desc: - submod_dir = os.path.join(root_dir,submodule_desc['path']) - if os.path.exists(os.path.join(submod_dir,".git")): - submodule_update(submod_dir, submodule_desc['url'], submodule_desc["esmtag"]) - - - - -if __name__ == '__main__': - root_dir, file_name, esmrequired, includelist, excludelist, verbose, action = commandline_arguments() - print(f"action is {action}") - read_gitmodules_file(root_dir, file_name=file_name, esmrequired=esmrequired, includelist=includelist, excludelist=excludelist, action=action) - diff --git a/git_sparse_checkout b/git_sparse_checkout deleted file mode 100755 index 9b52bb29..00000000 --- a/git_sparse_checkout +++ /dev/null @@ -1,75 +0,0 @@ -#!/bin/bash -eu - -# see also: git_origin and git_clone - -function git_sparse_checkout { - local self=$(readlink -f "${BASH_SOURCE[0]}") - local app=$(basename $self) - local usage=\ -"USAGE: ${app} repository-URL [branch] [project-directory] [[--] [list-of-files-or-directories]] - where: - 'repository-URL' is a valid URL pointing to a Git repository. - 'branch' is a branch, atag or a commit id. Default: master. - 'project-directory' is a folder to be created and populated. Default: the project name. - 'list-of-files-or-directories' is a list of file names or directories separated by spaces. - Examples: - ${app} http://github.com/frgomes/bash-scripts -- README.md - ${app} http://github.com/frgomes/bash-scripts develop -- README.md bin/ - ${app} http://github.com/frgomes/bash-scripts develop tmpdir -- README.md bin/ docs/" - - # obtain repository-URL, e.g.: http://github.com/frgomes/bash-scripts - [[ $# != 0 ]] || (echo "${usage}" 1>&2 ; return 1) - local arg=${1} - [[ "${arg}" != "--" ]] || (echo "${usage}" 1>&2 ; return 1) - local url="${arg}" - [[ $# == 0 ]] || shift - - # obtain branch, which the default is master for historical reasons - if [[ "${arg}" != "--" ]] ;then arg="${1:-master}" ;fi - if [[ "${arg}" == "--" ]] ;then - local tag=master - else - local tag="${arg}" - [[ $# == 0 ]] || shift - fi - - # obtain the project directory, which defaults to the repository name - local prj=$(echo "$url" | sed 's:/:\n:g' | tail -1) - - if [[ "${arg}" != "--" ]] ;then arg="${1:-.}" ;fi - if [[ "${arg}" == "--" || "${arg}" == "." ]] ;then - local dir=$(readlink -f "./${prj}") - else - local dir=$(readlink -f "${arg}") - [[ $# == 0 ]] || shift - fi - - if [[ "${arg}" == "--" ]] ;then [[ $# == 0 ]] || shift; fi - if [[ "${1:-}" == "--" ]] ;then [[ $# == 0 ]] || shift; fi - - # Note: any remaining arguments after these above are considered as a - # list of files or directories to be downloaded. Names of directories - # must be followed by a slash /. - - local sparse=true - local opts='--depth=1' - - # now perform the sparse checkout - - mkdir -p "${dir}" - git -C "${dir}" init - git -C "${dir}" config core.sparseCheckout ${sparse} - git -C "${dir}" remote add origin ${url} - super=$(git rev-parse --show-toplevel) - pathtodotgit=$(realpath -m --relative-to=${dir} ${super})/.git - relpathfromsuper=$(realpath -m --relative-to=${super} ${dir}) - mv ${dir}/.git ${super}/.git/modules/${relpathfromsuper} - echo "gitdir: ${pathtodotgit}/modules/${relpathfromsuper}" > ${dir}/.git - for path in $* ;do - echo "${path}" >> ${super}/.git/modules/${relpathfromsuper}/info/sparse-checkout - done - git -C "${dir}" fetch ${opts} origin ${tag} - git -C "${dir}" checkout ${tag} -} - -git_sparse_checkout $@ diff --git a/modules/gitmodules.py b/modules/gitmodules.py new file mode 100644 index 00000000..3ef3ed87 --- /dev/null +++ b/modules/gitmodules.py @@ -0,0 +1,53 @@ +import os +from configparser import ConfigParser + +from modules.lstripreader import LstripReader + + +class GitModules(ConfigParser.ConfigParser): + def __init__( + self, + confpath=os.getcwd(), + conffile=".gitmodules", + includelist=None, + excludelist=None, + ): + ConfigParser.ConfigParser.__init__(self) + self.read_file(LstripReader(confpath), source=conffile) + self.conf_file = os.path.join(confpath, conffile) + self.includelist = includelist + self.excludelist = excludelist + + def set(self, name, option, value): + section = f'submodule "{name}"' + if not self.has_section(section): + self.add_section(section) + ConfigParser.ConfigParser.set(self, section, option, str(value)) + + def get(self, name, option): + section = f'submodule "{name}"' + try: + return ConfigParser.ConfigParser.get(self, section, option) + except ConfigParser.NoOptionError: + return None + + def save(self): + self.write(open(self.conf_file, "w")) + + def __del__(self): + self.save() + + def sections(self): + names = [] + for section in ConfigParser.ConfigParser.sections(self): + name = section[11:-1] + if self.includelist and name not in self.includelist: + continue + if self.excludelist and name in self.excludelist: + continue + names.append(name) + return names + + def items(self, name): + section = f'submodule "{name}"' + return ConfigParser.ConfigParser.items(section) diff --git a/modules/utils.py b/modules/utils.py index 82715387..41c6bb25 100644 --- a/modules/utils.py +++ b/modules/utils.py @@ -10,12 +10,24 @@ import sys from threading import Timer -LOCAL_PATH_INDICATOR = '.' +LOCAL_PATH_INDICATOR = "." # --------------------------------------------------------------------- # -# screen and logging output and functions to massage text for output +# functions to massage text for output and other useful utilities # # --------------------------------------------------------------------- +from contextlib import contextmanager + + +@contextmanager +def pushd(new_dir): + """context for chdir. usage: with pushd(new_dir)""" + previous_dir = os.getcwd() + os.chdir(new_dir) + try: + yield + finally: + os.chdir(previous_dir) def log_process_output(output): @@ -25,7 +37,7 @@ def log_process_output(output): line. This makes it hard to filter with grep. """ - output = output.split('\n') + output = output.split("\n") for line in output: logging.debug(line) @@ -63,9 +75,9 @@ def last_n_lines(the_string, n_lines, truncation_message=None): return_val = the_string else: lines_subset = lines[-n_lines:] - str_truncated = ''.join(lines_subset) + str_truncated = "".join(lines_subset) if truncation_message: - str_truncated = truncation_message + '\n' + str_truncated + str_truncated = truncation_message + "\n" + str_truncated return_val = str_truncated return return_val @@ -85,9 +97,10 @@ def indent_string(the_string, indent_level): """ lines = the_string.splitlines(True) - padding = ' ' * indent_level + padding = " " * indent_level lines_indented = [padding + line for line in lines] - return ''.join(lines_indented) + return "".join(lines_indented) + # --------------------------------------------------------------------- # @@ -116,24 +129,26 @@ def str_to_bool(bool_str): """ value = None str_lower = bool_str.lower() - if str_lower in ('true', 't'): + if str_lower in ("true", "t"): value = True - elif str_lower in ('false', 'f'): + elif str_lower in ("false", "f"): value = False if value is None: - msg = ('ERROR: invalid boolean string value "{0}". ' - 'Must be "true" or "false"'.format(bool_str)) + msg = ( + 'ERROR: invalid boolean string value "{0}". ' + 'Must be "true" or "false"'.format(bool_str) + ) fatal_error(msg) return value -REMOTE_PREFIXES = ['http://', 'https://', 'ssh://', 'git@'] +REMOTE_PREFIXES = ["http://", "https://", "ssh://", "git@"] def is_remote_url(url): """check if the user provided a local file path instead of a - remote. If so, it must be expanded to an absolute - path. + remote. If so, it must be expanded to an absolute + path. """ remote_url = False @@ -145,7 +160,7 @@ def is_remote_url(url): def split_remote_url(url): """check if the user provided a local file path or a - remote. If remote, try to strip off protocol info. + remote. If remote, try to strip off protocol info. """ remote_url = is_remote_url(url) @@ -153,13 +168,13 @@ def split_remote_url(url): return url for prefix in REMOTE_PREFIXES: - url = url.replace(prefix, '') + url = url.replace(prefix, "") - if '@' in url: - url = url.split('@')[1] + if "@" in url: + url = url.split("@")[1] - if ':' in url: - url = url.split(':')[1] + if ":" in url: + url = url.split(":")[1] return url @@ -181,10 +196,12 @@ def expand_local_url(url, field): url = os.path.expandvars(url) url = os.path.expanduser(url) if not os.path.isabs(url): - msg = ('WARNING: Externals description for "{0}" contains a ' - 'url that is not remote and does not expand to an ' - 'absolute path. Version control operations may ' - 'fail.\n\nurl={1}'.format(field, url)) + msg = ( + 'WARNING: Externals description for "{0}" contains a ' + "url that is not remote and does not expand to an " + "absolute path. Version control operations may " + "fail.\n\nurl={1}".format(field, url) + ) printlog(msg) else: url = os.path.normpath(url) @@ -203,7 +220,8 @@ def expand_local_url(url, field): def _hanging_msg(working_directory, command): - print(""" + print( + """ Command '{command}' from directory {working_directory} @@ -217,13 +235,15 @@ def _hanging_msg(working_directory, command): will appear to hang. Ensure you can run svn and git manually and access all repositories without entering your authentication information. -""".format(command=command, - working_directory=working_directory, - hanging_sec=_HANGING_SEC)) +""".format( + command=command, + working_directory=working_directory, + hanging_sec=_HANGING_SEC, + ) + ) -def execute_subprocess(commands, status_to_caller=False, - output_to_caller=False): +def execute_subprocess(commands, status_to_caller=False, output_to_caller=False): """Wrapper around subprocess.check_output to handle common exceptions. @@ -237,32 +257,35 @@ def execute_subprocess(commands, status_to_caller=False, """ cwd = os.getcwd() - msg = 'In directory: {0}\nexecute_subprocess running command:'.format(cwd) + msg = "In directory: {0}\nexecute_subprocess running command:".format(cwd) logging.info(msg) - commands_str = ' '.join(commands) + commands_str = " ".join(commands) logging.info(commands_str) return_to_caller = status_to_caller or output_to_caller status = -1 - output = '' - hanging_timer = Timer(_HANGING_SEC, _hanging_msg, - kwargs={"working_directory": cwd, - "command": commands_str}) + output = "" + hanging_timer = Timer( + _HANGING_SEC, + _hanging_msg, + kwargs={"working_directory": cwd, "command": commands_str}, + ) hanging_timer.start() try: - output = subprocess.check_output(commands, stderr=subprocess.STDOUT, - universal_newlines=True) + output = subprocess.check_output( + commands, stderr=subprocess.STDOUT, universal_newlines=True + ) log_process_output(output) status = 0 except OSError as error: msg = failed_command_msg( - 'Command execution failed. Does the executable exist?', - commands) + "Command execution failed. Does the executable exist?", commands + ) logging.error(error) fatal_error(msg) except ValueError as error: msg = failed_command_msg( - 'DEV_ERROR: Invalid arguments trying to run subprocess', - commands) + "DEV_ERROR: Invalid arguments trying to run subprocess", commands + ) logging.error(error) fatal_error(msg) except subprocess.CalledProcessError as error: @@ -272,10 +295,11 @@ def execute_subprocess(commands, status_to_caller=False, # responsibility determine if an error occurred and handle it # appropriately. if not return_to_caller: - msg_context = ('Process did not run successfully; ' - 'returned status {0}'.format(error.returncode)) - msg = failed_command_msg(msg_context, commands, - output=error.output) + msg_context = ( + "Process did not run successfully; " + "returned status {0}".format(error.returncode) + ) + msg = failed_command_msg(msg_context, commands, output=error.output) logging.error(error) logging.error(msg) log_process_output(error.output) @@ -304,22 +328,25 @@ def failed_command_msg(msg_context, command, output=None): """ if output: - output_truncated = last_n_lines(output, 20, - truncation_message='[... Output truncated for brevity ...]') - errmsg = ('Failed with output:\n' + - indent_string(output_truncated, 4) + - '\nERROR: ') + output_truncated = last_n_lines( + output, 20, truncation_message="[... Output truncated for brevity ...]" + ) + errmsg = ( + "Failed with output:\n" + indent_string(output_truncated, 4) + "\nERROR: " + ) else: - errmsg = '' + errmsg = "" - command_str = ' '.join(command) + command_str = " ".join(command) errmsg += """In directory {cwd} {context}: {command} -""".format(cwd=os.getcwd(), context=msg_context, command=command_str) +""".format( + cwd=os.getcwd(), context=msg_context, command=command_str + ) if output: - errmsg += 'See above for output from failed command.\n' + errmsg += "See above for output from failed command.\n" return errmsg diff --git a/show-tags b/show-tags deleted file mode 100755 index 56c7d9c5..00000000 --- a/show-tags +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -this_dir=$(pwd) -printf "\nSubmodule status\n" -printf "(currently checked out commit for each submodule)\n" -printf "(when the submodule is initialized and a tag exists, the commit is shown as: 'most recent tag-commits since tag-commit hash')\n" -printf "(when the submodule is not initialized, only the checked out commit is shown)\n\n" -grep path .gitmodules | sed 's/.*= //' | while read x -do - cd "$this_dir" - printf "$x\n - current commit: " - if [ "$(ls -A $x)" ] ; then - cd "$x" - git describe --tags --always - else - git submodule status $x | sed 's/^-//' | awk '{ print $1 }' - fi -done -printf "\n" - From 815e6cd0d9f00fdf1cd06bd67ca912006c8d3a46 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sat, 6 Jan 2024 10:34:47 -0700 Subject: [PATCH 019/159] now renamed git-fleximod for git interface --- git-fleximod.py => git-fleximod | 24 ++++++++++++++++++------ modules/gitmodules.py | 21 ++++++++++++--------- 2 files changed, 30 insertions(+), 15 deletions(-) rename git-fleximod.py => git-fleximod (92%) mode change 100644 => 100755 diff --git a/git-fleximod.py b/git-fleximod old mode 100644 new mode 100755 similarity index 92% rename from git-fleximod.py rename to git-fleximod index ded13eb9..00fc2446 --- a/git-fleximod.py +++ b/git-fleximod @@ -8,6 +8,9 @@ from modules.gitinterface import GitInterface from modules.gitmodules import GitModules +logger = logging.getLogger(__name__) +logger.propogate = False + def commandline_arguments(args=None): description = """ @@ -135,9 +138,9 @@ def commandline_arguments(args=None): options = parser.parse_args() if options.optional: - esmrequired = ("T:T", "T:F", "I:T") + esmrequired = ["T:T", "T:F", "I:T"] else: - esmrequired = ("T:T", "I:T") + esmrequired = ["T:T", "I:T"] if options.status: action = "status" @@ -177,7 +180,7 @@ def submodule_sparse_checkout(name, url, path, sparsefile, tag="master"): topgit = os.path.join(topdir, ".git", "modules") gitsparse = os.path.join(topgit, name, "info", "sparse-checkout") if os.path.isfile(gitsparse): - logging.warning("submodule {} is already initialized".format(name)) + logger.warning("submodule {} is already initialized".format(name)) return # initialize a new git repo and set the sparse checkout flag @@ -213,7 +216,7 @@ def submodule_checkout(root, name, path): # recursively handle this checkout print(f"Recursively checking out submodules of {name} {repodir}") gitmodules = GitModules(repodir) - submodules_install(gitmodules, repodir, ("I:T")) + submodules_install(gitmodules, repodir, ["I:T"]) if os.path.exists(os.path.join(repodir, ".git")): print(f"Successfully checked out {name}") else: @@ -253,19 +256,27 @@ def submodules_update(gitmodules, root_dir): def submodules_install(gitmodules, root_dir, requiredlist): + print(gitmodules.sections()) for name in gitmodules.sections(): esmrequired = gitmodules.get(name, "esmrequired") esmsparse = gitmodules.get(name, "esmsparse") esmtag = gitmodules.get(name, "esmtag") path = gitmodules.get(name, "path") url = gitmodules.get(name, "url") - if esmrequired not in requiredlist: - if "T:F" in esmrequired: + + if esmrequired and esmrequired not in requiredlist: + if "T:F" == esmrequired: print("Skipping optional component {}".format(name)) continue if esmsparse: + logger.debug( + f"Callng submodule_sparse_checkout({name} {url}, {path}, {esmsparse}, {esmtag}" + ) submodule_sparse_checkout(name, url, path, esmsparse, tag=esmtag) else: + logger.debug( + "Calling submodule_checkout({},{},{})".format(root_dir, name, path) + ) submodule_checkout(root_dir, name, path) @@ -281,6 +292,7 @@ def _main_func(): ) = commandline_arguments() if verbose: print(f"action is {action}") + gitmodules = GitModules( confpath=root_dir, conffile=file_name, diff --git a/modules/gitmodules.py b/modules/gitmodules.py index 3ef3ed87..28586292 100644 --- a/modules/gitmodules.py +++ b/modules/gitmodules.py @@ -4,7 +4,7 @@ from modules.lstripreader import LstripReader -class GitModules(ConfigParser.ConfigParser): +class GitModules(ConfigParser): def __init__( self, confpath=os.getcwd(), @@ -12,9 +12,9 @@ def __init__( includelist=None, excludelist=None, ): - ConfigParser.ConfigParser.__init__(self) - self.read_file(LstripReader(confpath), source=conffile) + ConfigParser.__init__(self) self.conf_file = os.path.join(confpath, conffile) + self.read_file(LstripReader(self.conf_file), source=conffile) self.includelist = includelist self.excludelist = excludelist @@ -22,12 +22,15 @@ def set(self, name, option, value): section = f'submodule "{name}"' if not self.has_section(section): self.add_section(section) - ConfigParser.ConfigParser.set(self, section, option, str(value)) + ConfigParser.set(self, section, option, str(value)) - def get(self, name, option): + # pylint: disable=redefined-builtin, arguments-differ + def get(self, name, option, raw=False, vars=None, fallback=None): section = f'submodule "{name}"' try: - return ConfigParser.ConfigParser.get(self, section, option) + return ConfigParser.get( + self, section, option, raw=raw, vars=vars, fallback=fallback + ) except ConfigParser.NoOptionError: return None @@ -39,7 +42,7 @@ def __del__(self): def sections(self): names = [] - for section in ConfigParser.ConfigParser.sections(self): + for section in ConfigParser.sections(self): name = section[11:-1] if self.includelist and name not in self.includelist: continue @@ -48,6 +51,6 @@ def sections(self): names.append(name) return names - def items(self, name): + def items(self, name, raw=False, vars=None): section = f'submodule "{name}"' - return ConfigParser.ConfigParser.items(section) + return ConfigParser.items(section, raw=raw, vars=vars) From a6ce960bc9d102007608ab7dde8b9fd4ad590dec Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sat, 6 Jan 2024 11:41:25 -0700 Subject: [PATCH 020/159] clean up and refactor, rename to git-fleximod --- git-fleximod | 48 ++++++++++++++++++++++++++++++++++-------------- modules/utils.py | 13 +++++++++++++ 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/git-fleximod b/git-fleximod index 00fc2446..44a86c50 100755 --- a/git-fleximod +++ b/git-fleximod @@ -168,16 +168,14 @@ def commandline_arguments(args=None): ) -def submodule_sparse_checkout(name, url, path, sparsefile, tag="master"): +def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master"): # first create the module directory if not os.path.isdir(path): os.makedirs(path) # Check first if the module is already defined # and the sparse-checkout file exists - git = GitInterface(os.getcwd()) - topdir = git.git_operation("rev-parse", "--show-toplevel").rstrip() - - topgit = os.path.join(topdir, ".git", "modules") + git = GitInterface(root_dir) + topgit = os.path.join(root_dir, ".git", "modules") gitsparse = os.path.join(topgit, name, "info", "sparse-checkout") if os.path.isfile(gitsparse): logger.warning("submodule {} is already initialized".format(name)) @@ -227,7 +225,7 @@ def submodule_checkout(root, name, path): def submodules_status(gitmodules, root_dir): for name in gitmodules.sections(): path = gitmodules.get(name, "path") - tag = gitmodules.git(name, "esmtag") + tag = gitmodules.get(name, "esmtag") with utils.pushd(path): git = GitInterface(os.path.join(root_dir, path)) atag = git.git_operation("describe", "--tags", "--always").rstrip() @@ -236,7 +234,12 @@ def submodules_status(gitmodules, root_dir): elif tag: print(f"Submodule {name} at tag {tag}") else: - print(f"Submodule {name} has no tag defined in .gitmodules") + print( + f"Submodule {name} has no tag defined in .gitmodules, module at {atag}" + ) + status = git.git_operation("status") + if "nothing to commit" not in status: + print(status) def submodules_update(gitmodules, root_dir): @@ -245,18 +248,26 @@ def submodules_update(gitmodules, root_dir): path = gitmodules.get(name, "path") url = gitmodules.get(name, "url") if os.path.exists(os.path.join(path, ".git")): - with utils.pushd(root_dir): - git = GitInterface(root_dir) + submoddir = os.path.join(root_dir, path) + with utils.pushd(submoddir): + git = GitInterface(submoddir) # first make sure the url is correct upstream = git.git_operation("ls-remote", "--get-url").rstrip() + newremote = "origin" if upstream != url: # TODO - this needs to be a unique name - git.git_operation("remote", "add", "newbranch", url) - git.git_operation("checkout", esmtag) + remotes = git.git_operation("remote", "-v") + newremote = "newremote" + git.git_operation("remote", "add", newremote, url) + + tags = git.git_operation("tag", "-l") + if esmtag not in tags: + git.git_operation("fetch", newremote, "--tags") + + git.git_operation("checkout", esmtag) def submodules_install(gitmodules, root_dir, requiredlist): - print(gitmodules.sections()) for name in gitmodules.sections(): esmrequired = gitmodules.get(name, "esmrequired") esmsparse = gitmodules.get(name, "esmsparse") @@ -270,9 +281,9 @@ def submodules_install(gitmodules, root_dir, requiredlist): continue if esmsparse: logger.debug( - f"Callng submodule_sparse_checkout({name} {url}, {path}, {esmsparse}, {esmtag}" + f"Callng submodule_sparse_checkout({root_dir}, {name}, {url}, {path}, {esmsparse}, {esmtag}" ) - submodule_sparse_checkout(name, url, path, esmsparse, tag=esmtag) + submodule_sparse_checkout(root_dir, name, url, path, esmsparse, tag=esmtag) else: logger.debug( "Calling submodule_checkout({},{},{})".format(root_dir, name, path) @@ -293,6 +304,15 @@ def _main_func(): if verbose: print(f"action is {action}") + if not os.path.isfile(os.path.join(root_dir, file_name)): + file_path = utils.find_upwards(root_dir, file_name) + + if file_path is None: + fatal_error( + "No {} found in {} or any of it's parents".format(file_name, root_dir) + ) + root_dir = os.path.dirname(file_path) + print(f"root_dir is {root_dir}") gitmodules = GitModules( confpath=root_dir, conffile=file_name, diff --git a/modules/utils.py b/modules/utils.py index 41c6bb25..f0753367 100644 --- a/modules/utils.py +++ b/modules/utils.py @@ -9,6 +9,7 @@ import subprocess import sys from threading import Timer +from pathlib import Path LOCAL_PATH_INDICATOR = "." # --------------------------------------------------------------------- @@ -55,6 +56,18 @@ def printlog(msg, **kwargs): sys.stdout.flush() +def find_upwards(root_dir, filename): + """Find a file in root dir or any of it's parents""" + d = Path(root_dir) + root = Path(d.root) + while d != root: + attempt = d / filename + if attempt.exists(): + return attempt + d = d.parent + return None + + def last_n_lines(the_string, n_lines, truncation_message=None): """Returns the last n lines of the given string From 816158ceb02abca2b360811beba25a5c1b98c5e2 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sat, 6 Jan 2024 12:11:04 -0700 Subject: [PATCH 021/159] add version.txt and .pre-commit-config.yaml --- .pre-commit-config.yaml | 18 ++++++++++++++++++ version.txt | 1 + 2 files changed, 19 insertions(+) create mode 100644 .pre-commit-config.yaml create mode 100644 version.txt diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..2f6089da --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,18 @@ +exclude: ^utils/.*$ + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.0.1 + hooks: + - id: end-of-file-fixer + - id: trailing-whitespace + - repo: https://github.com/psf/black + rev: 22.3.0 + hooks: + - id: black + - repo: https://github.com/PyCQA/pylint + rev: v2.11.1 + hooks: + - id: pylint + args: + - --disable=I,C,R,logging-not-lazy,wildcard-import,unused-wildcard-import,fixme,broad-except,bare-except,eval-used,exec-used,global-statement,logging-format-interpolation,no-name-in-module,arguments-renamed,unspecified-encoding,protected-access,import-error,no-member diff --git a/version.txt b/version.txt new file mode 100644 index 00000000..a3dce6cd --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +v0.0.2 From 3be6537e39f7d9209be9b66b36756da022455612 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sat, 6 Jan 2024 15:21:15 -0700 Subject: [PATCH 022/159] fix issue in update --- git-fleximod | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/git-fleximod b/git-fleximod index 44a86c50..e5978cff 100755 --- a/git-fleximod +++ b/git-fleximod @@ -257,11 +257,20 @@ def submodules_update(gitmodules, root_dir): if upstream != url: # TODO - this needs to be a unique name remotes = git.git_operation("remote", "-v") - newremote = "newremote" - git.git_operation("remote", "add", newremote, url) + if url in remotes: + for line in remotes: + if url in line and "fetch" in line: + newremote = line.split()[0] + break + else: + i = 0 + while newremote in remotes: + i = i + 1 + newremote = f"newremote.{i:02d}" + git.git_operation("remote", "add", newremote, url) tags = git.git_operation("tag", "-l") - if esmtag not in tags: + if esmtag and esmtag not in tags: git.git_operation("fetch", newremote, "--tags") git.git_operation("checkout", esmtag) From d709c124c284c1ad007caed586db93914d78be47 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sat, 6 Jan 2024 15:47:38 -0700 Subject: [PATCH 023/159] make action a required subargument to look more like a git subcommand --- git-fleximod | 38 +++++++++++++------------------------- modules/gitmodules.py | 3 --- 2 files changed, 13 insertions(+), 28 deletions(-) diff --git a/git-fleximod b/git-fleximod index e5978cff..d13f1b9f 100755 --- a/git-fleximod +++ b/git-fleximod @@ -23,6 +23,14 @@ def commandline_arguments(args=None): # # user options # + choices = ["update", "install", "status"] + parser.add_argument( + "action", + choices=choices, + default="install", + help=f"Subcommand of fleximod, choices are {choices}", + ) + parser.add_argument( "components", nargs="*", @@ -62,24 +70,6 @@ def commandline_arguments(args=None): "optional submodules relative to the toplevel directory.", ) - parser.add_argument( - "-S", - "--status", - action="store_true", - default=False, - help="Output the status of the repositories managed by " - "%(prog)s. By default only summary information " - "is provided. Use the verbose option to see details.", - ) - - parser.add_argument( - "-u", - "--update", - action="store_true", - default=False, - help="Update submodules to the tags defined in .gitmodules.", - ) - parser.add_argument( "-v", "--verbose", @@ -142,11 +132,8 @@ def commandline_arguments(args=None): else: esmrequired = ["T:T", "I:T"] - if options.status: - action = "status" - elif options.update: - action = "update" - else: + action = options.action + if not action: action = "install" if options.version: @@ -226,8 +213,9 @@ def submodules_status(gitmodules, root_dir): for name in gitmodules.sections(): path = gitmodules.get(name, "path") tag = gitmodules.get(name, "esmtag") - with utils.pushd(path): - git = GitInterface(os.path.join(root_dir, path)) + newpath = os.path.join(root_dir, path) + with utils.pushd(newpath): + git = GitInterface(newpath) atag = git.git_operation("describe", "--tags", "--always").rstrip() if tag and atag != tag: print(f"Submodule {name} {atag} is out of sync with .gitmodules {tag}") diff --git a/modules/gitmodules.py b/modules/gitmodules.py index 28586292..5af10be4 100644 --- a/modules/gitmodules.py +++ b/modules/gitmodules.py @@ -37,9 +37,6 @@ def get(self, name, option, raw=False, vars=None, fallback=None): def save(self): self.write(open(self.conf_file, "w")) - def __del__(self): - self.save() - def sections(self): names = [] for section in ConfigParser.sections(self): From 83aea04a4366e246e5a107e88ef29aa347657e24 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sat, 6 Jan 2024 16:02:03 -0700 Subject: [PATCH 024/159] add documentation --- modules/gitmodules.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/modules/gitmodules.py b/modules/gitmodules.py index 5af10be4..9e38a580 100644 --- a/modules/gitmodules.py +++ b/modules/gitmodules.py @@ -12,6 +12,12 @@ def __init__( includelist=None, excludelist=None, ): + """ + confpath: Path to the directory containing the .gitmodules file (defaults to the current working directory). + conffile: Name of the configuration file (defaults to .gitmodules). + includelist: Optional list of submodules to include. + excludelist: Optional list of submodules to exclude. + """ ConfigParser.__init__(self) self.conf_file = os.path.join(confpath, conffile) self.read_file(LstripReader(self.conf_file), source=conffile) @@ -19,6 +25,11 @@ def __init__( self.excludelist = excludelist def set(self, name, option, value): + """ + Sets a configuration value for a specific submodule: + Ensures the appropriate section exists for the submodule. + Calls the parent class's set method to store the value. + """ section = f'submodule "{name}"' if not self.has_section(section): self.add_section(section) @@ -26,6 +37,11 @@ def set(self, name, option, value): # pylint: disable=redefined-builtin, arguments-differ def get(self, name, option, raw=False, vars=None, fallback=None): + """ + Retrieves a configuration value for a specific submodule: + Uses the parent class's get method to access the value. + Handles potential errors if the section or option doesn't exist. + """ section = f'submodule "{name}"' try: return ConfigParser.get( @@ -38,6 +54,7 @@ def save(self): self.write(open(self.conf_file, "w")) def sections(self): + """Strip the submodule part out of section and just use the name""" names = [] for section in ConfigParser.sections(self): name = section[11:-1] From 905824aefcd187a25f4133826bd0a0a76edcfcdd Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sun, 7 Jan 2024 09:09:23 -0700 Subject: [PATCH 025/159] add README and License --- License | 20 ++++++++++++++++++++ README.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 License create mode 100644 README.md diff --git a/License b/License new file mode 100644 index 00000000..2c6fe768 --- /dev/null +++ b/License @@ -0,0 +1,20 @@ +Copyright 2024 National Center for Atmospheric Sciences (NCAR) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +“Software”), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..4ee56bc4 --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# git-fleximod + +Flexible Submodule Management for Git + +## Overview + +Git-fleximod is a Python-based tool that extends Git's submodule capabilities, offering additional features for managing submodules in a more flexible and efficient way. + +## Installation + + Install using pip: + pip install git-fleximod + +## Usage + + Basic Usage: + git fleximod [options] + Available Commands: + install: Install submodules according to configuration. + status: Display the status of submodules. + update: Update submodules to their latest commits. + Additional Options: + See git fleximod --help for more details. + +## Supported .gitmodules Variables + + fxtag: Specify a specific tag or branch to checkout for a submodule. + fxrequired: Mark a submodule's checkout behavior, with allowed values: + - T:T: Top-level and required (checked out only when this is the Toplevel module). + - T:F: Top-level and optional (checked out with --optional flag if this is the Toplevel module). + - I:T: Internal and required (always checked out). + - I:F: Internal and optional (checked out with --optional flag). + fxsparse: Enable sparse checkout for a submodule, pointing to a file containing sparse checkout paths. + +## Sparse Checkouts + + To enable sparse checkout for a submodule, set the fxsparse variable + in the .gitmodules file to the path of a file containing the desired + sparse checkout paths. Git-fleximod will automatically configure + sparse checkout based on this file when applicable commands are run. + +## Examples + + Installing submodules with optional ones: git fleximod install --optional + Checking out a specific tag for a submodule: git fleximod update --fxtag=v1.2.3 submodule-name + +## Contributing + +We welcome contributions! Please see the CONTRIBUTING.md file for guidelines. + +## License + +Git-fleximod is released under the MIT License. From 3c723a86b39db2274c223e4a8ffb1d83f5b01ec1 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sun, 7 Jan 2024 10:56:42 -0700 Subject: [PATCH 026/159] update README --- README.md | 34 +++++++++++++++++++++++++++++----- git-fleximod | 39 +++++++++++++++++++++------------------ 2 files changed, 50 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 4ee56bc4..da725ab5 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,9 @@ Git-fleximod is a Python-based tool that extends Git's submodule capabilities, o ## Installation - Install using pip: - pip install git-fleximod +#TODO Install using pip: +# pip install git-fleximod + If you choose to locate git-fleximod in your path you can access it via command: git fleximod ## Usage @@ -18,7 +19,7 @@ Git-fleximod is a Python-based tool that extends Git's submodule capabilities, o Available Commands: install: Install submodules according to configuration. status: Display the status of submodules. - update: Update submodules to their latest commits. + update: Update submodules to the tag indicated in .gitmodules variable fxtag. Additional Options: See git fleximod --help for more details. @@ -38,11 +39,34 @@ Git-fleximod is a Python-based tool that extends Git's submodule capabilities, o in the .gitmodules file to the path of a file containing the desired sparse checkout paths. Git-fleximod will automatically configure sparse checkout based on this file when applicable commands are run. + See [git-sparse-checkout](https://git-scm.com/docs/git-sparse-checkout#_internalsfull_pattern_set) for details on the format of this file. ## Examples - Installing submodules with optional ones: git fleximod install --optional - Checking out a specific tag for a submodule: git fleximod update --fxtag=v1.2.3 submodule-name + Installing submodules including optional ones: git fleximod install --optional + Update a specific submodule to the fxtag indicated in .gitmodules: git fleximod update submodule-name + Example .gitmodules entry: + [submodule "cosp2"] + path = src/physics/cosp2/src + url = https://github.com/CFMIP/COSPv2.0 + fxsparse = ../.cosp_sparse_checkout + fxtag = v2.1.4cesm + + This indicates that submodule named cosp2 at tag v2.1.4cesm should + be checked out into directory src/physics/cosp2/src relative to the + .gitmodules directory from the given url and that it should use the + sparse checkout as described in file ../.cosp_sparse_checkout relative + to the path directory. + + [submodule "cime"] + path = cime + url = https://github.com/jedwards4b/cime + fxrequired = T:T + fxtag = cime6.0.198_rme01 + + This indicates that submodule cime should be checked out into a directory cime + at tag cime6.0.198_rme01 from url https://github.com/jedwards4b/cime, this should + only be done if this .gitmodules file is at the TopLevel of the repository clone. ## Contributing diff --git a/git-fleximod b/git-fleximod index d13f1b9f..f03984f8 100755 --- a/git-fleximod +++ b/git-fleximod @@ -128,9 +128,9 @@ def commandline_arguments(args=None): options = parser.parse_args() if options.optional: - esmrequired = ["T:T", "T:F", "I:T"] + fxrequired = ["T:T", "T:F", "I:T"] else: - esmrequired = ["T:T", "I:T"] + fxrequired = ["T:T", "I:T"] action = options.action if not action: @@ -147,7 +147,7 @@ def commandline_arguments(args=None): return ( options.path, options.gitmodules, - esmrequired, + fxrequired, options.components, options.exclude, options.verbose, @@ -212,7 +212,7 @@ def submodule_checkout(root, name, path): def submodules_status(gitmodules, root_dir): for name in gitmodules.sections(): path = gitmodules.get(name, "path") - tag = gitmodules.get(name, "esmtag") + tag = gitmodules.get(name, "fxtag") newpath = os.path.join(root_dir, path) with utils.pushd(newpath): git = GitInterface(newpath) @@ -232,9 +232,10 @@ def submodules_status(gitmodules, root_dir): def submodules_update(gitmodules, root_dir): for name in gitmodules.sections(): - esmtag = gitmodules.get(name, "esmtag") + fxtag = gitmodules.get(name, "fxtag") path = gitmodules.get(name, "path") url = gitmodules.get(name, "url") + print(f"name={name} path={path} url={url} fxtag={fxtag}") if os.path.exists(os.path.join(path, ".git")): submoddir = os.path.join(root_dir, path) with utils.pushd(submoddir): @@ -258,29 +259,31 @@ def submodules_update(gitmodules, root_dir): git.git_operation("remote", "add", newremote, url) tags = git.git_operation("tag", "-l") - if esmtag and esmtag not in tags: + if fxtag and fxtag not in tags: git.git_operation("fetch", newremote, "--tags") - - git.git_operation("checkout", esmtag) + if fxtag: + git.git_operation("checkout", fxtag) + else: + print(f"No fxtag found for submodule {name}") def submodules_install(gitmodules, root_dir, requiredlist): for name in gitmodules.sections(): - esmrequired = gitmodules.get(name, "esmrequired") - esmsparse = gitmodules.get(name, "esmsparse") - esmtag = gitmodules.get(name, "esmtag") + fxrequired = gitmodules.get(name, "fxrequired") + fxsparse = gitmodules.get(name, "fxsparse") + fxtag = gitmodules.get(name, "fxtag") path = gitmodules.get(name, "path") url = gitmodules.get(name, "url") - if esmrequired and esmrequired not in requiredlist: - if "T:F" == esmrequired: + if fxrequired and fxrequired not in requiredlist: + if "T:F" == fxrequired: print("Skipping optional component {}".format(name)) continue - if esmsparse: + if fxsparse: logger.debug( - f"Callng submodule_sparse_checkout({root_dir}, {name}, {url}, {path}, {esmsparse}, {esmtag}" + f"Callng submodule_sparse_checkout({root_dir}, {name}, {url}, {path}, {fxsparse}, {fxtag}" ) - submodule_sparse_checkout(root_dir, name, url, path, esmsparse, tag=esmtag) + submodule_sparse_checkout(root_dir, name, url, path, fxsparse, tag=fxtag) else: logger.debug( "Calling submodule_checkout({},{},{})".format(root_dir, name, path) @@ -292,7 +295,7 @@ def _main_func(): ( root_dir, file_name, - esmrequired, + fxrequired, includelist, excludelist, verbose, @@ -320,7 +323,7 @@ def _main_func(): if action == "update": submodules_update(gitmodules, root_dir) elif action == "install": - submodules_install(gitmodules, root_dir, esmrequired) + submodules_install(gitmodules, root_dir, fxrequired) elif action == "status": submodules_status(gitmodules, root_dir) else: From 3903206397dfebfd0ca2f02a53761466b79cfc89 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sun, 7 Jan 2024 11:05:00 -0700 Subject: [PATCH 027/159] trying to improve markdown formatting --- README.md | 71 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index da725ab5..c5a885bd 100644 --- a/README.md +++ b/README.md @@ -43,30 +43,53 @@ Git-fleximod is a Python-based tool that extends Git's submodule capabilities, o ## Examples - Installing submodules including optional ones: git fleximod install --optional - Update a specific submodule to the fxtag indicated in .gitmodules: git fleximod update submodule-name - Example .gitmodules entry: - [submodule "cosp2"] - path = src/physics/cosp2/src - url = https://github.com/CFMIP/COSPv2.0 - fxsparse = ../.cosp_sparse_checkout - fxtag = v2.1.4cesm - - This indicates that submodule named cosp2 at tag v2.1.4cesm should - be checked out into directory src/physics/cosp2/src relative to the - .gitmodules directory from the given url and that it should use the - sparse checkout as described in file ../.cosp_sparse_checkout relative - to the path directory. - - [submodule "cime"] - path = cime - url = https://github.com/jedwards4b/cime - fxrequired = T:T - fxtag = cime6.0.198_rme01 - - This indicates that submodule cime should be checked out into a directory cime - at tag cime6.0.198_rme01 from url https://github.com/jedwards4b/cime, this should - only be done if this .gitmodules file is at the TopLevel of the repository clone. +Here are some common usage examples: + + Installing submodules, including optional ones: + Bash + + git fleximod install --optional + + +Updating a specific submodule to the fxtag indicated in .gitmodules: + +Bash + +git fleximod update submodule-name + +Example .gitmodules entry: +Ini, TOML + +[submodule "cosp2"] + path = src/physics/cosp2/src + url = https://github.com/CFMIP/COSPv2.0 + fxsparse = ../.cosp_sparse_checkout + fxtag = v2.1.4cesm + +Explanation: + + This entry indicates that the submodule named cosp2 at tag + v2.1.4cesm should be checked out into the directory + src/physics/cosp2/src relative to the .gitmodules directory. It + should be checked out from the URL + https://github.com/CFMIP/COSPv2.0 and use sparse checkout as + described in the file ../.cosp_sparse_checkout relative to the + path directory. + +Additional example: +Ini, TOML + +[submodule "cime"] + path = cime + url = https://github.com/jedwards4b/cime + fxrequired = T:T + fxtag = cime6.0.198_rme01 + + +Explanation: + + This entry indicates that the submodule cime should be checked out into a directory named cime at tag cime6.0.198_rme01 from the URL https://github.com/jedwards4b/cime. + This should only be done if the .gitmodules file is at the top level of the repository clone. ## Contributing From 8bd89cf9b7bdd710b0aa23def9a4b2f21c047b84 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sun, 7 Jan 2024 11:09:47 -0700 Subject: [PATCH 028/159] trying to improve markdown --- README.md | 66 +++++++++++++++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index c5a885bd..f3a0f889 100644 --- a/README.md +++ b/README.md @@ -46,27 +46,24 @@ Git-fleximod is a Python-based tool that extends Git's submodule capabilities, o Here are some common usage examples: Installing submodules, including optional ones: - Bash - + ```Bash git fleximod install --optional - - -Updating a specific submodule to the fxtag indicated in .gitmodules: - -Bash - -git fleximod update submodule-name - -Example .gitmodules entry: -Ini, TOML - -[submodule "cosp2"] - path = src/physics/cosp2/src - url = https://github.com/CFMIP/COSPv2.0 - fxsparse = ../.cosp_sparse_checkout - fxtag = v2.1.4cesm - -Explanation: + ``` + + Updating a specific submodule to the fxtag indicated in .gitmodules: + + ```Bash + git fleximod update submodule-name + ``` + Example .gitmodules entry: + ```Ini, TOML + [submodule "cosp2"] + path = src/physics/cosp2/src + url = https://github.com/CFMIP/COSPv2.0 + fxsparse = ../.cosp_sparse_checkout + fxtag = v2.1.4cesm + ``` + Explanation: This entry indicates that the submodule named cosp2 at tag v2.1.4cesm should be checked out into the directory @@ -76,20 +73,21 @@ Explanation: described in the file ../.cosp_sparse_checkout relative to the path directory. -Additional example: -Ini, TOML - -[submodule "cime"] - path = cime - url = https://github.com/jedwards4b/cime - fxrequired = T:T - fxtag = cime6.0.198_rme01 - - -Explanation: - - This entry indicates that the submodule cime should be checked out into a directory named cime at tag cime6.0.198_rme01 from the URL https://github.com/jedwards4b/cime. - This should only be done if the .gitmodules file is at the top level of the repository clone. + Additional example: + ```Ini, TOML + [submodule "cime"] + path = cime + url = https://github.com/jedwards4b/cime + fxrequired = T:T + fxtag = cime6.0.198_rme01 + ``` + + Explanation: + + This entry indicates that the submodule cime should be checked out + into a directory named cime at tag cime6.0.198_rme01 from the URL + https://github.com/jedwards4b/cime. This should only be done if + the .gitmodules file is at the top level of the repository clone. ## Contributing From e83bc7a865540751093c393f9edd8f66a450cf40 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sun, 7 Jan 2024 11:12:13 -0700 Subject: [PATCH 029/159] trying to improve markdown --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f3a0f889..199d2064 100644 --- a/README.md +++ b/README.md @@ -46,17 +46,17 @@ Git-fleximod is a Python-based tool that extends Git's submodule capabilities, o Here are some common usage examples: Installing submodules, including optional ones: - ```Bash - git fleximod install --optional - ``` +```bash + git fleximod install --optional +``` Updating a specific submodule to the fxtag indicated in .gitmodules: - ```Bash + ```bash git fleximod update submodule-name ``` Example .gitmodules entry: - ```Ini, TOML + ```ini, toml [submodule "cosp2"] path = src/physics/cosp2/src url = https://github.com/CFMIP/COSPv2.0 @@ -74,7 +74,7 @@ Here are some common usage examples: path directory. Additional example: - ```Ini, TOML + ```ini, toml [submodule "cime"] path = cime url = https://github.com/jedwards4b/cime From 967edf4ced928a444162079227637c794ed562e0 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sun, 7 Jan 2024 11:14:22 -0700 Subject: [PATCH 030/159] trying to improve markdown --- README.md | 49 ++++++++++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 199d2064..910858e7 100644 --- a/README.md +++ b/README.md @@ -45,49 +45,48 @@ Git-fleximod is a Python-based tool that extends Git's submodule capabilities, o Here are some common usage examples: - Installing submodules, including optional ones: +Installing submodules, including optional ones: ```bash git fleximod install --optional ``` - Updating a specific submodule to the fxtag indicated in .gitmodules: +Updating a specific submodule to the fxtag indicated in .gitmodules: - ```bash +```bash git fleximod update submodule-name - ``` - Example .gitmodules entry: - ```ini, toml +``` +Example .gitmodules entry: +```ini, toml [submodule "cosp2"] path = src/physics/cosp2/src url = https://github.com/CFMIP/COSPv2.0 fxsparse = ../.cosp_sparse_checkout fxtag = v2.1.4cesm - ``` - Explanation: - - This entry indicates that the submodule named cosp2 at tag - v2.1.4cesm should be checked out into the directory - src/physics/cosp2/src relative to the .gitmodules directory. It - should be checked out from the URL - https://github.com/CFMIP/COSPv2.0 and use sparse checkout as - described in the file ../.cosp_sparse_checkout relative to the - path directory. - - Additional example: - ```ini, toml +``` +Explanation: + +This entry indicates that the submodule named cosp2 at tag v2.1.4cesm +should be checked out into the directory src/physics/cosp2/src +relative to the .gitmodules directory. It should be checked out from +the URL https://github.com/CFMIP/COSPv2.0 and use sparse checkout as +described in the file ../.cosp_sparse_checkout relative to the path +directory. + +Additional example: +```ini, toml [submodule "cime"] path = cime url = https://github.com/jedwards4b/cime fxrequired = T:T fxtag = cime6.0.198_rme01 - ``` +``` - Explanation: +Explanation: - This entry indicates that the submodule cime should be checked out - into a directory named cime at tag cime6.0.198_rme01 from the URL - https://github.com/jedwards4b/cime. This should only be done if - the .gitmodules file is at the top level of the repository clone. +This entry indicates that the submodule cime should be checked out +into a directory named cime at tag cime6.0.198_rme01 from the URL +https://github.com/jedwards4b/cime. This should only be done if +the .gitmodules file is at the top level of the repository clone. ## Contributing From 4926864512c75496530bc713ddadb26071f16ac9 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sun, 7 Jan 2024 13:17:00 -0700 Subject: [PATCH 031/159] add pip setup.py file --- setup.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 setup.py diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..2d4cb364 --- /dev/null +++ b/setup.py @@ -0,0 +1,26 @@ +import setuptools +import os + +with open("README.md", "r") as fh: + long_description = fh.read() +with open("version.txt", "r") as fh: + version = fh.read() + cwd = os.getcwd() +setuptools.setup( + name="git-fleximod", # This is the name of the package + version=version, # The initial release version + author="Jim Edwards", # Full name of the author + description="Extended support for git-submodule and git-sparse-checkout", + long_description=long_description, # Long description read from the the readme file + long_description_content_type="text/markdown", + packages=setuptools.find_packages(), # List of all python modules to be installed + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], # Information to filter the project on PyPi website + python_requires='>=3.6', # Minimum version requirement of the package + py_modules=['git-fleximod'], # Name of the python package + package_dir={'git-fleximod':'.'}, # Directory of the source code of the package + install_requires=[] # Install other dependencies if any +) From d2b3985e4c0d43eb5afbf497f50fd67adde2d0e3 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sun, 7 Jan 2024 13:22:48 -0700 Subject: [PATCH 032/159] bug fix --- git-fleximod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git-fleximod b/git-fleximod index f03984f8..0f0fc363 100755 --- a/git-fleximod +++ b/git-fleximod @@ -169,7 +169,7 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master return # initialize a new git repo and set the sparse checkout flag - sprepo_git = GitInterface(os.path.join(topdir, path)) + sprepo_git = GitInterface(os.path.join(root_dir, path)) sprepo_git.config_set_value("core", "sparseCheckout", "true") # set the repository remote From 27375e88a3ee5d08ffc567f2d52f7ab6a7d1df7e Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sun, 7 Jan 2024 14:23:55 -0700 Subject: [PATCH 033/159] improve messages --- git-fleximod | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/git-fleximod b/git-fleximod index 0f0fc363..69491a5f 100755 --- a/git-fleximod +++ b/git-fleximod @@ -235,7 +235,7 @@ def submodules_update(gitmodules, root_dir): fxtag = gitmodules.get(name, "fxtag") path = gitmodules.get(name, "path") url = gitmodules.get(name, "url") - print(f"name={name} path={path} url={url} fxtag={fxtag}") + logger.info(f"name={name} path={path} url={url} fxtag={fxtag}") if os.path.exists(os.path.join(path, ".git")): submoddir = os.path.join(root_dir, path) with utils.pushd(submoddir): @@ -261,10 +261,14 @@ def submodules_update(gitmodules, root_dir): tags = git.git_operation("tag", "-l") if fxtag and fxtag not in tags: git.git_operation("fetch", newremote, "--tags") - if fxtag: + atag = git.git_operation("describe", "--tags", "--always").rstrip() + if fxtag and fxtag != atag: + print(f"Updating {name} to {fxtag}") git.git_operation("checkout", fxtag) - else: + elif not fxtag: print(f"No fxtag found for submodule {name}") + else: + print(f"submodule {name} up to date.") def submodules_install(gitmodules, root_dir, requiredlist): From a17fe845273a99aa767558b88a020c1d82724f98 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Mon, 8 Jan 2024 12:03:03 -0700 Subject: [PATCH 034/159] fix some issues in sparse checkout --- git-fleximod | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/git-fleximod b/git-fleximod index 69491a5f..51d03726 100755 --- a/git-fleximod +++ b/git-fleximod @@ -162,11 +162,6 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master # Check first if the module is already defined # and the sparse-checkout file exists git = GitInterface(root_dir) - topgit = os.path.join(root_dir, ".git", "modules") - gitsparse = os.path.join(topgit, name, "info", "sparse-checkout") - if os.path.isfile(gitsparse): - logger.warning("submodule {} is already initialized".format(name)) - return # initialize a new git repo and set the sparse checkout flag sprepo_git = GitInterface(os.path.join(root_dir, path)) @@ -175,16 +170,31 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master # set the repository remote sprepo_git.git_operation("remote", "add", "origin", url) + + if os.path.isfile(os.path.join(root_dir, ".git")): + with open(os.path.join(root_dir, ".git")) as f: + gitpath = os.path.abspath(os.path.join(root_dir,f.read().split()[1])) + print(f"gitpath is {gitpath}") + topgit = os.path.join(gitpath, "modules") + else: + topgit = os.path.join(root_dir, ".git", "modules") + print(f"topgit is {topgit}") if not os.path.isdir(topgit): os.makedirs(topgit) topgit = os.path.join(topgit, name) + print(f"topgit is {topgit}") + if os.path.isdir(os.path.join(root_dir,path,".git")): + shutil.move(os.path.join(root_dir,path, ".git"), topgit) + with open(os.path.join(root_dir,path, ".git"), "w") as f: + f.write("gitdir: " + os.path.relpath(topgit, path)) + + gitsparse = os.path.abspath(os.path.join(topgit, "info", "sparse-checkout")) + if os.path.isfile(gitsparse): + logger.warning("submodule {} is already initialized".format(name)) + return - shutil.move(os.path.join(path, ".git"), topgit) - - shutil.copy(os.path.join(path, sparsefile), gitsparse) - with open(os.path.join(path, ".git"), "w") as f: - f.write("gitdir: " + os.path.relpath(topgit, path)) + shutil.copy(os.path.join(root_dir,path, sparsefile), gitsparse) # Finally checkout the repo sprepo_git.git_operation("fetch", "--depth=1", "origin", "--tags") From 8c9c123bd687e857a0d01205ddd0a3d9849fe6ec Mon Sep 17 00:00:00 2001 From: James Edwards Date: Mon, 8 Jan 2024 14:35:57 -0700 Subject: [PATCH 035/159] need to replace ssh with https --- git-fleximod | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/git-fleximod b/git-fleximod index 51d03726..fbd6099a 100755 --- a/git-fleximod +++ b/git-fleximod @@ -162,7 +162,7 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master # Check first if the module is already defined # and the sparse-checkout file exists git = GitInterface(root_dir) - + print(f"root_dir is {root_dir}") # initialize a new git repo and set the sparse checkout flag sprepo_git = GitInterface(os.path.join(root_dir, path)) sprepo_git.config_set_value("core", "sparseCheckout", "true") @@ -170,7 +170,6 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master # set the repository remote sprepo_git.git_operation("remote", "add", "origin", url) - if os.path.isfile(os.path.join(root_dir, ".git")): with open(os.path.join(root_dir, ".git")) as f: gitpath = os.path.abspath(os.path.join(root_dir,f.read().split()[1])) @@ -224,20 +223,23 @@ def submodules_status(gitmodules, root_dir): path = gitmodules.get(name, "path") tag = gitmodules.get(name, "fxtag") newpath = os.path.join(root_dir, path) - with utils.pushd(newpath): - git = GitInterface(newpath) - atag = git.git_operation("describe", "--tags", "--always").rstrip() - if tag and atag != tag: - print(f"Submodule {name} {atag} is out of sync with .gitmodules {tag}") - elif tag: - print(f"Submodule {name} at tag {tag}") - else: - print( - f"Submodule {name} has no tag defined in .gitmodules, module at {atag}" - ) - status = git.git_operation("status") - if "nothing to commit" not in status: - print(status) + if not os.path.exists(os.path.join(newpath, ".git")): + print(f"Submodule {name} not checked out") + else: + with utils.pushd(newpath): + git = GitInterface(newpath) + atag = git.git_operation("describe", "--tags", "--always").rstrip() + if tag and atag != tag: + print(f"Submodule {name} {atag} is out of sync with .gitmodules {tag}") + elif tag: + print(f"Submodule {name} at tag {tag}") + else: + print( + f"Submodule {name} has no tag defined in .gitmodules, module at {atag}" + ) + status = git.git_operation("status") + if "nothing to commit" not in status: + print(status) def submodules_update(gitmodules, root_dir): @@ -293,6 +295,11 @@ def submodules_install(gitmodules, root_dir, requiredlist): if "T:F" == fxrequired: print("Skipping optional component {}".format(name)) continue + # ssh urls cause problems for those who dont have git accounts with ssh keys defined + # but cime has one since e3sm prefers ssh to https + if url.startswith("git@"): + url = url.replace("git@github.com:", "https://github.com") + if fxsparse: logger.debug( f"Callng submodule_sparse_checkout({root_dir}, {name}, {url}, {path}, {fxsparse}, {fxtag}" From 2752eccf37f28d3b7505d7d3c1faeb352c1a1bd0 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Mon, 8 Jan 2024 14:45:48 -0700 Subject: [PATCH 036/159] change from ssh to https --- git-fleximod | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/git-fleximod b/git-fleximod index fbd6099a..179bc2ca 100755 --- a/git-fleximod +++ b/git-fleximod @@ -201,9 +201,12 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master print(f"Successfully checked out {name}") -def submodule_checkout(root, name, path): +def submodule_checkout(root, name, path, url=None): git = GitInterface(root) repodir = os.path.join(root, path) + # if url is provided update to the new url + if url: + git.git_operation("submodule", "set-url", path, url) git.git_operation("submodule", "update", "--init", path) # Look for a .gitmodules file in the newly checkedout repo if os.path.exists(os.path.join(repodir, ".gitmodules")): @@ -309,7 +312,7 @@ def submodules_install(gitmodules, root_dir, requiredlist): logger.debug( "Calling submodule_checkout({},{},{})".format(root_dir, name, path) ) - submodule_checkout(root_dir, name, path) + submodule_checkout(root_dir, name, path, url=url) def _main_func(): From 2092f3ff672051f4c34de4986f7b59debbc30920 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Mon, 8 Jan 2024 14:58:44 -0700 Subject: [PATCH 037/159] fix ssh issue --- git-fleximod | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/git-fleximod b/git-fleximod index 179bc2ca..0fcbc1fe 100755 --- a/git-fleximod +++ b/git-fleximod @@ -205,10 +205,19 @@ def submodule_checkout(root, name, path, url=None): git = GitInterface(root) repodir = os.path.join(root, path) # if url is provided update to the new url + tmpurl = None if url: + # ssh urls cause problems for those who dont have git accounts with ssh keys defined + # but cime has one since e3sm prefers ssh to https + if url.startswith("git@"): + tmpurl = url + url = url.replace("git@github.com:", "https://github.com") git.git_operation("submodule", "set-url", path, url) git.git_operation("submodule", "update", "--init", path) # Look for a .gitmodules file in the newly checkedout repo + if tmpurl: + git.git_operation("submodule", "set-url", path, tmpurl) + if os.path.exists(os.path.join(repodir, ".gitmodules")): # recursively handle this checkout print(f"Recursively checking out submodules of {name} {repodir}") @@ -298,10 +307,6 @@ def submodules_install(gitmodules, root_dir, requiredlist): if "T:F" == fxrequired: print("Skipping optional component {}".format(name)) continue - # ssh urls cause problems for those who dont have git accounts with ssh keys defined - # but cime has one since e3sm prefers ssh to https - if url.startswith("git@"): - url = url.replace("git@github.com:", "https://github.com") if fxsparse: logger.debug( @@ -312,6 +317,7 @@ def submodules_install(gitmodules, root_dir, requiredlist): logger.debug( "Calling submodule_checkout({},{},{})".format(root_dir, name, path) ) + submodule_checkout(root_dir, name, path, url=url) From 4fd30a41f604b2bb6fbb8533429b60b4f1d0adc5 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Mon, 8 Jan 2024 15:07:31 -0700 Subject: [PATCH 038/159] remove debug print statments --- git-fleximod | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/git-fleximod b/git-fleximod index 0fcbc1fe..404b598f 100755 --- a/git-fleximod +++ b/git-fleximod @@ -162,7 +162,7 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master # Check first if the module is already defined # and the sparse-checkout file exists git = GitInterface(root_dir) - print(f"root_dir is {root_dir}") + # initialize a new git repo and set the sparse checkout flag sprepo_git = GitInterface(os.path.join(root_dir, path)) sprepo_git.config_set_value("core", "sparseCheckout", "true") @@ -173,15 +173,14 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master if os.path.isfile(os.path.join(root_dir, ".git")): with open(os.path.join(root_dir, ".git")) as f: gitpath = os.path.abspath(os.path.join(root_dir,f.read().split()[1])) - print(f"gitpath is {gitpath}") topgit = os.path.join(gitpath, "modules") else: topgit = os.path.join(root_dir, ".git", "modules") - print(f"topgit is {topgit}") + if not os.path.isdir(topgit): os.makedirs(topgit) topgit = os.path.join(topgit, name) - print(f"topgit is {topgit}") + if os.path.isdir(os.path.join(root_dir,path,".git")): shutil.move(os.path.join(root_dir,path, ".git"), topgit) with open(os.path.join(root_dir,path, ".git"), "w") as f: From 86a5a72abcbf9709a01ad01fa6c01229700a13f2 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Mon, 8 Jan 2024 16:59:21 -0700 Subject: [PATCH 039/159] fix issue with sparse checkout --- git-fleximod | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/git-fleximod b/git-fleximod index 404b598f..cb1c948d 100755 --- a/git-fleximod +++ b/git-fleximod @@ -170,21 +170,23 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master # set the repository remote sprepo_git.git_operation("remote", "add", "origin", url) + superroot = git.git_operation("rev-parse", "--show-superproject-working-tree") if os.path.isfile(os.path.join(root_dir, ".git")): with open(os.path.join(root_dir, ".git")) as f: gitpath = os.path.abspath(os.path.join(root_dir,f.read().split()[1])) - topgit = os.path.join(gitpath, "modules") + topgit = os.path.abspath(os.path.join(gitpath, "modules")) else: - topgit = os.path.join(root_dir, ".git", "modules") + topgit = os.path.abspath(os.path.join(root_dir, ".git", "modules")) if not os.path.isdir(topgit): os.makedirs(topgit) topgit = os.path.join(topgit, name) + logger.debug(f"root_dir is {root_dir} topgit is {topgit} superroot is {superroot}") if os.path.isdir(os.path.join(root_dir,path,".git")): shutil.move(os.path.join(root_dir,path, ".git"), topgit) with open(os.path.join(root_dir,path, ".git"), "w") as f: - f.write("gitdir: " + os.path.relpath(topgit, path)) + f.write("gitdir: " + os.path.relpath(topgit, os.path.join(root_dir,path))) gitsparse = os.path.abspath(os.path.join(topgit, "info", "sparse-checkout")) if os.path.isfile(gitsparse): From a9130f296d068f441994084f86c2fd376c66d6be Mon Sep 17 00:00:00 2001 From: James Edwards Date: Tue, 9 Jan 2024 09:42:17 -0700 Subject: [PATCH 040/159] more error checking --- git-fleximod | 4 +++- modules/gitmodules.py | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/git-fleximod b/git-fleximod index cb1c948d..2a00d410 100755 --- a/git-fleximod +++ b/git-fleximod @@ -221,7 +221,7 @@ def submodule_checkout(root, name, path, url=None): if os.path.exists(os.path.join(repodir, ".gitmodules")): # recursively handle this checkout - print(f"Recursively checking out submodules of {name} {repodir}") + print(f"Recursively checking out submodules of {name} ") gitmodules = GitModules(repodir) submodules_install(gitmodules, repodir, ["I:T"]) if os.path.exists(os.path.join(repodir, ".git")): @@ -235,6 +235,8 @@ def submodules_status(gitmodules, root_dir): for name in gitmodules.sections(): path = gitmodules.get(name, "path") tag = gitmodules.get(name, "fxtag") + if not path: + utils.fatal_error("No path found in .gitmodules for {}".format(name)) newpath = os.path.join(root_dir, path) if not os.path.exists(os.path.join(newpath, ".git")): print(f"Submodule {name} not checked out") diff --git a/modules/gitmodules.py b/modules/gitmodules.py index 9e38a580..64b539a3 100644 --- a/modules/gitmodules.py +++ b/modules/gitmodules.py @@ -51,7 +51,8 @@ def get(self, name, option, raw=False, vars=None, fallback=None): return None def save(self): - self.write(open(self.conf_file, "w")) + print("Called gitmodules save, not expected") + # self.write(open(self.conf_file, "w")) def sections(self): """Strip the submodule part out of section and just use the name""" From 3ccf8a3adf15825ff440f9430c9cf3f56b99fe4b Mon Sep 17 00:00:00 2001 From: James Edwards Date: Tue, 9 Jan 2024 17:44:28 -0700 Subject: [PATCH 041/159] hack to prevent file corruption --- git-fleximod | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/git-fleximod b/git-fleximod index 2a00d410..e8a719aa 100755 --- a/git-fleximod +++ b/git-fleximod @@ -203,6 +203,7 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master def submodule_checkout(root, name, path, url=None): + shutil.copy(os.path.join(root,".gitmodules"), os.path.join(root,".save.gitmodules")) git = GitInterface(root) repodir = os.path.join(root, path) # if url is provided update to the new url @@ -214,7 +215,9 @@ def submodule_checkout(root, name, path, url=None): tmpurl = url url = url.replace("git@github.com:", "https://github.com") git.git_operation("submodule", "set-url", path, url) - git.git_operation("submodule", "update", "--init", path) + git.git_operation("submodule", "update", "--init", "--", path) + shutil.copy(os.path.join(root,".save.gitmodules"), os.path.join(root,".gitmodules")) + # Look for a .gitmodules file in the newly checkedout repo if tmpurl: git.git_operation("submodule", "set-url", path, tmpurl) From 840499c5601b1c944ac509b38558da0e8323ed9b Mon Sep 17 00:00:00 2001 From: James Edwards Date: Tue, 9 Jan 2024 18:01:01 -0700 Subject: [PATCH 042/159] ignore untracked files in submodules --- git-fleximod | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/git-fleximod b/git-fleximod index e8a719aa..04fcf204 100755 --- a/git-fleximod +++ b/git-fleximod @@ -216,7 +216,8 @@ def submodule_checkout(root, name, path, url=None): url = url.replace("git@github.com:", "https://github.com") git.git_operation("submodule", "set-url", path, url) git.git_operation("submodule", "update", "--init", "--", path) - shutil.copy(os.path.join(root,".save.gitmodules"), os.path.join(root,".gitmodules")) + + shutil.move(os.path.join(root,".save.gitmodules"), os.path.join(root,".gitmodules")) # Look for a .gitmodules file in the newly checkedout repo if tmpurl: @@ -255,7 +256,7 @@ def submodules_status(gitmodules, root_dir): print( f"Submodule {name} has no tag defined in .gitmodules, module at {atag}" ) - status = git.git_operation("status") + status = git.git_operation("status","--ignore-submodules","untracked") if "nothing to commit" not in status: print(status) From a25b257efc706ebc03468be128ec9c566791b05e Mon Sep 17 00:00:00 2001 From: James Edwards Date: Wed, 10 Jan 2024 05:24:18 -0700 Subject: [PATCH 043/159] a better solution to the file coruption issue --- git-fleximod | 9 ++++----- modules/gitmodules.py | 6 ++++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/git-fleximod b/git-fleximod index 04fcf204..97260591 100755 --- a/git-fleximod +++ b/git-fleximod @@ -203,22 +203,21 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master def submodule_checkout(root, name, path, url=None): - shutil.copy(os.path.join(root,".gitmodules"), os.path.join(root,".save.gitmodules")) git = GitInterface(root) repodir = os.path.join(root, path) # if url is provided update to the new url tmpurl = None if url: # ssh urls cause problems for those who dont have git accounts with ssh keys defined - # but cime has one since e3sm prefers ssh to https + # but cime has one since e3sm prefers ssh to https, because the .gitmodules file was + # opened with a GitModules object we don't need to worry about restoring the file here + # it will be done by the GitModules class if url.startswith("git@"): tmpurl = url url = url.replace("git@github.com:", "https://github.com") git.git_operation("submodule", "set-url", path, url) git.git_operation("submodule", "update", "--init", "--", path) - shutil.move(os.path.join(root,".save.gitmodules"), os.path.join(root,".gitmodules")) - # Look for a .gitmodules file in the newly checkedout repo if tmpurl: git.git_operation("submodule", "set-url", path, tmpurl) @@ -345,7 +344,7 @@ def _main_func(): file_path = utils.find_upwards(root_dir, file_name) if file_path is None: - fatal_error( + utils.fatal_error( "No {} found in {} or any of it's parents".format(file_name, root_dir) ) root_dir = os.path.dirname(file_path) diff --git a/modules/gitmodules.py b/modules/gitmodules.py index 64b539a3..fe05e3ca 100644 --- a/modules/gitmodules.py +++ b/modules/gitmodules.py @@ -1,4 +1,5 @@ import os +import shutil from configparser import ConfigParser from modules.lstripreader import LstripReader @@ -20,6 +21,8 @@ def __init__( """ ConfigParser.__init__(self) self.conf_file = os.path.join(confpath, conffile) + # first create a backup of this file to be restored on deletion of the object + shutil.copy(self.conf_file, self.conf_file+".save") self.read_file(LstripReader(self.conf_file), source=conffile) self.includelist = includelist self.excludelist = excludelist @@ -54,6 +57,9 @@ def save(self): print("Called gitmodules save, not expected") # self.write(open(self.conf_file, "w")) + def __del__(self): + shutil.move(self.conf_file+".save", self.conf_file) + def sections(self): """Strip the submodule part out of section and just use the name""" names = [] From 657f110a761e2c49524e2f2dd64c139968b3ef2c Mon Sep 17 00:00:00 2001 From: James Edwards Date: Wed, 10 Jan 2024 10:48:30 -0700 Subject: [PATCH 044/159] awkward solution for ssh urls --- git-fleximod | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/git-fleximod b/git-fleximod index 97260591..206359e3 100755 --- a/git-fleximod +++ b/git-fleximod @@ -202,11 +202,14 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master print(f"Successfully checked out {name}") -def submodule_checkout(root, name, path, url=None): +def submodule_checkout(root, name, path, url=None, tag=None): git = GitInterface(root) repodir = os.path.join(root, path) + # if url is provided update to the new url tmpurl = None + + # Look for a .gitmodules file in the newly checkedout repo if url: # ssh urls cause problems for those who dont have git accounts with ssh keys defined # but cime has one since e3sm prefers ssh to https, because the .gitmodules file was @@ -215,22 +218,31 @@ def submodule_checkout(root, name, path, url=None): if url.startswith("git@"): tmpurl = url url = url.replace("git@github.com:", "https://github.com") - git.git_operation("submodule", "set-url", path, url) - git.git_operation("submodule", "update", "--init", "--", path) - - # Look for a .gitmodules file in the newly checkedout repo - if tmpurl: - git.git_operation("submodule", "set-url", path, tmpurl) - + + git.git_operation("clone", "-b", tag, url, path) + print(f"Using {url} for {name}") + # print(git.git_operation("submodule", "set-url", name, url)) + # if "cprnc" in repodir: + # with open(os.path.join(root,".gitmodules"),"r") as f: + # print(f.read()) + print(f"calling update for {name}") + if not tmpurl: + logger.debug(git.git_operation("submodule", "update", "--init", "--", name)) + if os.path.exists(os.path.join(repodir, ".gitmodules")): # recursively handle this checkout - print(f"Recursively checking out submodules of {name} ") + print(f"Recursively checking out submodules of {name} {repodir} {url}") gitmodules = GitModules(repodir) submodules_install(gitmodules, repodir, ["I:T"]) if os.path.exists(os.path.join(repodir, ".git")): print(f"Successfully checked out {name}") else: utils.fatal_error(f"Failed to checkout {name}") + + + if tmpurl: + print(git.git_operation("restore", ".gitmodules")) + print(f"Using {tmpurl} for {name}") return @@ -324,7 +336,7 @@ def submodules_install(gitmodules, root_dir, requiredlist): "Calling submodule_checkout({},{},{})".format(root_dir, name, path) ) - submodule_checkout(root_dir, name, path, url=url) + submodule_checkout(root_dir, name, path, url=url, tag=fxtag) def _main_func(): From 4879c7530523b8b7a16c11a9c113755d8b0e4a85 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Wed, 10 Jan 2024 10:57:31 -0700 Subject: [PATCH 045/159] remove debug print statements --- git-fleximod | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/git-fleximod b/git-fleximod index 206359e3..f78dfa25 100755 --- a/git-fleximod +++ b/git-fleximod @@ -218,16 +218,10 @@ def submodule_checkout(root, name, path, url=None, tag=None): if url.startswith("git@"): tmpurl = url url = url.replace("git@github.com:", "https://github.com") - git.git_operation("clone", "-b", tag, url, path) - print(f"Using {url} for {name}") - # print(git.git_operation("submodule", "set-url", name, url)) - # if "cprnc" in repodir: - # with open(os.path.join(root,".gitmodules"),"r") as f: - # print(f.read()) - print(f"calling update for {name}") + if not tmpurl: - logger.debug(git.git_operation("submodule", "update", "--init", "--", name)) + logger.debug(git.git_operation("submodule", "update", "--init", "--", path)) if os.path.exists(os.path.join(repodir, ".gitmodules")): # recursively handle this checkout @@ -239,10 +233,9 @@ def submodule_checkout(root, name, path, url=None, tag=None): else: utils.fatal_error(f"Failed to checkout {name}") - if tmpurl: print(git.git_operation("restore", ".gitmodules")) - print(f"Using {tmpurl} for {name}") + return From 661ba5efc813fa910e7fc43172a513a177d2e013 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Wed, 10 Jan 2024 17:29:53 -0700 Subject: [PATCH 046/159] update for distribution --- setup.py | 25 +++++---- {modules => src/fleximod}/__init__.py | 0 {modules => src/fleximod}/gitinterface.py | 13 +++-- {modules => src/fleximod}/gitmodules.py | 12 +++-- {modules => src/fleximod}/lstripreader.py | 0 {modules => src/fleximod}/utils.py | 0 src/fleximod/version.py | 1 + git-fleximod => src/git-fleximod | 62 +++++++++++++---------- version.txt | 1 - 9 files changed, 70 insertions(+), 44 deletions(-) rename {modules => src/fleximod}/__init__.py (100%) rename {modules => src/fleximod}/gitinterface.py (87%) rename {modules => src/fleximod}/gitmodules.py (83%) rename {modules => src/fleximod}/lstripreader.py (100%) rename {modules => src/fleximod}/utils.py (100%) create mode 100644 src/fleximod/version.py rename git-fleximod => src/git-fleximod (90%) delete mode 100644 version.txt diff --git a/setup.py b/setup.py index 2d4cb364..1abf8392 100644 --- a/setup.py +++ b/setup.py @@ -1,26 +1,33 @@ import setuptools import os +from setuptools import setup, find_packages +from distutils.util import convert_path with open("README.md", "r") as fh: long_description = fh.read() -with open("version.txt", "r") as fh: - version = fh.read() - cwd = os.getcwd() + +main_ns = {} +ver_path = convert_path('src/fleximod/version.py') +with open(ver_path) as ver_file: + exec(ver_file.read(), main_ns) + + setuptools.setup( - name="git-fleximod", # This is the name of the package - version=version, # The initial release version + scripts=["src/git-fleximod"], # This is the name of the package + version=main_ns['__version__'], # The initial release version author="Jim Edwards", # Full name of the author description="Extended support for git-submodule and git-sparse-checkout", long_description=long_description, # Long description read from the the readme file long_description_content_type="text/markdown", - packages=setuptools.find_packages(), # List of all python modules to be installed + packages=['fleximod'], # List of all python modules to be installed + package_dir={'fleximod': 'src/fleximod'}, + package_data={"":['version.txt']}, classifiers=[ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ], # Information to filter the project on PyPi website python_requires='>=3.6', # Minimum version requirement of the package - py_modules=['git-fleximod'], # Name of the python package - package_dir={'git-fleximod':'.'}, # Directory of the source code of the package - install_requires=[] # Install other dependencies if any +# py_modules=['git-fleximod'], # Name of the python package + install_requires=["PyGit"] # Install other dependencies if any ) diff --git a/modules/__init__.py b/src/fleximod/__init__.py similarity index 100% rename from modules/__init__.py rename to src/fleximod/__init__.py diff --git a/modules/gitinterface.py b/src/fleximod/gitinterface.py similarity index 87% rename from modules/gitinterface.py rename to src/fleximod/gitinterface.py index a4e25292..c127163d 100644 --- a/modules/gitinterface.py +++ b/src/fleximod/gitinterface.py @@ -1,11 +1,12 @@ import os import logging -from modules import utils +from fleximod import utils class GitInterface: - def __init__(self, repo_path): + def __init__(self, repo_path, logger): + logger.debug("Initialize GitInterface for {}".format(repo_path)) self.repo_path = repo_path - + self.logger = logger try: import git self._use_module = True @@ -20,10 +21,10 @@ def __init__(self, repo_path): if not os.path.exists(os.path.join(repo_path,".git")): self._init_git_repo() msg = "Using shell interface to git" - logging.info(msg) + self.logger.info(msg) def _git_command(self, operation, *args): - logging.info(operation) + self.logger.info(operation) if self._use_module and operation != "submodule": return getattr(self.repo.git, operation)(*args) else: @@ -39,6 +40,7 @@ def _init_git_repo(self): def git_operation(self, operation, *args, **kwargs): command = self._git_command(operation, *args) + self.logger.info(command) if isinstance(command, list): return utils.execute_subprocess(command, output_to_caller=True) else: @@ -60,4 +62,5 @@ def config_set_value(self, section, name, value): writer.release() # Ensure changes are saved else: cmd = ("git","-C",self.repo_path,"config", f"{section}.{name}", value) + self.logger.info(cmd) utils.execute_subprocess(cmd, output_to_caller=True) diff --git a/modules/gitmodules.py b/src/fleximod/gitmodules.py similarity index 83% rename from modules/gitmodules.py rename to src/fleximod/gitmodules.py index fe05e3ca..a6f75893 100644 --- a/modules/gitmodules.py +++ b/src/fleximod/gitmodules.py @@ -1,13 +1,12 @@ import os import shutil from configparser import ConfigParser - -from modules.lstripreader import LstripReader - +from fleximod.lstripreader import LstripReader class GitModules(ConfigParser): def __init__( self, + logger, confpath=os.getcwd(), conffile=".gitmodules", includelist=None, @@ -19,6 +18,8 @@ def __init__( includelist: Optional list of submodules to include. excludelist: Optional list of submodules to exclude. """ + self.logger = logger + self.logger.debug("Creating a GitModules object {} {} {} {}".format(confpath,conffile,includelist,excludelist)) ConfigParser.__init__(self) self.conf_file = os.path.join(confpath, conffile) # first create a backup of this file to be restored on deletion of the object @@ -33,6 +34,7 @@ def set(self, name, option, value): Ensures the appropriate section exists for the submodule. Calls the parent class's set method to store the value. """ + self.logger.debug("set called {} {} {}".format(name,option,value)) section = f'submodule "{name}"' if not self.has_section(section): self.add_section(section) @@ -45,6 +47,7 @@ def get(self, name, option, raw=False, vars=None, fallback=None): Uses the parent class's get method to access the value. Handles potential errors if the section or option doesn't exist. """ + self.logger.debug("get called {} {}".format(name,option)) section = f'submodule "{name}"' try: return ConfigParser.get( @@ -58,10 +61,12 @@ def save(self): # self.write(open(self.conf_file, "w")) def __del__(self): + self.logger.debug("Destroying GitModules object") shutil.move(self.conf_file+".save", self.conf_file) def sections(self): """Strip the submodule part out of section and just use the name""" + self.logger.debug("calling GitModules sections iterator") names = [] for section in ConfigParser.sections(self): name = section[11:-1] @@ -73,5 +78,6 @@ def sections(self): return names def items(self, name, raw=False, vars=None): + self.logger.debug("calling GitModules items for {}".format(name)) section = f'submodule "{name}"' return ConfigParser.items(section, raw=raw, vars=vars) diff --git a/modules/lstripreader.py b/src/fleximod/lstripreader.py similarity index 100% rename from modules/lstripreader.py rename to src/fleximod/lstripreader.py diff --git a/modules/utils.py b/src/fleximod/utils.py similarity index 100% rename from modules/utils.py rename to src/fleximod/utils.py diff --git a/src/fleximod/version.py b/src/fleximod/version.py new file mode 100644 index 00000000..8ce9b362 --- /dev/null +++ b/src/fleximod/version.py @@ -0,0 +1 @@ +__version__ = '0.1.3' diff --git a/git-fleximod b/src/git-fleximod similarity index 90% rename from git-fleximod rename to src/git-fleximod index f78dfa25..dd2adb0f 100755 --- a/git-fleximod +++ b/src/git-fleximod @@ -4,13 +4,12 @@ import os import shutil import logging import argparse -from modules import utils -from modules.gitinterface import GitInterface -from modules.gitmodules import GitModules - -logger = logging.getLogger(__name__) -logger.propogate = False - +from fleximod import utils +from fleximod.gitinterface import GitInterface +from fleximod.gitmodules import GitModules +from fleximod.version import __version__ +# logger variable is global +logger = None def commandline_arguments(args=None): description = """ @@ -84,9 +83,9 @@ def commandline_arguments(args=None): parser.add_argument( "-V", "--version", - action="store_true", - default=False, - help="Print manage_externals version and exit.", + action="version", + version=f"%(prog)s {__version__}", + help="Print version and exit.", ) # @@ -136,14 +135,21 @@ def commandline_arguments(args=None): if not action: action = "install" + if options.debug: + level = logging.DEBUG + elif options.verbose: + level = logging.INFO + else: + level = logging.WARNING + # Configure the root logger + logging.basicConfig( + level=level, + format="%(name)s - %(levelname)s - %(message)s", + handlers=[logging.FileHandler("fleximod.log"), logging.StreamHandler()], + ) if options.version: - version_info = "" - version_file_path = os.path.join(os.path.dirname(__file__), "version.txt") - with open(version_file_path) as f: - version_info = f.readlines()[0].strip() - print(version_info) - sys.exit(0) - + exit() + return ( options.path, options.gitmodules, @@ -161,10 +167,10 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master os.makedirs(path) # Check first if the module is already defined # and the sparse-checkout file exists - git = GitInterface(root_dir) + git = GitInterface(root_dir, logger) # initialize a new git repo and set the sparse checkout flag - sprepo_git = GitInterface(os.path.join(root_dir, path)) + sprepo_git = GitInterface(os.path.join(root_dir, path), logger) sprepo_git.config_set_value("core", "sparseCheckout", "true") # set the repository remote @@ -203,7 +209,7 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master def submodule_checkout(root, name, path, url=None, tag=None): - git = GitInterface(root) + git = GitInterface(root, logger) repodir = os.path.join(root, path) # if url is provided update to the new url @@ -226,7 +232,7 @@ def submodule_checkout(root, name, path, url=None, tag=None): if os.path.exists(os.path.join(repodir, ".gitmodules")): # recursively handle this checkout print(f"Recursively checking out submodules of {name} {repodir} {url}") - gitmodules = GitModules(repodir) + gitmodules = GitModules(logger,confpath=repodir) submodules_install(gitmodules, repodir, ["I:T"]) if os.path.exists(os.path.join(repodir, ".git")): print(f"Successfully checked out {name}") @@ -250,7 +256,7 @@ def submodules_status(gitmodules, root_dir): print(f"Submodule {name} not checked out") else: with utils.pushd(newpath): - git = GitInterface(newpath) + git = GitInterface(newpath, logger) atag = git.git_operation("describe", "--tags", "--always").rstrip() if tag and atag != tag: print(f"Submodule {name} {atag} is out of sync with .gitmodules {tag}") @@ -274,7 +280,7 @@ def submodules_update(gitmodules, root_dir): if os.path.exists(os.path.join(path, ".git")): submoddir = os.path.join(root_dir, path) with utils.pushd(submoddir): - git = GitInterface(submoddir) + git = GitInterface(submoddir, logger) # first make sure the url is correct upstream = git.git_operation("ls-remote", "--get-url").rstrip() newremote = "origin" @@ -342,8 +348,11 @@ def _main_func(): verbose, action, ) = commandline_arguments() - if verbose: - print(f"action is {action}") + # Get a logger for the package + global logger + logger = logging.getLogger(__name__) + + logger.info(f"action is {action}") if not os.path.isfile(os.path.join(root_dir, file_name)): file_path = utils.find_upwards(root_dir, file_name) @@ -353,8 +362,9 @@ def _main_func(): "No {} found in {} or any of it's parents".format(file_name, root_dir) ) root_dir = os.path.dirname(file_path) - print(f"root_dir is {root_dir}") + logger.info(f"root_dir is {root_dir}") gitmodules = GitModules( + logger, confpath=root_dir, conffile=file_name, includelist=includelist, diff --git a/version.txt b/version.txt deleted file mode 100644 index a3dce6cd..00000000 --- a/version.txt +++ /dev/null @@ -1 +0,0 @@ -v0.0.2 From b22bab6c5c359a9635493dd90cc8d17af03321b9 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Thu, 11 Jan 2024 11:03:15 -0700 Subject: [PATCH 047/159] fix error with version flag --- src/git-fleximod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/git-fleximod b/src/git-fleximod index dd2adb0f..d45acb14 100755 --- a/src/git-fleximod +++ b/src/git-fleximod @@ -147,7 +147,7 @@ def commandline_arguments(args=None): format="%(name)s - %(levelname)s - %(message)s", handlers=[logging.FileHandler("fleximod.log"), logging.StreamHandler()], ) - if options.version: + if hasattr(options, 'version'): exit() return ( From e81099a63708e4e5cace47b6a1fe39bafee694b5 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Thu, 11 Jan 2024 11:04:10 -0700 Subject: [PATCH 048/159] update version to 0.1.4 --- src/fleximod/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fleximod/version.py b/src/fleximod/version.py index 8ce9b362..7525d199 100644 --- a/src/fleximod/version.py +++ b/src/fleximod/version.py @@ -1 +1 @@ -__version__ = '0.1.3' +__version__ = '0.1.4' From 1a80e973560b86ccee228274dbb2b38e17037abc Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Mon, 15 Jan 2024 07:52:01 -0700 Subject: [PATCH 049/159] fix issues with rerunning --- src/fleximod/version.py | 2 +- src/git-fleximod | 25 ++++++++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/fleximod/version.py b/src/fleximod/version.py index 7525d199..2fb25139 100644 --- a/src/fleximod/version.py +++ b/src/fleximod/version.py @@ -1 +1 @@ -__version__ = '0.1.4' +__version__ = '0.1.6' diff --git a/src/git-fleximod b/src/git-fleximod index d45acb14..d7bc2c12 100755 --- a/src/git-fleximod +++ b/src/git-fleximod @@ -8,6 +8,7 @@ from fleximod import utils from fleximod.gitinterface import GitInterface from fleximod.gitmodules import GitModules from fleximod.version import __version__ +from configparser import NoOptionError # logger variable is global logger = None @@ -170,7 +171,21 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master git = GitInterface(root_dir, logger) # initialize a new git repo and set the sparse checkout flag - sprepo_git = GitInterface(os.path.join(root_dir, path), logger) + sprep_repo = os.path.join(root_dir, path) + sprepo_git = GitInterface(sprep_repo, logger) + if os.path.exists(os.path.join(sprep_repo,".git")): + try: + logger.info("Submodule {} found".format(name)) + chk = sprepo_git.config_get_value("core", "sparseCheckout") + if chk == "true": + logger.info("Sparse submodule {} already checked out".format(name)) + return + except NoOptionError: + logger.debug("Sparse submodule {} not present".format(name)) + except Exception as e: + utils.fatal_error("Unexpected error {} occured.".format(e)) + + sprepo_git.config_set_value("core", "sparseCheckout", "true") # set the repository remote @@ -211,7 +226,9 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master def submodule_checkout(root, name, path, url=None, tag=None): git = GitInterface(root, logger) repodir = os.path.join(root, path) - + if os.path.exists(os.path.join(repodir, ".git")): + logger.info("Submodule {} already checked out".format(name)) + return # if url is provided update to the new url tmpurl = None @@ -225,7 +242,9 @@ def submodule_checkout(root, name, path, url=None, tag=None): tmpurl = url url = url.replace("git@github.com:", "https://github.com") git.git_operation("clone", "-b", tag, url, path) - + # Now need to move the .git dir to the submodule location + + if not tmpurl: logger.debug(git.git_operation("submodule", "update", "--init", "--", path)) From 13eb8b87bcfba34eb70c5bd3a39e570d2527a452 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Mon, 15 Jan 2024 07:53:10 -0700 Subject: [PATCH 050/159] update setup.py --- setup.cfg | 2 ++ setup.py | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 setup.cfg diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..4f727fa0 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[metadata] +license_file = License \ No newline at end of file diff --git a/setup.py b/setup.py index 1abf8392..f8915938 100644 --- a/setup.py +++ b/setup.py @@ -16,6 +16,8 @@ scripts=["src/git-fleximod"], # This is the name of the package version=main_ns['__version__'], # The initial release version author="Jim Edwards", # Full name of the author + maintainer="jedwards4b", + license="MIT License", description="Extended support for git-submodule and git-sparse-checkout", long_description=long_description, # Long description read from the the readme file long_description_content_type="text/markdown", @@ -29,5 +31,5 @@ ], # Information to filter the project on PyPi website python_requires='>=3.6', # Minimum version requirement of the package # py_modules=['git-fleximod'], # Name of the python package - install_requires=["PyGit"] # Install other dependencies if any + install_requires=["GitPython"] # Install other dependencies if any ) From 5a590c541ca168c879d3726957c102f878af0f6d Mon Sep 17 00:00:00 2001 From: James Edwards Date: Mon, 15 Jan 2024 09:36:10 -0700 Subject: [PATCH 051/159] update version --- src/fleximod/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fleximod/version.py b/src/fleximod/version.py index 2fb25139..124e4620 100644 --- a/src/fleximod/version.py +++ b/src/fleximod/version.py @@ -1 +1 @@ -__version__ = '0.1.6' +__version__ = '0.1.7' From 8c92b14204c80b0b444a3155a1ce52e5dca8411b Mon Sep 17 00:00:00 2001 From: James Edwards Date: Wed, 17 Jan 2024 16:23:33 -0700 Subject: [PATCH 052/159] provide more info for status of uninstalled submodules --- src/fleximod/version.py | 2 +- src/git-fleximod | 20 ++++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/fleximod/version.py b/src/fleximod/version.py index 124e4620..c3bb2961 100644 --- a/src/fleximod/version.py +++ b/src/fleximod/version.py @@ -1 +1 @@ -__version__ = '0.1.7' +__version__ = '0.1.8' diff --git a/src/git-fleximod b/src/git-fleximod index d7bc2c12..3e743c3f 100755 --- a/src/git-fleximod +++ b/src/git-fleximod @@ -127,7 +127,8 @@ def commandline_arguments(args=None): else: options = parser.parse_args() - if options.optional: +# explicitly listing a component overrides the optional flag + if options.optional or options.components: fxrequired = ["T:T", "T:F", "I:T"] else: fxrequired = ["T:T", "I:T"] @@ -271,8 +272,23 @@ def submodules_status(gitmodules, root_dir): if not path: utils.fatal_error("No path found in .gitmodules for {}".format(name)) newpath = os.path.join(root_dir, path) + logger.debug("newpath is {}".format(newpath)) if not os.path.exists(os.path.join(newpath, ".git")): - print(f"Submodule {name} not checked out") + rootgit = GitInterface(root_dir, logger) + # submodule commands use path, not name + nhash = (rootgit.git_operation("submodule","status",path).split()[0])[1:] + url = gitmodules.get(name, "url") + tags = rootgit.git_operation("ls-remote","--tags",url) + atag = None + for htag in tags.split('\n'): + if tag in htag: + atag = (htag.split()[1])[10:] + break + if tag == atag: + print(f"Submodule {name} not checked out, aligned at tag {tag}") + else: + print(f"Submodule {name} not checked out, out of sync at tag {atag}, expected tag is {tag}") + else: with utils.pushd(newpath): git = GitInterface(newpath, logger) From 43225eac86892d074453cbdc622124b2eedf48cf Mon Sep 17 00:00:00 2001 From: James Edwards Date: Thu, 18 Jan 2024 16:49:52 -0700 Subject: [PATCH 053/159] add test action --- escomp_install | 25 +++++++++++++++++++ src/fleximod/version.py | 2 +- src/git-fleximod | 55 ++++++++++++++++++++++++----------------- 3 files changed, 59 insertions(+), 23 deletions(-) create mode 100644 escomp_install diff --git a/escomp_install b/escomp_install new file mode 100644 index 00000000..ae782e72 --- /dev/null +++ b/escomp_install @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# updates git-fleximod in an ESCOMP model +# this script should be run from the model root directory, it expects +# git-fleximod to already be installed with the script in bin +# and the classes in lib/python/site-packages +import sys +import shutil +import os + +from glob import iglob + +fleximod_root = sys.argv[1] +fleximod_path = os.path.join(fleximod_root,"src","git-fleximod") +if os.path.isfile(fleximod_path): + with open(fleximod_path,"r") as f: + fleximod = f.readlines() + with open(os.path.join(".","bin","git-fleximod"),"w") as f: + for line in fleximod: + f.write(line) + if "import argparse" in line: + f.write('\nsys.path.append(os.path.join(os.path.dirname(__file__),"..","lib","python","site-packages"))\n\n') + + for file in iglob(os.path.join(fleximod_root, "src", "fleximod", "*.py")): + shutil.copy(file, + os.path.join("lib","python","site-packages","fleximod",os.path.basename(file))) diff --git a/src/fleximod/version.py b/src/fleximod/version.py index c3bb2961..1c98a23a 100644 --- a/src/fleximod/version.py +++ b/src/fleximod/version.py @@ -1 +1 @@ -__version__ = '0.1.8' +__version__ = '0.1.9' diff --git a/src/git-fleximod b/src/git-fleximod index 3e743c3f..fce79b85 100755 --- a/src/git-fleximod +++ b/src/git-fleximod @@ -23,7 +23,7 @@ def commandline_arguments(args=None): # # user options # - choices = ["update", "install", "status"] + choices = ["update", "install", "status", "test"] parser.add_argument( "action", choices=choices, @@ -107,21 +107,6 @@ def commandline_arguments(args=None): "information to the screen and log file.", ) - logging_group = parser.add_mutually_exclusive_group() - - logging_group.add_argument( - "--logging", - dest="do_logging", - action="store_true", - help="DEVELOPER: enable logging.", - ) - logging_group.add_argument( - "--no-logging", - dest="do_logging", - action="store_false", - default=False, - help="DEVELOPER: disable logging " "(this is the default)", - ) if args: options = parser.parse_args(args) else: @@ -266,6 +251,7 @@ def submodule_checkout(root, name, path, url=None, tag=None): def submodules_status(gitmodules, root_dir): + testfails = 0 for name in gitmodules.sections(): path = gitmodules.get(name, "path") tag = gitmodules.get(name, "fxtag") @@ -288,23 +274,27 @@ def submodules_status(gitmodules, root_dir): print(f"Submodule {name} not checked out, aligned at tag {tag}") else: print(f"Submodule {name} not checked out, out of sync at tag {atag}, expected tag is {tag}") - + testfails += 1 else: with utils.pushd(newpath): git = GitInterface(newpath, logger) atag = git.git_operation("describe", "--tags", "--always").rstrip() if tag and atag != tag: print(f"Submodule {name} {atag} is out of sync with .gitmodules {tag}") + testfails += 1 elif tag: print(f"Submodule {name} at tag {tag}") else: print( f"Submodule {name} has no tag defined in .gitmodules, module at {atag}" ) + testfails += 1 + status = git.git_operation("status","--ignore-submodules","untracked") if "nothing to commit" not in status: print(status) - + + return testfails def submodules_update(gitmodules, root_dir): for name in gitmodules.sections(): @@ -372,7 +362,26 @@ def submodules_install(gitmodules, root_dir, requiredlist): submodule_checkout(root_dir, name, path, url=url, tag=fxtag) +def submodules_test(gitmodules, root_dir): + # First check that fxtags are present and in sync with submodule hashes + testfails = submodules_status(gitmodules, root_dir) + # Then make sure that urls are consistant with fxurls (not forks and not ssh) + # and that sparse checkout files exist + for name in gitmodules.sections(): + url = gitmodules.get(name, "url") + fxurl = gitmodules.get(name, "fxurl") + fxsparse = gitmodules.get(name, "fxsparse") + path = gitmodules.get(name, "path") + if not fxurl or url != fxurl: + print(f"submodule {name} url {url} not in sync with required {fxurl}") + testfails += 1 + if fxsparse and not os.path.isfile(os.path.join(root_dir, path, fxsparse)): + print(f"sparse submodule {name} sparse checkout file {fxsparse} not found") + testfails += 1 + return testfails + + def _main_func(): ( root_dir, @@ -405,16 +414,18 @@ def _main_func(): includelist=includelist, excludelist=excludelist, ) - + retval = 0 if action == "update": submodules_update(gitmodules, root_dir) elif action == "install": submodules_install(gitmodules, root_dir, fxrequired) elif action == "status": submodules_status(gitmodules, root_dir) + elif action == "test": + retval = submodules_test(gitmodules, root_dir) else: utils.fatal_error(f"unrecognized action request {action}") - - + return(retval) + if __name__ == "__main__": - _main_func() + sys.exit(_main_func()) From bbb1b1d98b587ddb5dd455a9ce889c64d53f7151 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Tue, 30 Jan 2024 16:18:53 -0700 Subject: [PATCH 054/159] fix issue with status --- README.md | 12 +++++++++++- src/git-fleximod | 19 ++++++++++--------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 910858e7..cec74c5a 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,9 @@ Git-fleximod is a Python-based tool that extends Git's submodule capabilities, o install: Install submodules according to configuration. status: Display the status of submodules. update: Update submodules to the tag indicated in .gitmodules variable fxtag. + test: Make sure that fxtags and submodule hashes are consistant, + make sure that official urls (as defined by fxurl) are set + make sure that fxtags are defined for all submodules Additional Options: See git fleximod --help for more details. @@ -32,6 +35,7 @@ Git-fleximod is a Python-based tool that extends Git's submodule capabilities, o - I:T: Internal and required (always checked out). - I:F: Internal and optional (checked out with --optional flag). fxsparse: Enable sparse checkout for a submodule, pointing to a file containing sparse checkout paths. + fcurl ## Sparse Checkouts @@ -39,7 +43,13 @@ Git-fleximod is a Python-based tool that extends Git's submodule capabilities, o in the .gitmodules file to the path of a file containing the desired sparse checkout paths. Git-fleximod will automatically configure sparse checkout based on this file when applicable commands are run. - See [git-sparse-checkout](https://git-scm.com/docs/git-sparse-checkout#_internalsfull_pattern_set) for details on the format of this file. + See [git-sparse-checkout](https://git-scm.com/docs/git-sparse-checkout#_internalsfull_pattern_set) + for details on the format of this file. + +## Tests + + The git fleximod test action is designed to be used by, for example, github workflows + to assure that protected branches are consistant with respect to submodule hashes and fleximod fxtags ## Examples diff --git a/src/git-fleximod b/src/git-fleximod index fce79b85..9e2620d7 100755 --- a/src/git-fleximod +++ b/src/git-fleximod @@ -4,6 +4,7 @@ import os import shutil import logging import argparse +import textwrap from fleximod import utils from fleximod.gitinterface import GitInterface from fleximod.gitmodules import GitModules @@ -23,11 +24,11 @@ def commandline_arguments(args=None): # # user options # - choices = ["update", "install", "status", "test"] + choices = ["update", "checkout", "status", "test"] parser.add_argument( "action", choices=choices, - default="install", + default="checkout", help=f"Subcommand of fleximod, choices are {choices}", ) @@ -120,7 +121,7 @@ def commandline_arguments(args=None): action = options.action if not action: - action = "install" + action = "checkout" if options.debug: level = logging.DEBUG @@ -238,7 +239,7 @@ def submodule_checkout(root, name, path, url=None, tag=None): # recursively handle this checkout print(f"Recursively checking out submodules of {name} {repodir} {url}") gitmodules = GitModules(logger,confpath=repodir) - submodules_install(gitmodules, repodir, ["I:T"]) + submodules_checkout(gitmodules, repodir, ["I:T"]) if os.path.exists(os.path.join(repodir, ".git")): print(f"Successfully checked out {name}") else: @@ -290,9 +291,9 @@ def submodules_status(gitmodules, root_dir): ) testfails += 1 - status = git.git_operation("status","--ignore-submodules","untracked") + status = git.git_operation("status","--ignore-submodules") if "nothing to commit" not in status: - print(status) + print(textwrap.indent(status,' ')) return testfails @@ -337,7 +338,7 @@ def submodules_update(gitmodules, root_dir): print(f"submodule {name} up to date.") -def submodules_install(gitmodules, root_dir, requiredlist): +def submodules_checkout(gitmodules, root_dir, requiredlist): for name in gitmodules.sections(): fxrequired = gitmodules.get(name, "fxrequired") fxsparse = gitmodules.get(name, "fxsparse") @@ -417,8 +418,8 @@ def _main_func(): retval = 0 if action == "update": submodules_update(gitmodules, root_dir) - elif action == "install": - submodules_install(gitmodules, root_dir, fxrequired) + elif action == "checkout": + submodules_checkout(gitmodules, root_dir, fxrequired) elif action == "status": submodules_status(gitmodules, root_dir) elif action == "test": From af78aa4de1632ad1bcb8e94bfa7b272294108132 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Tue, 30 Jan 2024 16:38:50 -0700 Subject: [PATCH 055/159] update version --- src/fleximod/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fleximod/version.py b/src/fleximod/version.py index 1c98a23a..7fd229a3 100644 --- a/src/fleximod/version.py +++ b/src/fleximod/version.py @@ -1 +1 @@ -__version__ = '0.1.9' +__version__ = '0.2.0' From 13a2f6b25dda439e74396073b52c3d748b838ea6 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Thu, 1 Feb 2024 12:21:58 -0700 Subject: [PATCH 056/159] cleaner format for status output, fix for special handling of ssh submodules, fix for 0 length log --- setup.cfg | 5 ++++- setup.py | 10 +++++++++- src/git-fleximod | 23 +++++++++++++++-------- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/setup.cfg b/setup.cfg index 4f727fa0..9a13827a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,5 @@ [metadata] -license_file = License \ No newline at end of file +license_file = License +[build_manpages] +manpages = + man/git-fleximod.1:object=parser:pyfile=bin/git-fleximod diff --git a/setup.py b/setup.py index f8915938..f68a4ea9 100644 --- a/setup.py +++ b/setup.py @@ -2,6 +2,7 @@ import os from setuptools import setup, find_packages from distutils.util import convert_path +from build_manpages import build_manpages, get_build_py_cmd, get_install_cmd with open("README.md", "r") as fh: long_description = fh.read() @@ -31,5 +32,12 @@ ], # Information to filter the project on PyPi website python_requires='>=3.6', # Minimum version requirement of the package # py_modules=['git-fleximod'], # Name of the python package - install_requires=["GitPython"] # Install other dependencies if any + install_requires=["GitPython"], # Install other dependencies if any + cmdclass={ + 'build_manpages': build_manpages, + # Re-define build_py and install commands so the manual pages + # are automatically re-generated and installed + 'build_py': get_build_py_cmd(), + 'install': get_install_cmd(), + } ) diff --git a/src/git-fleximod b/src/git-fleximod index 9e2620d7..f80d359c 100755 --- a/src/git-fleximod +++ b/src/git-fleximod @@ -122,9 +122,11 @@ def commandline_arguments(args=None): action = options.action if not action: action = "checkout" + handlers=[logging.StreamHandler()] if options.debug: level = logging.DEBUG + handlers.append(logging.FileHandler("fleximod.log")) elif options.verbose: level = logging.INFO else: @@ -133,8 +135,9 @@ def commandline_arguments(args=None): logging.basicConfig( level=level, format="%(name)s - %(levelname)s - %(message)s", - handlers=[logging.FileHandler("fleximod.log"), logging.StreamHandler()], + handlers=handlers ) + if hasattr(options, 'version'): exit() @@ -228,7 +231,11 @@ def submodule_checkout(root, name, path, url=None, tag=None): if url.startswith("git@"): tmpurl = url url = url.replace("git@github.com:", "https://github.com") - git.git_operation("clone", "-b", tag, url, path) + git.git_operation("clone", url, path) + smgit = GitInterface(path, logger) + if not tag: + tag = smgit.git_operation("describe", "--tags", "--always").rstrip() + smgit.git_operation("checkout",tag) # Now need to move the .git dir to the submodule location @@ -272,28 +279,28 @@ def submodules_status(gitmodules, root_dir): atag = (htag.split()[1])[10:] break if tag == atag: - print(f"Submodule {name} not checked out, aligned at tag {tag}") + print(f"e {name:>20} not checked out, aligned at tag {tag}") else: - print(f"Submodule {name} not checked out, out of sync at tag {atag}, expected tag is {tag}") + print(f"e {name:>20} not checked out, out of sync at tag {atag}, expected tag is {tag}") testfails += 1 else: with utils.pushd(newpath): git = GitInterface(newpath, logger) atag = git.git_operation("describe", "--tags", "--always").rstrip() if tag and atag != tag: - print(f"Submodule {name} {atag} is out of sync with .gitmodules {tag}") + print(f"s {name:>20} {atag} is out of sync with .gitmodules {tag}") testfails += 1 elif tag: - print(f"Submodule {name} at tag {tag}") + print(f" {name:>20} at tag {tag}") else: print( - f"Submodule {name} has no tag defined in .gitmodules, module at {atag}" + f"e {name:>20} has no tag defined in .gitmodules, module at {atag}" ) testfails += 1 status = git.git_operation("status","--ignore-submodules") if "nothing to commit" not in status: - print(textwrap.indent(status,' ')) + print('M'+textwrap.indent(status,' ')) return testfails From c21372a088fa6a72c01397eb8c955b5e5cf411d8 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Thu, 1 Feb 2024 13:04:22 -0700 Subject: [PATCH 057/159] check for permission in fleximod.log file --- src/git-fleximod | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/git-fleximod b/src/git-fleximod index f80d359c..cbfa5f4f 100755 --- a/src/git-fleximod +++ b/src/git-fleximod @@ -125,6 +125,10 @@ def commandline_arguments(args=None): handlers=[logging.StreamHandler()] if options.debug: + try: + open("fleximod.log","w") + except PermissionError: + sys.exit("ABORT: Could not write file fleximod.log") level = logging.DEBUG handlers.append(logging.FileHandler("fleximod.log")) elif options.verbose: From 07eb5dd3edcde5bfd1f125c85d6935bd0fa3b3cc Mon Sep 17 00:00:00 2001 From: James Edwards Date: Thu, 1 Feb 2024 14:02:34 -0700 Subject: [PATCH 058/159] fix issue with ssh submodule checkout --- src/git-fleximod | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/git-fleximod b/src/git-fleximod index cbfa5f4f..2ebb2e53 100755 --- a/src/git-fleximod +++ b/src/git-fleximod @@ -236,11 +236,28 @@ def submodule_checkout(root, name, path, url=None, tag=None): tmpurl = url url = url.replace("git@github.com:", "https://github.com") git.git_operation("clone", url, path) - smgit = GitInterface(path, logger) + smgit = GitInterface(repodir, logger) if not tag: tag = smgit.git_operation("describe", "--tags", "--always").rstrip() smgit.git_operation("checkout",tag) # Now need to move the .git dir to the submodule location + rootdotgit = os.path.join(root,".git") + if os.path.isfile(rootdotgit): + with open(rootdotgit) as f: + line = f.readline() + if line.startswith("gitdir: "): + rootdotgit = line[8:].rstrip() + + newpath = os.path.abspath(os.path.join(root,rootdotgit,"modules",path)) + print(f"root is {root} rootdotgit is {rootdotgit} newpath is {newpath}") + if not os.path.isdir(os.path.join(newpath,os.pardir)): + os.makedirs(os.path.abspath(os.path.join(newpath,os.pardir))) + + shutil.move(os.path.join(repodir,".git"), newpath) + with open(os.path.join(repodir,".git"), "w") as f: + f.write("gitdir: "+newpath) + + if not tmpurl: From a12a5b051580b5f26048ab87ffe0521e4ba135ef Mon Sep 17 00:00:00 2001 From: James Edwards Date: Thu, 1 Feb 2024 14:22:41 -0700 Subject: [PATCH 059/159] add --force option and cowardly refusal --- src/git-fleximod | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/src/git-fleximod b/src/git-fleximod index 2ebb2e53..5489ae16 100755 --- a/src/git-fleximod +++ b/src/git-fleximod @@ -60,6 +60,13 @@ def commandline_arguments(args=None): nargs="*", help="Component(s) listed in the gitmodules file which should be ignored.", ) + parser.add_argument( + "-f", + "--force", + action="store_true", + default=False, + help="Override cautions and update or checkout over locally modified repository." + ) parser.add_argument( "-o", @@ -152,6 +159,7 @@ def commandline_arguments(args=None): options.components, options.exclude, options.verbose, + options.force, action, ) @@ -281,6 +289,7 @@ def submodule_checkout(root, name, path, url=None, tag=None): def submodules_status(gitmodules, root_dir): testfails = 0 + localmods = 0 for name in gitmodules.sections(): path = gitmodules.get(name, "path") tag = gitmodules.get(name, "fxtag") @@ -321,11 +330,17 @@ def submodules_status(gitmodules, root_dir): status = git.git_operation("status","--ignore-submodules") if "nothing to commit" not in status: + localmods = localmods+1 print('M'+textwrap.indent(status,' ')) - return testfails + return testfails, localmods -def submodules_update(gitmodules, root_dir): +def submodules_update(gitmodules, root_dir, force): + _,localmods = submodules_status(gitmodules, root_dir) + print("") + if localmods and not force: + print(f"Repository has local mods, cowardly refusing to continue, fix issues or use --force to override") + return for name in gitmodules.sections(): fxtag = gitmodules.get(name, "fxtag") path = gitmodules.get(name, "path") @@ -358,15 +373,20 @@ def submodules_update(gitmodules, root_dir): git.git_operation("fetch", newremote, "--tags") atag = git.git_operation("describe", "--tags", "--always").rstrip() if fxtag and fxtag != atag: - print(f"Updating {name} to {fxtag}") + print(f"{name:>20} updated to {fxtag}") git.git_operation("checkout", fxtag) elif not fxtag: - print(f"No fxtag found for submodule {name}") + print(f"No fxtag found for submodule {name:>20}") else: - print(f"submodule {name} up to date.") + print(f"{name:>20} up to date.") -def submodules_checkout(gitmodules, root_dir, requiredlist): +def submodules_checkout(gitmodules, root_dir, requiredlist, force): + _,localmods = submodules_status(gitmodules, root_dir) + print("") + if localmods and not force: + print(f"Repository has local mods, cowardly refusing to continue, fix issues or use --force to override") + return for name in gitmodules.sections(): fxrequired = gitmodules.get(name, "fxrequired") fxsparse = gitmodules.get(name, "fxsparse") @@ -393,7 +413,7 @@ def submodules_checkout(gitmodules, root_dir, requiredlist): def submodules_test(gitmodules, root_dir): # First check that fxtags are present and in sync with submodule hashes - testfails = submodules_status(gitmodules, root_dir) + testfails,localmods = submodules_status(gitmodules, root_dir) # Then make sure that urls are consistant with fxurls (not forks and not ssh) # and that sparse checkout files exist for name in gitmodules.sections(): @@ -407,7 +427,7 @@ def submodules_test(gitmodules, root_dir): if fxsparse and not os.path.isfile(os.path.join(root_dir, path, fxsparse)): print(f"sparse submodule {name} sparse checkout file {fxsparse} not found") testfails += 1 - return testfails + return testfails+localmods @@ -419,6 +439,7 @@ def _main_func(): includelist, excludelist, verbose, + force, action, ) = commandline_arguments() # Get a logger for the package @@ -445,9 +466,9 @@ def _main_func(): ) retval = 0 if action == "update": - submodules_update(gitmodules, root_dir) + submodules_update(gitmodules, root_dir, force) elif action == "checkout": - submodules_checkout(gitmodules, root_dir, fxrequired) + submodules_checkout(gitmodules, root_dir, fxrequired, force) elif action == "status": submodules_status(gitmodules, root_dir) elif action == "test": From ce22059f0f860c9e9f0961ee2637d21d967facfb Mon Sep 17 00:00:00 2001 From: James Edwards Date: Thu, 1 Feb 2024 14:37:08 -0700 Subject: [PATCH 060/159] start at root git dir unless --path flag is used --- src/git-fleximod | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/git-fleximod b/src/git-fleximod index 5489ae16..d0e6149f 100755 --- a/src/git-fleximod +++ b/src/git-fleximod @@ -5,6 +5,7 @@ import shutil import logging import argparse import textwrap +from pathlib import Path from fleximod import utils from fleximod.gitinterface import GitInterface from fleximod.gitmodules import GitModules @@ -13,6 +14,17 @@ from configparser import NoOptionError # logger variable is global logger = None +def find_root_dir(filename=".git"): + d = Path.cwd() + root = Path(d.root) + while d != root: + attempt = d / filename + if attempt.is_dir(): + return attempt + d = d.parent + return None + + def commandline_arguments(args=None): description = """ %(prog)s manages checking out groups of gitsubmodules with addtional support for Earth System Models @@ -42,8 +54,8 @@ def commandline_arguments(args=None): parser.add_argument( "-C", "--path", - default=os.getcwd(), - help="Toplevel repository directory. Defaults to current directory.", + default=find_root_dir(), + help="Toplevel repository directory. Defaults to top git directory relative to current.", ) parser.add_argument( @@ -430,6 +442,7 @@ def submodules_test(gitmodules, root_dir): return testfails+localmods + def _main_func(): ( @@ -455,6 +468,7 @@ def _main_func(): utils.fatal_error( "No {} found in {} or any of it's parents".format(file_name, root_dir) ) + root_dir = os.path.dirname(file_path) logger.info(f"root_dir is {root_dir}") gitmodules = GitModules( From 8b7267959971c16d66803d92faa833055a8ff213 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Thu, 1 Feb 2024 14:43:10 -0700 Subject: [PATCH 061/159] remove test from visable options --- src/git-fleximod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/git-fleximod b/src/git-fleximod index d0e6149f..da8431a1 100755 --- a/src/git-fleximod +++ b/src/git-fleximod @@ -41,7 +41,7 @@ def commandline_arguments(args=None): "action", choices=choices, default="checkout", - help=f"Subcommand of fleximod, choices are {choices}", + help=f"Subcommand of fleximod, choices are {choices[:-1]}", ) parser.add_argument( From a990864cef359b6a2f6e9a75f420ec05f669f894 Mon Sep 17 00:00:00 2001 From: Negin Sobhani Date: Thu, 1 Feb 2024 17:34:21 -0700 Subject: [PATCH 062/159] update setup.cfg --- setup.cfg | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 9a13827a..59964655 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,4 +2,7 @@ license_file = License [build_manpages] manpages = - man/git-fleximod.1:object=parser:pyfile=bin/git-fleximod + # solution 1: --split up the commandline_arguments parser + #man/git-fleximod.1:function:commandline_arguments:pyfile=src/git-fleximod + # solution 2 -- make parser global + man/git-fleximod.2:pyfile=src/git-fleximod:object=parser From d2123b6022ad648ca78241a155fed93d13642ff4 Mon Sep 17 00:00:00 2001 From: Negin Sobhani Date: Thu, 1 Feb 2024 17:34:43 -0700 Subject: [PATCH 063/159] updates for setup.py --- setup.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index f68a4ea9..53cc4e43 100644 --- a/setup.py +++ b/setup.py @@ -2,6 +2,8 @@ import os from setuptools import setup, find_packages from distutils.util import convert_path +from setuptools.command.build_py import build_py +from setuptools.command.install import install from build_manpages import build_manpages, get_build_py_cmd, get_install_cmd with open("README.md", "r") as fh: @@ -14,7 +16,8 @@ setuptools.setup( - scripts=["src/git-fleximod"], # This is the name of the package + name="git-fleximod", # package name + #scripts=["src/git-fleximod"], # This is the name of the package version=main_ns['__version__'], # The initial release version author="Jim Edwards", # Full name of the author maintainer="jedwards4b", @@ -38,6 +41,6 @@ # Re-define build_py and install commands so the manual pages # are automatically re-generated and installed 'build_py': get_build_py_cmd(), - 'install': get_install_cmd(), + 'install': get_install_cmd(install), } ) From 89dfbbe5e6a6d2ba532d4ec8a05ddd83ed3abb79 Mon Sep 17 00:00:00 2001 From: Negin Sobhani Date: Thu, 1 Feb 2024 17:39:19 -0700 Subject: [PATCH 064/159] setup.cfg --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 59964655..8df52e87 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,7 +2,7 @@ license_file = License [build_manpages] manpages = - # solution 1: --split up the commandline_arguments parser + # solution 1: --split up the commandline_arguments parser --better #man/git-fleximod.1:function:commandline_arguments:pyfile=src/git-fleximod # solution 2 -- make parser global man/git-fleximod.2:pyfile=src/git-fleximod:object=parser From 3fdff62ff545bf6fda5c7e475e7fc2c98f1e1f12 Mon Sep 17 00:00:00 2001 From: Negin Sobhani Date: Thu, 1 Feb 2024 17:47:53 -0700 Subject: [PATCH 065/159] pointing to the better solution --- setup.cfg | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index 8df52e87..7bd0d34d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,7 +2,7 @@ license_file = License [build_manpages] manpages = - # solution 1: --split up the commandline_arguments parser --better - #man/git-fleximod.1:function:commandline_arguments:pyfile=src/git-fleximod + # solution 1: --split up the commandline_arguments parser + man/git-fleximod.1:function:commandline_arguments:pyfile=src/git-fleximod # solution 2 -- make parser global - man/git-fleximod.2:pyfile=src/git-fleximod:object=parser + #man/git-fleximod.2:pyfile=src/git-fleximod:object=parser From 49e8fadbf834e1c03effd933e7bd5698b7555cc0 Mon Sep 17 00:00:00 2001 From: Negin Sobhani Date: Thu, 1 Feb 2024 18:11:22 -0700 Subject: [PATCH 066/159] minimal changes required --- src/git-fleximod | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/git-fleximod b/src/git-fleximod index d0e6149f..26c00f55 100755 --- a/src/git-fleximod +++ b/src/git-fleximod @@ -24,8 +24,7 @@ def find_root_dir(filename=".git"): d = d.parent return None - -def commandline_arguments(args=None): +def get_parser(): description = """ %(prog)s manages checking out groups of gitsubmodules with addtional support for Earth System Models """ @@ -127,6 +126,11 @@ def commandline_arguments(args=None): "information to the screen and log file.", ) + return parser + +def commandline_arguments(args=None): + parser = get_parser() + if args: options = parser.parse_args(args) else: From 8c09b5b016629660613f0c0acb3889c5b8d5209b Mon Sep 17 00:00:00 2001 From: Negin Sobhani Date: Thu, 1 Feb 2024 18:22:59 -0700 Subject: [PATCH 067/159] updates to setup.cfg --- setup.cfg | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup.cfg b/setup.cfg index 7bd0d34d..f6b73700 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,7 +2,4 @@ license_file = License [build_manpages] manpages = - # solution 1: --split up the commandline_arguments parser man/git-fleximod.1:function:commandline_arguments:pyfile=src/git-fleximod - # solution 2 -- make parser global - #man/git-fleximod.2:pyfile=src/git-fleximod:object=parser From a9bd103363ec9fe041daf4ee36bc915fe7209e15 Mon Sep 17 00:00:00 2001 From: Negin Sobhani Date: Thu, 1 Feb 2024 18:23:22 -0700 Subject: [PATCH 068/159] minor changes --- setup.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.cfg b/setup.cfg index f6b73700..7bd0d34d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,4 +2,7 @@ license_file = License [build_manpages] manpages = + # solution 1: --split up the commandline_arguments parser man/git-fleximod.1:function:commandline_arguments:pyfile=src/git-fleximod + # solution 2 -- make parser global + #man/git-fleximod.2:pyfile=src/git-fleximod:object=parser From 3b82247ef72c9c3f2f9e4d5697bbb735bb7181d2 Mon Sep 17 00:00:00 2001 From: Negin Sobhani Date: Thu, 1 Feb 2024 18:26:19 -0700 Subject: [PATCH 069/159] fix syntax and new function name --- setup.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 7bd0d34d..66ff24ad 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,7 +2,7 @@ license_file = License [build_manpages] manpages = - # solution 1: --split up the commandline_arguments parser - man/git-fleximod.1:function:commandline_arguments:pyfile=src/git-fleximod + # solution 1: -- use parser function + man/git-fleximod.1:function=get_parser:pyfile=src/git-fleximod # solution 2 -- make parser global #man/git-fleximod.2:pyfile=src/git-fleximod:object=parser From 23e0813eb3340dd410e989a48a7b753a69d0ae74 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Fri, 2 Feb 2024 07:21:14 -0700 Subject: [PATCH 070/159] names were too similar, force needs to be optional --- src/git-fleximod | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/git-fleximod b/src/git-fleximod index 1a55e916..984ae9e1 100755 --- a/src/git-fleximod +++ b/src/git-fleximod @@ -241,7 +241,7 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master print(f"Successfully checked out {name}") -def submodule_checkout(root, name, path, url=None, tag=None): +def single_submodule_checkout(root, name, path, url=None, tag=None, force=False): git = GitInterface(root, logger) repodir = os.path.join(root, path) if os.path.exists(os.path.join(repodir, ".git")): @@ -291,7 +291,7 @@ def submodule_checkout(root, name, path, url=None, tag=None): # recursively handle this checkout print(f"Recursively checking out submodules of {name} {repodir} {url}") gitmodules = GitModules(logger,confpath=repodir) - submodules_checkout(gitmodules, repodir, ["I:T"]) + submodules_checkout(gitmodules, repodir, ["I:T"], force=force) if os.path.exists(os.path.join(repodir, ".git")): print(f"Successfully checked out {name}") else: @@ -397,7 +397,7 @@ def submodules_update(gitmodules, root_dir, force): print(f"{name:>20} up to date.") -def submodules_checkout(gitmodules, root_dir, requiredlist, force): +def submodules_checkout(gitmodules, root_dir, requiredlist, force=False): _,localmods = submodules_status(gitmodules, root_dir) print("") if localmods and not force: @@ -425,7 +425,7 @@ def submodules_checkout(gitmodules, root_dir, requiredlist, force): "Calling submodule_checkout({},{},{})".format(root_dir, name, path) ) - submodule_checkout(root_dir, name, path, url=url, tag=fxtag) + single_submodule_checkout(root_dir, name, path, url=url, tag=fxtag, force=force) def submodules_test(gitmodules, root_dir): # First check that fxtags are present and in sync with submodule hashes From ae4c9c5962fe0ddbcc8043d9a647115dfb7e89e8 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Fri, 2 Feb 2024 09:30:33 -0700 Subject: [PATCH 071/159] more cleanup --- setup.py | 12 ++++++++---- src/git-fleximod | 12 ++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/setup.py b/setup.py index 53cc4e43..259f9589 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,8 @@ -import setuptools +import sys import os +sys.path.insert(0,os.path.join(os.getenv("CONDA_PREFIX"),"lib","python3.12","site-packages")) + +import setuptools from setuptools import setup, find_packages from distutils.util import convert_path from setuptools.command.build_py import build_py @@ -17,7 +20,7 @@ setuptools.setup( name="git-fleximod", # package name - #scripts=["src/git-fleximod"], # This is the name of the package + scripts=["src/git-fleximod"], # This is the name of the package version=main_ns['__version__'], # The initial release version author="Jim Edwards", # Full name of the author maintainer="jedwards4b", @@ -25,8 +28,9 @@ description="Extended support for git-submodule and git-sparse-checkout", long_description=long_description, # Long description read from the the readme file long_description_content_type="text/markdown", - packages=['fleximod'], # List of all python modules to be installed - package_dir={'fleximod': 'src/fleximod'}, + packages=find_packages(), # List of all python modules to be installed + package_dir={'git-fleximod': 'src', + 'fleximod': 'src/fleximod'}, package_data={"":['version.txt']}, classifiers=[ "Programming Language :: Python :: 3", diff --git a/src/git-fleximod b/src/git-fleximod index 984ae9e1..0706a562 100755 --- a/src/git-fleximod +++ b/src/git-fleximod @@ -273,7 +273,6 @@ def single_submodule_checkout(root, name, path, url=None, tag=None, force=False) rootdotgit = line[8:].rstrip() newpath = os.path.abspath(os.path.join(root,rootdotgit,"modules",path)) - print(f"root is {root} rootdotgit is {rootdotgit} newpath is {newpath}") if not os.path.isdir(os.path.join(newpath,os.pardir)): os.makedirs(os.path.abspath(os.path.join(newpath,os.pardir))) @@ -321,14 +320,17 @@ def submodules_status(gitmodules, root_dir): tags = rootgit.git_operation("ls-remote","--tags",url) atag = None for htag in tags.split('\n'): - if tag in htag: + if tag and tag in htag: atag = (htag.split()[1])[10:] break - if tag == atag: + if tag and tag == atag: print(f"e {name:>20} not checked out, aligned at tag {tag}") - else: + elif tag: print(f"e {name:>20} not checked out, out of sync at tag {atag}, expected tag is {tag}") testfails += 1 + else: + print(f"e {name:>20} has no fxtag defined in .gitmodules") + testfails +=1 else: with utils.pushd(newpath): git = GitInterface(newpath, logger) @@ -482,6 +484,8 @@ def _main_func(): includelist=includelist, excludelist=excludelist, ) + if not gitmodules.sections(): + sys.exit("No submodule components found") retval = 0 if action == "update": submodules_update(gitmodules, root_dir, force) From 140529afedf13f88ba3673526917d6dd27750c3e Mon Sep 17 00:00:00 2001 From: James Edwards Date: Fri, 2 Feb 2024 10:00:15 -0700 Subject: [PATCH 072/159] pre-commit cleanup --- setup.py | 51 ++++++------ src/fleximod/gitinterface.py | 15 ++-- src/fleximod/gitmodules.py | 17 ++-- src/fleximod/lstripreader.py | 7 +- src/fleximod/version.py | 2 +- src/git-fleximod | 150 +++++++++++++++++++---------------- 6 files changed, 129 insertions(+), 113 deletions(-) diff --git a/setup.py b/setup.py index 259f9589..8f8a49cb 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,13 @@ import sys import os -sys.path.insert(0,os.path.join(os.getenv("CONDA_PREFIX"),"lib","python3.12","site-packages")) + +sys.path.insert( + 0, os.path.join(os.getenv("CONDA_PREFIX"), "lib", "python3.12", "site-packages") +) import setuptools -from setuptools import setup, find_packages -from distutils.util import convert_path -from setuptools.command.build_py import build_py +from setuptools import find_packages +from setuptools.util import convert_path from setuptools.command.install import install from build_manpages import build_manpages, get_build_py_cmd, get_install_cmd @@ -13,38 +15,37 @@ long_description = fh.read() main_ns = {} -ver_path = convert_path('src/fleximod/version.py') +ver_path = convert_path("src/fleximod/version.py") with open(ver_path) as ver_file: exec(ver_file.read(), main_ns) - + setuptools.setup( - name="git-fleximod", # package name - scripts=["src/git-fleximod"], # This is the name of the package - version=main_ns['__version__'], # The initial release version - author="Jim Edwards", # Full name of the author + name="git-fleximod", # package name + scripts=["src/git-fleximod"], # This is the name of the package + version=main_ns["__version__"], # The initial release version + author="Jim Edwards", # Full name of the author maintainer="jedwards4b", license="MIT License", description="Extended support for git-submodule and git-sparse-checkout", - long_description=long_description, # Long description read from the the readme file + long_description=long_description, # Long description read from the the readme file long_description_content_type="text/markdown", - packages=find_packages(), # List of all python modules to be installed - package_dir={'git-fleximod': 'src', - 'fleximod': 'src/fleximod'}, - package_data={"":['version.txt']}, + packages=find_packages(), # List of all python modules to be installed + package_dir={"git-fleximod": "src", "fleximod": "src/fleximod"}, + package_data={"": ["version.txt"]}, classifiers=[ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", - ], # Information to filter the project on PyPi website - python_requires='>=3.6', # Minimum version requirement of the package -# py_modules=['git-fleximod'], # Name of the python package - install_requires=["GitPython"], # Install other dependencies if any + ], # Information to filter the project on PyPi website + python_requires=">=3.6", # Minimum version requirement of the package + # py_modules=['git-fleximod'], # Name of the python package + install_requires=["GitPython"], # Install other dependencies if any cmdclass={ - 'build_manpages': build_manpages, - # Re-define build_py and install commands so the manual pages - # are automatically re-generated and installed - 'build_py': get_build_py_cmd(), - 'install': get_install_cmd(install), - } + "build_manpages": build_manpages, + # Re-define build_py and install commands so the manual pages + # are automatically re-generated and installed + "build_py": get_build_py_cmd(), + "install": get_install_cmd(install), + }, ) diff --git a/src/fleximod/gitinterface.py b/src/fleximod/gitinterface.py index c127163d..96bb9ac5 100644 --- a/src/fleximod/gitinterface.py +++ b/src/fleximod/gitinterface.py @@ -1,7 +1,7 @@ import os -import logging from fleximod import utils + class GitInterface: def __init__(self, repo_path, logger): logger.debug("Initialize GitInterface for {}".format(repo_path)) @@ -9,6 +9,7 @@ def __init__(self, repo_path, logger): self.logger = logger try: import git + self._use_module = True try: self.repo = git.Repo(repo_path) # Initialize GitPython repo @@ -18,17 +19,17 @@ def __init__(self, repo_path, logger): msg = "Using GitPython interface to git" except ImportError: self._use_module = False - if not os.path.exists(os.path.join(repo_path,".git")): + if not os.path.exists(os.path.join(repo_path, ".git")): self._init_git_repo() msg = "Using shell interface to git" self.logger.info(msg) - + def _git_command(self, operation, *args): self.logger.info(operation) if self._use_module and operation != "submodule": return getattr(self.repo.git, operation)(*args) else: - return ["git", "-C",self.repo_path, operation] + list(args) + return ["git", "-C", self.repo_path, operation] + list(args) def _init_git_repo(self): if self._use_module: @@ -37,7 +38,7 @@ def _init_git_repo(self): command = ("git", "-C", self.repo_path, "init") utils.execute_subprocess(command) - + # pylint: disable=unused-argument def git_operation(self, operation, *args, **kwargs): command = self._git_command(operation, *args) self.logger.info(command) @@ -51,7 +52,7 @@ def config_get_value(self, section, name): config = self.repo.config_reader() return config.get_value(section, name) else: - cmd = ("git","-C",self.repo_path,"config", "--get", f"{section}.{name}") + cmd = ("git", "-C", self.repo_path, "config", "--get", f"{section}.{name}") output = utils.execute_subprocess(cmd, output_to_caller=True) return output.strip() @@ -61,6 +62,6 @@ def config_set_value(self, section, name, value): writer.set_value(section, name, value) writer.release() # Ensure changes are saved else: - cmd = ("git","-C",self.repo_path,"config", f"{section}.{name}", value) + cmd = ("git", "-C", self.repo_path, "config", f"{section}.{name}", value) self.logger.info(cmd) utils.execute_subprocess(cmd, output_to_caller=True) diff --git a/src/fleximod/gitmodules.py b/src/fleximod/gitmodules.py index a6f75893..11a24572 100644 --- a/src/fleximod/gitmodules.py +++ b/src/fleximod/gitmodules.py @@ -3,6 +3,7 @@ from configparser import ConfigParser from fleximod.lstripreader import LstripReader + class GitModules(ConfigParser): def __init__( self, @@ -19,11 +20,15 @@ def __init__( excludelist: Optional list of submodules to exclude. """ self.logger = logger - self.logger.debug("Creating a GitModules object {} {} {} {}".format(confpath,conffile,includelist,excludelist)) + self.logger.debug( + "Creating a GitModules object {} {} {} {}".format( + confpath, conffile, includelist, excludelist + ) + ) ConfigParser.__init__(self) self.conf_file = os.path.join(confpath, conffile) # first create a backup of this file to be restored on deletion of the object - shutil.copy(self.conf_file, self.conf_file+".save") + shutil.copy(self.conf_file, self.conf_file + ".save") self.read_file(LstripReader(self.conf_file), source=conffile) self.includelist = includelist self.excludelist = excludelist @@ -34,7 +39,7 @@ def set(self, name, option, value): Ensures the appropriate section exists for the submodule. Calls the parent class's set method to store the value. """ - self.logger.debug("set called {} {} {}".format(name,option,value)) + self.logger.debug("set called {} {} {}".format(name, option, value)) section = f'submodule "{name}"' if not self.has_section(section): self.add_section(section) @@ -47,7 +52,7 @@ def get(self, name, option, raw=False, vars=None, fallback=None): Uses the parent class's get method to access the value. Handles potential errors if the section or option doesn't exist. """ - self.logger.debug("get called {} {}".format(name,option)) + self.logger.debug("get called {} {}".format(name, option)) section = f'submodule "{name}"' try: return ConfigParser.get( @@ -62,8 +67,8 @@ def save(self): def __del__(self): self.logger.debug("Destroying GitModules object") - shutil.move(self.conf_file+".save", self.conf_file) - + shutil.move(self.conf_file + ".save", self.conf_file) + def sections(self): """Strip the submodule part out of section and just use the name""" self.logger.debug("calling GitModules sections iterator") diff --git a/src/fleximod/lstripreader.py b/src/fleximod/lstripreader.py index 530abd29..01d5580e 100644 --- a/src/fleximod/lstripreader.py +++ b/src/fleximod/lstripreader.py @@ -1,8 +1,8 @@ - class LstripReader(object): "LstripReader formats .gitmodules files to be acceptable for configparser" + def __init__(self, filename): - with open(filename, 'r') as infile: + with open(filename, "r") as infile: lines = infile.readlines() self._lines = list() self._num_lines = len(lines) @@ -19,7 +19,7 @@ def readline(self, size=-1): try: line = self.next() except StopIteration: - line = '' + line = "" if (size > 0) and (len(line) < size): return line[0:size] @@ -41,4 +41,3 @@ def next(self): def __next__(self): return self.next() - diff --git a/src/fleximod/version.py b/src/fleximod/version.py index 7fd229a3..d3ec452c 100644 --- a/src/fleximod/version.py +++ b/src/fleximod/version.py @@ -1 +1 @@ -__version__ = '0.2.0' +__version__ = "0.2.0" diff --git a/src/git-fleximod b/src/git-fleximod index 0706a562..aa587ea3 100755 --- a/src/git-fleximod +++ b/src/git-fleximod @@ -11,9 +11,11 @@ from fleximod.gitinterface import GitInterface from fleximod.gitmodules import GitModules from fleximod.version import __version__ from configparser import NoOptionError + # logger variable is global logger = None + def find_root_dir(filename=".git"): d = Path.cwd() root = Path(d.root) @@ -23,7 +25,8 @@ def find_root_dir(filename=".git"): return attempt d = d.parent return None - + + def get_parser(): description = """ %(prog)s manages checking out groups of gitsubmodules with addtional support for Earth System Models @@ -76,7 +79,7 @@ def get_parser(): "--force", action="store_true", default=False, - help="Override cautions and update or checkout over locally modified repository." + help="Override cautions and update or checkout over locally modified repository.", ) parser.add_argument( @@ -128,6 +131,7 @@ def get_parser(): return parser + def commandline_arguments(args=None): parser = get_parser() @@ -136,7 +140,7 @@ def commandline_arguments(args=None): else: options = parser.parse_args() -# explicitly listing a component overrides the optional flag + # explicitly listing a component overrides the optional flag if options.optional or options.components: fxrequired = ["T:T", "T:F", "I:T"] else: @@ -145,11 +149,11 @@ def commandline_arguments(args=None): action = options.action if not action: action = "checkout" - handlers=[logging.StreamHandler()] + handlers = [logging.StreamHandler()] if options.debug: try: - open("fleximod.log","w") + open("fleximod.log", "w") except PermissionError: sys.exit("ABORT: Could not write file fleximod.log") level = logging.DEBUG @@ -160,21 +164,18 @@ def commandline_arguments(args=None): level = logging.WARNING # Configure the root logger logging.basicConfig( - level=level, - format="%(name)s - %(levelname)s - %(message)s", - handlers=handlers + level=level, format="%(name)s - %(levelname)s - %(message)s", handlers=handlers ) - - if hasattr(options, 'version'): + + if hasattr(options, "version"): exit() - + return ( options.path, options.gitmodules, fxrequired, options.components, options.exclude, - options.verbose, options.force, action, ) @@ -191,7 +192,7 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master # initialize a new git repo and set the sparse checkout flag sprep_repo = os.path.join(root_dir, path) sprepo_git = GitInterface(sprep_repo, logger) - if os.path.exists(os.path.join(sprep_repo,".git")): + if os.path.exists(os.path.join(sprep_repo, ".git")): try: logger.info("Submodule {} found".format(name)) chk = sprepo_git.config_get_value("core", "sparseCheckout") @@ -203,7 +204,6 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master except Exception as e: utils.fatal_error("Unexpected error {} occured.".format(e)) - sprepo_git.config_set_value("core", "sparseCheckout", "true") # set the repository remote @@ -212,28 +212,31 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master superroot = git.git_operation("rev-parse", "--show-superproject-working-tree") if os.path.isfile(os.path.join(root_dir, ".git")): with open(os.path.join(root_dir, ".git")) as f: - gitpath = os.path.abspath(os.path.join(root_dir,f.read().split()[1])) + gitpath = os.path.abspath(os.path.join(root_dir, f.read().split()[1])) topgit = os.path.abspath(os.path.join(gitpath, "modules")) - else: + else: topgit = os.path.abspath(os.path.join(root_dir, ".git", "modules")) if not os.path.isdir(topgit): os.makedirs(topgit) topgit = os.path.join(topgit, name) - logger.debug(f"root_dir is {root_dir} topgit is {topgit} superroot is {superroot}") + logger.debug( + "root_dir is {} topgit is {} superroot is {}".format( + root_dir, topgit, superroot + ) + ) + + if os.path.isdir(os.path.join(root_dir, path, ".git")): + shutil.move(os.path.join(root_dir, path, ".git"), topgit) + with open(os.path.join(root_dir, path, ".git"), "w") as f: + f.write("gitdir: " + os.path.relpath(topgit, os.path.join(root_dir, path))) - if os.path.isdir(os.path.join(root_dir,path,".git")): - shutil.move(os.path.join(root_dir,path, ".git"), topgit) - with open(os.path.join(root_dir,path, ".git"), "w") as f: - f.write("gitdir: " + os.path.relpath(topgit, os.path.join(root_dir,path))) - gitsparse = os.path.abspath(os.path.join(topgit, "info", "sparse-checkout")) if os.path.isfile(gitsparse): logger.warning("submodule {} is already initialized".format(name)) return - - shutil.copy(os.path.join(root_dir,path, sparsefile), gitsparse) + shutil.copy(os.path.join(root_dir, path, sparsefile), gitsparse) # Finally checkout the repo sprepo_git.git_operation("fetch", "--depth=1", "origin", "--tags") @@ -258,38 +261,35 @@ def single_submodule_checkout(root, name, path, url=None, tag=None, force=False) # it will be done by the GitModules class if url.startswith("git@"): tmpurl = url - url = url.replace("git@github.com:", "https://github.com") + url = url.replace("git@github.com:", "https://github.com") git.git_operation("clone", url, path) smgit = GitInterface(repodir, logger) if not tag: tag = smgit.git_operation("describe", "--tags", "--always").rstrip() - smgit.git_operation("checkout",tag) + smgit.git_operation("checkout", tag) # Now need to move the .git dir to the submodule location - rootdotgit = os.path.join(root,".git") + rootdotgit = os.path.join(root, ".git") if os.path.isfile(rootdotgit): with open(rootdotgit) as f: line = f.readline() if line.startswith("gitdir: "): rootdotgit = line[8:].rstrip() - - newpath = os.path.abspath(os.path.join(root,rootdotgit,"modules",path)) - if not os.path.isdir(os.path.join(newpath,os.pardir)): - os.makedirs(os.path.abspath(os.path.join(newpath,os.pardir))) - - shutil.move(os.path.join(repodir,".git"), newpath) - with open(os.path.join(repodir,".git"), "w") as f: - f.write("gitdir: "+newpath) - - - - + + newpath = os.path.abspath(os.path.join(root, rootdotgit, "modules", path)) + if not os.path.isdir(os.path.join(newpath, os.pardir)): + os.makedirs(os.path.abspath(os.path.join(newpath, os.pardir))) + + shutil.move(os.path.join(repodir, ".git"), newpath) + with open(os.path.join(repodir, ".git"), "w") as f: + f.write("gitdir: " + newpath) + if not tmpurl: logger.debug(git.git_operation("submodule", "update", "--init", "--", path)) if os.path.exists(os.path.join(repodir, ".gitmodules")): # recursively handle this checkout print(f"Recursively checking out submodules of {name} {repodir} {url}") - gitmodules = GitModules(logger,confpath=repodir) + gitmodules = GitModules(logger, confpath=repodir) submodules_checkout(gitmodules, repodir, ["I:T"], force=force) if os.path.exists(os.path.join(repodir, ".git")): print(f"Successfully checked out {name}") @@ -315,22 +315,23 @@ def submodules_status(gitmodules, root_dir): if not os.path.exists(os.path.join(newpath, ".git")): rootgit = GitInterface(root_dir, logger) # submodule commands use path, not name - nhash = (rootgit.git_operation("submodule","status",path).split()[0])[1:] url = gitmodules.get(name, "url") - tags = rootgit.git_operation("ls-remote","--tags",url) + tags = rootgit.git_operation("ls-remote", "--tags", url) atag = None - for htag in tags.split('\n'): + for htag in tags.split("\n"): if tag and tag in htag: atag = (htag.split()[1])[10:] break if tag and tag == atag: print(f"e {name:>20} not checked out, aligned at tag {tag}") elif tag: - print(f"e {name:>20} not checked out, out of sync at tag {atag}, expected tag is {tag}") + print( + f"e {name:>20} not checked out, out of sync at tag {atag}, expected tag is {tag}" + ) testfails += 1 else: print(f"e {name:>20} has no fxtag defined in .gitmodules") - testfails +=1 + testfails += 1 else: with utils.pushd(newpath): git = GitInterface(newpath, logger) @@ -346,24 +347,27 @@ def submodules_status(gitmodules, root_dir): ) testfails += 1 - status = git.git_operation("status","--ignore-submodules") + status = git.git_operation("status", "--ignore-submodules") if "nothing to commit" not in status: - localmods = localmods+1 - print('M'+textwrap.indent(status,' ')) - + localmods = localmods + 1 + print("M" + textwrap.indent(status, " ")) + return testfails, localmods + def submodules_update(gitmodules, root_dir, force): - _,localmods = submodules_status(gitmodules, root_dir) + _, localmods = submodules_status(gitmodules, root_dir) print("") if localmods and not force: - print(f"Repository has local mods, cowardly refusing to continue, fix issues or use --force to override") + print( + "Repository has local mods, cowardly refusing to continue, fix issues or use --force to override" + ) return for name in gitmodules.sections(): fxtag = gitmodules.get(name, "fxtag") path = gitmodules.get(name, "path") url = gitmodules.get(name, "url") - logger.info(f"name={name} path={path} url={url} fxtag={fxtag}") + logger.info("name={} path={} url={} fxtag={}".format(name, path, url, fxtag)) if os.path.exists(os.path.join(path, ".git")): submoddir = os.path.join(root_dir, path) with utils.pushd(submoddir): @@ -400,10 +404,12 @@ def submodules_update(gitmodules, root_dir, force): def submodules_checkout(gitmodules, root_dir, requiredlist, force=False): - _,localmods = submodules_status(gitmodules, root_dir) + _, localmods = submodules_status(gitmodules, root_dir) print("") if localmods and not force: - print(f"Repository has local mods, cowardly refusing to continue, fix issues or use --force to override") + print( + "Repository has local mods, cowardly refusing to continue, fix issues or use --force to override" + ) return for name in gitmodules.sections(): fxrequired = gitmodules.get(name, "fxrequired") @@ -416,22 +422,28 @@ def submodules_checkout(gitmodules, root_dir, requiredlist, force=False): if "T:F" == fxrequired: print("Skipping optional component {}".format(name)) continue - + if fxsparse: logger.debug( - f"Callng submodule_sparse_checkout({root_dir}, {name}, {url}, {path}, {fxsparse}, {fxtag}" + "Callng submodule_sparse_checkout({}, {}, {}, {}, {}, {}".format( + root_dir, name, url, path, fxsparse, fxtag + ) ) submodule_sparse_checkout(root_dir, name, url, path, fxsparse, tag=fxtag) else: logger.debug( "Calling submodule_checkout({},{},{})".format(root_dir, name, path) ) - - single_submodule_checkout(root_dir, name, path, url=url, tag=fxtag, force=force) + + single_submodule_checkout( + root_dir, name, path, url=url, tag=fxtag, force=force + ) + def submodules_test(gitmodules, root_dir): # First check that fxtags are present and in sync with submodule hashes - testfails,localmods = submodules_status(gitmodules, root_dir) + testfails, localmods = submodules_status(gitmodules, root_dir) + print("") # Then make sure that urls are consistant with fxurls (not forks and not ssh) # and that sparse checkout files exist for name in gitmodules.sections(): @@ -440,16 +452,14 @@ def submodules_test(gitmodules, root_dir): fxsparse = gitmodules.get(name, "fxsparse") path = gitmodules.get(name, "path") if not fxurl or url != fxurl: - print(f"submodule {name} url {url} not in sync with required {fxurl}") + print(f"{name:>20} url {url} not in sync with required {fxurl}") testfails += 1 if fxsparse and not os.path.isfile(os.path.join(root_dir, path, fxsparse)): - print(f"sparse submodule {name} sparse checkout file {fxsparse} not found") + print(f"{name:>20} sparse checkout file {fxsparse} not found") testfails += 1 - return testfails+localmods - + return testfails + localmods - def _main_func(): ( root_dir, @@ -457,15 +467,14 @@ def _main_func(): fxrequired, includelist, excludelist, - verbose, force, action, ) = commandline_arguments() # Get a logger for the package global logger logger = logging.getLogger(__name__) - - logger.info(f"action is {action}") + + logger.info("action is {}".format(action)) if not os.path.isfile(os.path.join(root_dir, file_name)): file_path = utils.find_upwards(root_dir, file_name) @@ -476,7 +485,7 @@ def _main_func(): ) root_dir = os.path.dirname(file_path) - logger.info(f"root_dir is {root_dir}") + logger.info("root_dir is {}".format(root_dir)) gitmodules = GitModules( logger, confpath=root_dir, @@ -497,7 +506,8 @@ def _main_func(): retval = submodules_test(gitmodules, root_dir) else: utils.fatal_error(f"unrecognized action request {action}") - return(retval) - + return retval + + if __name__ == "__main__": sys.exit(_main_func()) From 0a5cf50fb696f84ea5a71bcdb973a8972c14bb55 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Fri, 2 Feb 2024 12:43:54 -0700 Subject: [PATCH 073/159] setup.py builds manpage but doesnt install --- .github/workflows/pre-commit | 13 ++++++ setup.py | 76 +++++++++++++++++++----------------- 2 files changed, 53 insertions(+), 36 deletions(-) create mode 100644 .github/workflows/pre-commit diff --git a/.github/workflows/pre-commit b/.github/workflows/pre-commit new file mode 100644 index 00000000..1a6ad008 --- /dev/null +++ b/.github/workflows/pre-commit @@ -0,0 +1,13 @@ +name: pre-commit +on: + pull_request: + push: + branches: [main] + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v3 + - uses: pre-commit/action@v3.0.0 diff --git a/setup.py b/setup.py index 8f8a49cb..048db759 100644 --- a/setup.py +++ b/setup.py @@ -1,51 +1,55 @@ import sys import os +sys.path.insert(0, os.path.join(os.getenv("CONDA_PREFIX"), "lib", "python3.12")) sys.path.insert( 0, os.path.join(os.getenv("CONDA_PREFIX"), "lib", "python3.12", "site-packages") ) import setuptools -from setuptools import find_packages -from setuptools.util import convert_path -from setuptools.command.install import install +from setuptools import find_packages, convert_path from build_manpages import build_manpages, get_build_py_cmd, get_install_cmd -with open("README.md", "r") as fh: - long_description = fh.read() - main_ns = {} -ver_path = convert_path("src/fleximod/version.py") +ver_path = convert_path( + os.path.join(os.path.dirname(__file__), "src", "fleximod", "version.py") +) +print(f"ver_path is {ver_path}") with open(ver_path) as ver_file: exec(ver_file.read(), main_ns) +if __name__ == "__main__": + with open("README.md", "r") as fh: + long_description = fh.read() -setuptools.setup( - name="git-fleximod", # package name - scripts=["src/git-fleximod"], # This is the name of the package - version=main_ns["__version__"], # The initial release version - author="Jim Edwards", # Full name of the author - maintainer="jedwards4b", - license="MIT License", - description="Extended support for git-submodule and git-sparse-checkout", - long_description=long_description, # Long description read from the the readme file - long_description_content_type="text/markdown", - packages=find_packages(), # List of all python modules to be installed - package_dir={"git-fleximod": "src", "fleximod": "src/fleximod"}, - package_data={"": ["version.txt"]}, - classifiers=[ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - ], # Information to filter the project on PyPi website - python_requires=">=3.6", # Minimum version requirement of the package - # py_modules=['git-fleximod'], # Name of the python package - install_requires=["GitPython"], # Install other dependencies if any - cmdclass={ - "build_manpages": build_manpages, - # Re-define build_py and install commands so the manual pages - # are automatically re-generated and installed - "build_py": get_build_py_cmd(), - "install": get_install_cmd(install), - }, -) + setuptools.setup( + name="git-fleximod", # package name + scripts=["src/git-fleximod"], # This is the name of the package + version=main_ns["__version__"], # The initial release version + author="Jim Edwards", # Full name of the author + maintainer="jedwards4b", + license="MIT License", + description="Extended support for git-submodule and git-sparse-checkout", + long_description=long_description, # Long description read from the the readme file + long_description_content_type="text/markdown", + packages=find_packages( + where="src" + ), # List of all python modules to be installed + package_dir={"": "src", "git-fleximod": "src", "fleximod": "src/fleximod"}, + package_data={"": ["src/fleximod/version.txt", "man/git-fleximod.1"]}, + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], # Information to filter the project on PyPi website + python_requires=">=3.6", # Minimum version requirement of the package + # py_modules=['git-fleximod'], # Name of the python package + install_requires=["GitPython"], # Install other dependencies if any + cmdclass={ + "build_manpages": build_manpages, + # Re-define build_py and install commands so the manual pages + # are automatically re-generated and installed + "build_py": get_build_py_cmd(), + "install": get_install_cmd(), + }, + ) From 64e26fc22df3c3faa7190eff289dce5accc88669 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Fri, 2 Feb 2024 12:46:58 -0700 Subject: [PATCH 074/159] update version --- src/fleximod/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fleximod/version.py b/src/fleximod/version.py index d3ec452c..493f7415 100644 --- a/src/fleximod/version.py +++ b/src/fleximod/version.py @@ -1 +1 @@ -__version__ = "0.2.0" +__version__ = "0.3.0" From 2a2ab6503091b3336bf759a32c0874959a275dcd Mon Sep 17 00:00:00 2001 From: James Edwards Date: Fri, 2 Feb 2024 12:58:20 -0700 Subject: [PATCH 075/159] update README --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cec74c5a..7c968e70 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Git-fleximod is a Python-based tool that extends Git's submodule capabilities, o Basic Usage: git fleximod [options] Available Commands: - install: Install submodules according to configuration. + checkout: Checkout submodules according to git submodule hash configuration. status: Display the status of submodules. update: Update submodules to the tag indicated in .gitmodules variable fxtag. test: Make sure that fxtags and submodule hashes are consistant, @@ -35,7 +35,7 @@ Git-fleximod is a Python-based tool that extends Git's submodule capabilities, o - I:T: Internal and required (always checked out). - I:F: Internal and optional (checked out with --optional flag). fxsparse: Enable sparse checkout for a submodule, pointing to a file containing sparse checkout paths. - fcurl + fxurl: This is the url used in the test subcommand to assure that protected branches do not point to forks ## Sparse Checkouts @@ -55,9 +55,9 @@ Git-fleximod is a Python-based tool that extends Git's submodule capabilities, o Here are some common usage examples: -Installing submodules, including optional ones: +Checkout submodules, including optional ones: ```bash - git fleximod install --optional + git fleximod checkout --optional ``` Updating a specific submodule to the fxtag indicated in .gitmodules: From ad0a976b38cd8de97fdce0d04050e412cf2e3905 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Fri, 2 Feb 2024 17:15:26 -0700 Subject: [PATCH 076/159] add support for fxhash variable in .gitmodules --- src/git-fleximod | 72 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 13 deletions(-) diff --git a/src/git-fleximod b/src/git-fleximod index aa587ea3..5182bb25 100755 --- a/src/git-fleximod +++ b/src/git-fleximod @@ -181,7 +181,9 @@ def commandline_arguments(args=None): ) -def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master"): +def submodule_sparse_checkout( + root_dir, name, url, path, sparsefile, tag="master", fxhash=None +): # first create the module directory if not os.path.isdir(path): os.makedirs(path) @@ -239,12 +241,19 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master shutil.copy(os.path.join(root_dir, path, sparsefile), gitsparse) # Finally checkout the repo - sprepo_git.git_operation("fetch", "--depth=1", "origin", "--tags") - sprepo_git.git_operation("checkout", tag) - print(f"Successfully checked out {name}") + if fxhash: + sprepo_git.git_operation("fetch", "origin", "--tags") + sprepo_git.git_operation("checkout", fxhash) + print(f"Successfully checked out {name:>20} at {fxhash}") + else: + sprepo_git.git_operation("fetch", "--depth=1", "origin", "--tags") + sprepo_git.git_operation("checkout", tag) + print(f"Successfully checked out {name:>20} at {tag}") -def single_submodule_checkout(root, name, path, url=None, tag=None, force=False): +def single_submodule_checkout( + root, name, path, url=None, tag=None, force=False, fxhash=None +): git = GitInterface(root, logger) repodir = os.path.join(root, path) if os.path.exists(os.path.join(repodir, ".git")): @@ -264,9 +273,12 @@ def single_submodule_checkout(root, name, path, url=None, tag=None, force=False) url = url.replace("git@github.com:", "https://github.com") git.git_operation("clone", url, path) smgit = GitInterface(repodir, logger) - if not tag: + if not tag and not fxhash: tag = smgit.git_operation("describe", "--tags", "--always").rstrip() - smgit.git_operation("checkout", tag) + if fxhash: + smgit.git_operation("checkout", fxhash) + else: + smgit.git_operation("checkout", tag) # Now need to move the .git dir to the submodule location rootdotgit = os.path.join(root, ".git") if os.path.isfile(rootdotgit): @@ -308,6 +320,9 @@ def submodules_status(gitmodules, root_dir): for name in gitmodules.sections(): path = gitmodules.get(name, "path") tag = gitmodules.get(name, "fxtag") + fxhash = gitmodules.get(name, "fxhash") + if tag and fxhash: + utils.fatal_error(f"{name:>20} cannot have both fxtag and fxhash") if not path: utils.fatal_error("No path found in .gitmodules for {}".format(name)) newpath = os.path.join(root_dir, path) @@ -329,21 +344,44 @@ def submodules_status(gitmodules, root_dir): f"e {name:>20} not checked out, out of sync at tag {atag}, expected tag is {tag}" ) testfails += 1 + elif fxhash: + n = len(fxhash) + smhash = rootgit.git_operation( + "ls-tree", "--object-only", f"--abbrev={n}", "HEAD", path + ) + if smhash == fxhash: + print(f" {name:>20} not checked out, aligned at hash {fxhash}") + else: + print( + f"s {name:>20} not checked out, out of sync at hash {smhash}, expected hash is {fxhash}" + ) + testfails += 1 else: - print(f"e {name:>20} has no fxtag defined in .gitmodules") + print(f"e {name:>20} has no fxtag nor fxhash defined in .gitmodules") testfails += 1 else: with utils.pushd(newpath): git = GitInterface(newpath, logger) atag = git.git_operation("describe", "--tags", "--always").rstrip() + ahash = git.git_operation("status").partition("\n")[0].split()[-1] if tag and atag != tag: print(f"s {name:>20} {atag} is out of sync with .gitmodules {tag}") testfails += 1 elif tag: print(f" {name:>20} at tag {tag}") + elif fxhash: + rootgit = GitInterface(root_dir, logger) + n = len(fxhash) + if ahash.startswith(fxhash): + print(f" {name:>20} at hash {fxhash}") + else: + print( + f"s {name:>20} {ahash} is out of sync with .gitmodules {fxhash}" + ) + testfails += 1 else: print( - f"e {name:>20} has no tag defined in .gitmodules, module at {atag}" + f"e {name:>20} has no fxtag nor fxhash defined in .gitmodules, module at {atag}" ) testfails += 1 @@ -365,6 +403,7 @@ def submodules_update(gitmodules, root_dir, force): return for name in gitmodules.sections(): fxtag = gitmodules.get(name, "fxtag") + fxhash = gitmodules.get(name, "fxhash") path = gitmodules.get(name, "path") url = gitmodules.get(name, "url") logger.info("name={} path={} url={} fxtag={}".format(name, path, url, fxtag)) @@ -394,11 +433,15 @@ def submodules_update(gitmodules, root_dir, force): if fxtag and fxtag not in tags: git.git_operation("fetch", newremote, "--tags") atag = git.git_operation("describe", "--tags", "--always").rstrip() + print(f"fxtag {fxtag} fxhash {fxhash}") if fxtag and fxtag != atag: print(f"{name:>20} updated to {fxtag}") git.git_operation("checkout", fxtag) - elif not fxtag: - print(f"No fxtag found for submodule {name:>20}") + elif fxhash: + print(f"{name:>20} updated to {fxhash}") + git.git_operation("checkout", fxhash) + elif not (fxtag or fxhash): + print(f"No fxtag nor fxhash found for submodule {name:>20}") else: print(f"{name:>20} up to date.") @@ -415,6 +458,7 @@ def submodules_checkout(gitmodules, root_dir, requiredlist, force=False): fxrequired = gitmodules.get(name, "fxrequired") fxsparse = gitmodules.get(name, "fxsparse") fxtag = gitmodules.get(name, "fxtag") + fxhash = gitmodules.get(name, "fxhash") path = gitmodules.get(name, "path") url = gitmodules.get(name, "url") @@ -429,14 +473,16 @@ def submodules_checkout(gitmodules, root_dir, requiredlist, force=False): root_dir, name, url, path, fxsparse, fxtag ) ) - submodule_sparse_checkout(root_dir, name, url, path, fxsparse, tag=fxtag) + submodule_sparse_checkout( + root_dir, name, url, path, fxsparse, tag=fxtag, fxhash=fxhash + ) else: logger.debug( "Calling submodule_checkout({},{},{})".format(root_dir, name, path) ) single_submodule_checkout( - root_dir, name, path, url=url, tag=fxtag, force=force + root_dir, name, path, url=url, tag=fxtag, force=force, fxhash=fxhash ) From bf56a97251d0f5e5e84910a56e4c8e3036d6b057 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sat, 3 Feb 2024 11:27:03 -0700 Subject: [PATCH 077/159] working on poetry --- pyproject.toml | 28 ++++++++++++ setup.cfg | 8 ---- setup.py | 55 ----------------------- src/{fleximod => }/__init__.py | 0 src/classes/__init__.py | 0 src/{fleximod => classes}/gitinterface.py | 2 +- src/{fleximod => classes}/gitmodules.py | 2 +- src/{fleximod => classes}/lstripreader.py | 0 src/{fleximod => classes}/utils.py | 0 src/{fleximod => classes}/version.py | 0 src/git-fleximod | 11 ++--- tbump.toml | 43 ++++++++++++++++++ tests/__init__.py | 3 ++ tests/test_import.py | 9 ++++ 14 files changed, 91 insertions(+), 70 deletions(-) create mode 100644 pyproject.toml delete mode 100644 setup.cfg delete mode 100644 setup.py rename src/{fleximod => }/__init__.py (100%) create mode 100644 src/classes/__init__.py rename src/{fleximod => classes}/gitinterface.py (98%) rename src/{fleximod => classes}/gitmodules.py (98%) rename src/{fleximod => classes}/lstripreader.py (100%) rename src/{fleximod => classes}/utils.py (100%) rename src/{fleximod => classes}/version.py (100%) create mode 100644 tbump.toml create mode 100644 tests/__init__.py create mode 100644 tests/test_import.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..28e0e35a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,28 @@ +[tool.poetry] +name = "git-fleximod" +version = "0.3.0" +description = "Extended support for git-submodule and git-sparse-checkout" +authors = ["Jim Edwards "] +maintainers = ["Jim Edwards "] +license = "MIT" +readme = "README.md" +homepage = "https://github.com/jedwards4b/git-fleximod" +keywords = ["git", "submodule", "sparse-checkout"] +packages = [ + { include = "src/git-fleximod" }, + { include = "src/classes/*.py" }, + { include = "License" }, + { include = "tests" }, + +] + +[tool.poetry.dependencies] +python = "^3.8" +GitPython = "^3.1.0" + +[tools.poetry.urls] +"Bug Tracker" = "https://github.com/jedwards4b/git-fleximod/issues" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 66ff24ad..00000000 --- a/setup.cfg +++ /dev/null @@ -1,8 +0,0 @@ -[metadata] -license_file = License -[build_manpages] -manpages = - # solution 1: -- use parser function - man/git-fleximod.1:function=get_parser:pyfile=src/git-fleximod - # solution 2 -- make parser global - #man/git-fleximod.2:pyfile=src/git-fleximod:object=parser diff --git a/setup.py b/setup.py deleted file mode 100644 index 048db759..00000000 --- a/setup.py +++ /dev/null @@ -1,55 +0,0 @@ -import sys -import os - -sys.path.insert(0, os.path.join(os.getenv("CONDA_PREFIX"), "lib", "python3.12")) -sys.path.insert( - 0, os.path.join(os.getenv("CONDA_PREFIX"), "lib", "python3.12", "site-packages") -) - -import setuptools -from setuptools import find_packages, convert_path -from build_manpages import build_manpages, get_build_py_cmd, get_install_cmd - -main_ns = {} -ver_path = convert_path( - os.path.join(os.path.dirname(__file__), "src", "fleximod", "version.py") -) -print(f"ver_path is {ver_path}") -with open(ver_path) as ver_file: - exec(ver_file.read(), main_ns) - -if __name__ == "__main__": - with open("README.md", "r") as fh: - long_description = fh.read() - - setuptools.setup( - name="git-fleximod", # package name - scripts=["src/git-fleximod"], # This is the name of the package - version=main_ns["__version__"], # The initial release version - author="Jim Edwards", # Full name of the author - maintainer="jedwards4b", - license="MIT License", - description="Extended support for git-submodule and git-sparse-checkout", - long_description=long_description, # Long description read from the the readme file - long_description_content_type="text/markdown", - packages=find_packages( - where="src" - ), # List of all python modules to be installed - package_dir={"": "src", "git-fleximod": "src", "fleximod": "src/fleximod"}, - package_data={"": ["src/fleximod/version.txt", "man/git-fleximod.1"]}, - classifiers=[ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - ], # Information to filter the project on PyPi website - python_requires=">=3.6", # Minimum version requirement of the package - # py_modules=['git-fleximod'], # Name of the python package - install_requires=["GitPython"], # Install other dependencies if any - cmdclass={ - "build_manpages": build_manpages, - # Re-define build_py and install commands so the manual pages - # are automatically re-generated and installed - "build_py": get_build_py_cmd(), - "install": get_install_cmd(), - }, - ) diff --git a/src/fleximod/__init__.py b/src/__init__.py similarity index 100% rename from src/fleximod/__init__.py rename to src/__init__.py diff --git a/src/classes/__init__.py b/src/classes/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/fleximod/gitinterface.py b/src/classes/gitinterface.py similarity index 98% rename from src/fleximod/gitinterface.py rename to src/classes/gitinterface.py index 96bb9ac5..35c593a0 100644 --- a/src/fleximod/gitinterface.py +++ b/src/classes/gitinterface.py @@ -1,5 +1,5 @@ import os -from fleximod import utils +from classes import utils class GitInterface: diff --git a/src/fleximod/gitmodules.py b/src/classes/gitmodules.py similarity index 98% rename from src/fleximod/gitmodules.py rename to src/classes/gitmodules.py index 11a24572..dd2e1fc9 100644 --- a/src/fleximod/gitmodules.py +++ b/src/classes/gitmodules.py @@ -1,7 +1,7 @@ import os import shutil from configparser import ConfigParser -from fleximod.lstripreader import LstripReader +from classes.lstripreader import LstripReader class GitModules(ConfigParser): diff --git a/src/fleximod/lstripreader.py b/src/classes/lstripreader.py similarity index 100% rename from src/fleximod/lstripreader.py rename to src/classes/lstripreader.py diff --git a/src/fleximod/utils.py b/src/classes/utils.py similarity index 100% rename from src/fleximod/utils.py rename to src/classes/utils.py diff --git a/src/fleximod/version.py b/src/classes/version.py similarity index 100% rename from src/fleximod/version.py rename to src/classes/version.py diff --git a/src/git-fleximod b/src/git-fleximod index aa587ea3..1c06d0d3 100755 --- a/src/git-fleximod +++ b/src/git-fleximod @@ -6,10 +6,10 @@ import logging import argparse import textwrap from pathlib import Path -from fleximod import utils -from fleximod.gitinterface import GitInterface -from fleximod.gitmodules import GitModules -from fleximod.version import __version__ +from classes import utils +from classes.gitinterface import GitInterface +from classes.gitmodules import GitModules +from classes.version import __version__ from configparser import NoOptionError # logger variable is global @@ -43,7 +43,7 @@ def get_parser(): "action", choices=choices, default="checkout", - help=f"Subcommand of fleximod, choices are {choices[:-1]}", + help=f"Subcommand of git-fleximod, choices are {choices[:-1]}", ) parser.add_argument( @@ -394,6 +394,7 @@ def submodules_update(gitmodules, root_dir, force): if fxtag and fxtag not in tags: git.git_operation("fetch", newremote, "--tags") atag = git.git_operation("describe", "--tags", "--always").rstrip() + if fxtag and fxtag != atag: print(f"{name:>20} updated to {fxtag}") git.git_operation("checkout", fxtag) diff --git a/tbump.toml b/tbump.toml new file mode 100644 index 00000000..0e0a6330 --- /dev/null +++ b/tbump.toml @@ -0,0 +1,43 @@ +# Uncomment this if your project is hosted on GitHub: +github_url = "https://github.com/jedwards4b/git-fleximod/" + +[version] +current = "0.3.0" + +# Example of a semver regexp. +# Make sure this matches current_version before +# using tbump +regex = ''' + (?P\d+) + \. + (?P\d+) + \. + (?P\d+) + ''' + +[git] +message_template = "Bump to {new_version}" +tag_template = "v{new_version}" + +# For each file to patch, add a [[file]] config +# section containing the path of the file, relative to the +# tbump.toml location. +[[file]] +src = "src/classes/version.py" + +[[file]] +src = "pyproject.toml" + +# You can specify a list of commands to +# run after the files have been patched +# and before the git commit is made + +# [[before_commit]] +# name = "check changelog" +# cmd = "grep -q {new_version} Changelog.rst" + +# Or run some commands after the git tag and the branch +# have been pushed: +# [[after_push]] +# name = "publish" +# cmd = "./publish.sh" diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..4d4c66c7 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,3 @@ +import sys, os + +sys.path.append(os.path.join(os.path.dirname(__file__), os.path.pardir, "src")) diff --git a/tests/test_import.py b/tests/test_import.py new file mode 100644 index 00000000..becf7514 --- /dev/null +++ b/tests/test_import.py @@ -0,0 +1,9 @@ +# pylint: disable=unused-import +from classes import utils +from classes.gitinterface import GitInterface +from classes.gitmodules import GitModules +from classes.version import __version__ + + +def test_import(): + print("here") From 4f3bb556e165f9f1087de841c9b408cedb62f4a3 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sat, 3 Feb 2024 13:59:24 -0700 Subject: [PATCH 078/159] more poetry and sphinx --- doc/Makefile | 20 +++++++ doc/conf.py | 26 +++++++++ doc/index.rst | 24 ++++++++ doc/make.bat | 35 ++++++++++++ pyproject.toml | 5 +- src/classes/cli.py | 120 ++++++++++++++++++++++++++++++++++++++++ src/classes/version.py | 1 - src/git-fleximod | 122 +---------------------------------------- tbump.toml | 2 +- 9 files changed, 232 insertions(+), 123 deletions(-) create mode 100644 doc/Makefile create mode 100644 doc/conf.py create mode 100644 doc/index.rst create mode 100644 doc/make.bat create mode 100644 src/classes/cli.py delete mode 100644 src/classes/version.py diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 00000000..d4bb2cbb --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/doc/conf.py b/doc/conf.py new file mode 100644 index 00000000..423099ee --- /dev/null +++ b/doc/conf.py @@ -0,0 +1,26 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = "git-fleximod" +author = "Jim Edwards " +release = "0.4.0" + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = ["sphinx_argparse_cli"] + +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = "alabaster" +html_static_path = ["_static"] diff --git a/doc/index.rst b/doc/index.rst new file mode 100644 index 00000000..fa534d0d --- /dev/null +++ b/doc/index.rst @@ -0,0 +1,24 @@ +.. git-fleximod documentation master file, created by + sphinx-quickstart on Sat Feb 3 12:02:22 2024. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to git-fleximod's documentation! +======================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: +.. module:: sphinxcontrib.autoprogram +.. sphinx_argparse_cli:: + :module: classes.cli + :func: get_parser + :prog: git-fleximod + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/doc/make.bat b/doc/make.bat new file mode 100644 index 00000000..32bb2452 --- /dev/null +++ b/doc/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/pyproject.toml b/pyproject.toml index 28e0e35a..d49cbae7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,10 @@ packages = [ { include = "src/classes/*.py" }, { include = "License" }, { include = "tests" }, - + { include = "doc/*.rst" }, + { include = "doc/Makefile" }, + { include = "doc/make.bat" }, + { include = "doc/conf.py" }, ] [tool.poetry.dependencies] diff --git a/src/classes/cli.py b/src/classes/cli.py new file mode 100644 index 00000000..437d9bee --- /dev/null +++ b/src/classes/cli.py @@ -0,0 +1,120 @@ +from pathlib import Path +import argparse + +__version__ = "0.3.0" + + +def find_root_dir(filename=".git"): + d = Path.cwd() + root = Path(d.root) + while d != root: + attempt = d / filename + if attempt.is_dir(): + return attempt + d = d.parent + return None + + +def get_parser(): + description = """ + %(prog)s manages checking out groups of gitsubmodules with addtional support for Earth System Models + """ + parser = argparse.ArgumentParser( + description=description, formatter_class=argparse.RawDescriptionHelpFormatter + ) + + # + # user options + # + choices = ["update", "checkout", "status", "test"] + parser.add_argument( + "action", + choices=choices, + default="checkout", + help=f"Subcommand of git-fleximod, choices are {choices[:-1]}", + ) + + parser.add_argument( + "components", + nargs="*", + help="Specific component(s) to checkout. By default, " + "all required submodules are checked out.", + ) + + parser.add_argument( + "-C", + "--path", + default=find_root_dir(), + help="Toplevel repository directory. Defaults to top git directory relative to current.", + ) + + parser.add_argument( + "-g", + "--gitmodules", + nargs="?", + default=".gitmodules", + help="The submodule description filename. " "Default: %(default)s.", + ) + + parser.add_argument( + "-x", + "--exclude", + nargs="*", + help="Component(s) listed in the gitmodules file which should be ignored.", + ) + parser.add_argument( + "-f", + "--force", + action="store_true", + default=False, + help="Override cautions and update or checkout over locally modified repository.", + ) + + parser.add_argument( + "-o", + "--optional", + action="store_true", + default=False, + help="By default only the required submodules " + "are checked out. This flag will also checkout the " + "optional submodules relative to the toplevel directory.", + ) + + parser.add_argument( + "-v", + "--verbose", + action="count", + default=0, + help="Output additional information to " + "the screen and log file. This flag can be " + "used up to two times, increasing the " + "verbosity level each time.", + ) + + parser.add_argument( + "-V", + "--version", + action="version", + version=f"%(prog)s {__version__}", + help="Print version and exit.", + ) + + # + # developer options + # + parser.add_argument( + "--backtrace", + action="store_true", + help="DEVELOPER: show exception backtraces as extra " "debugging output", + ) + + parser.add_argument( + "-d", + "--debug", + action="store_true", + default=False, + help="DEVELOPER: output additional debugging " + "information to the screen and log file.", + ) + + return parser diff --git a/src/classes/version.py b/src/classes/version.py deleted file mode 100644 index 493f7415..00000000 --- a/src/classes/version.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "0.3.0" diff --git a/src/git-fleximod b/src/git-fleximod index 1c06d0d3..38551581 100755 --- a/src/git-fleximod +++ b/src/git-fleximod @@ -3,137 +3,19 @@ import sys import os import shutil import logging -import argparse import textwrap -from pathlib import Path from classes import utils +from classes import cli from classes.gitinterface import GitInterface from classes.gitmodules import GitModules -from classes.version import __version__ from configparser import NoOptionError # logger variable is global logger = None -def find_root_dir(filename=".git"): - d = Path.cwd() - root = Path(d.root) - while d != root: - attempt = d / filename - if attempt.is_dir(): - return attempt - d = d.parent - return None - - -def get_parser(): - description = """ - %(prog)s manages checking out groups of gitsubmodules with addtional support for Earth System Models - """ - parser = argparse.ArgumentParser( - description=description, formatter_class=argparse.RawDescriptionHelpFormatter - ) - - # - # user options - # - choices = ["update", "checkout", "status", "test"] - parser.add_argument( - "action", - choices=choices, - default="checkout", - help=f"Subcommand of git-fleximod, choices are {choices[:-1]}", - ) - - parser.add_argument( - "components", - nargs="*", - help="Specific component(s) to checkout. By default, " - "all required submodules are checked out.", - ) - - parser.add_argument( - "-C", - "--path", - default=find_root_dir(), - help="Toplevel repository directory. Defaults to top git directory relative to current.", - ) - - parser.add_argument( - "-g", - "--gitmodules", - nargs="?", - default=".gitmodules", - help="The submodule description filename. " "Default: %(default)s.", - ) - - parser.add_argument( - "-x", - "--exclude", - nargs="*", - help="Component(s) listed in the gitmodules file which should be ignored.", - ) - parser.add_argument( - "-f", - "--force", - action="store_true", - default=False, - help="Override cautions and update or checkout over locally modified repository.", - ) - - parser.add_argument( - "-o", - "--optional", - action="store_true", - default=False, - help="By default only the required submodules " - "are checked out. This flag will also checkout the " - "optional submodules relative to the toplevel directory.", - ) - - parser.add_argument( - "-v", - "--verbose", - action="count", - default=0, - help="Output additional information to " - "the screen and log file. This flag can be " - "used up to two times, increasing the " - "verbosity level each time.", - ) - - parser.add_argument( - "-V", - "--version", - action="version", - version=f"%(prog)s {__version__}", - help="Print version and exit.", - ) - - # - # developer options - # - parser.add_argument( - "--backtrace", - action="store_true", - help="DEVELOPER: show exception backtraces as extra " "debugging output", - ) - - parser.add_argument( - "-d", - "--debug", - action="store_true", - default=False, - help="DEVELOPER: output additional debugging " - "information to the screen and log file.", - ) - - return parser - - def commandline_arguments(args=None): - parser = get_parser() + parser = cli.get_parser() if args: options = parser.parse_args(args) diff --git a/tbump.toml b/tbump.toml index 0e0a6330..35521d04 100644 --- a/tbump.toml +++ b/tbump.toml @@ -23,7 +23,7 @@ tag_template = "v{new_version}" # section containing the path of the file, relative to the # tbump.toml location. [[file]] -src = "src/classes/version.py" +src = "src/classes/cli.py" [[file]] src = "pyproject.toml" From 461ee4be93bc63eb9c735e03a7ce12c04ff3fe84 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sat, 3 Feb 2024 14:02:07 -0700 Subject: [PATCH 079/159] Bump to 0.5.0 --- pyproject.toml | 2 +- src/classes/cli.py | 2 +- tbump.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d49cbae7..8b4246e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "git-fleximod" -version = "0.3.0" +version = "0.5.0" description = "Extended support for git-submodule and git-sparse-checkout" authors = ["Jim Edwards "] maintainers = ["Jim Edwards "] diff --git a/src/classes/cli.py b/src/classes/cli.py index 437d9bee..65f70cdd 100644 --- a/src/classes/cli.py +++ b/src/classes/cli.py @@ -1,7 +1,7 @@ from pathlib import Path import argparse -__version__ = "0.3.0" +__version__ = "0.5.0" def find_root_dir(filename=".git"): diff --git a/tbump.toml b/tbump.toml index 35521d04..18e21a69 100644 --- a/tbump.toml +++ b/tbump.toml @@ -2,7 +2,7 @@ github_url = "https://github.com/jedwards4b/git-fleximod/" [version] -current = "0.3.0" +current = "0.5.0" # Example of a semver regexp. # Make sure this matches current_version before From f0ab8150ba149c42d4690aa8964754bd58225a11 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sun, 4 Feb 2024 09:15:55 -0700 Subject: [PATCH 080/159] reformed to work with poetry --- {src => git_fleximod}/__init__.py | 0 {src/classes => git_fleximod}/cli.py | 1 - src/git-fleximod => git_fleximod/git-fleximod.py | 12 ++++++------ {src/classes => git_fleximod}/gitinterface.py | 3 +-- {src/classes => git_fleximod}/gitmodules.py | 2 +- {src/classes => git_fleximod}/lstripreader.py | 0 {src/classes => git_fleximod}/utils.py | 0 pyproject.toml | 16 ++++++---------- src/classes/__init__.py | 0 9 files changed, 14 insertions(+), 20 deletions(-) rename {src => git_fleximod}/__init__.py (100%) rename {src/classes => git_fleximod}/cli.py (99%) rename src/git-fleximod => git_fleximod/git-fleximod.py (98%) rename {src/classes => git_fleximod}/gitinterface.py (98%) rename {src/classes => git_fleximod}/gitmodules.py (98%) rename {src/classes => git_fleximod}/lstripreader.py (100%) rename {src/classes => git_fleximod}/utils.py (100%) delete mode 100644 src/classes/__init__.py diff --git a/src/__init__.py b/git_fleximod/__init__.py similarity index 100% rename from src/__init__.py rename to git_fleximod/__init__.py diff --git a/src/classes/cli.py b/git_fleximod/cli.py similarity index 99% rename from src/classes/cli.py rename to git_fleximod/cli.py index 65f70cdd..822b3143 100644 --- a/src/classes/cli.py +++ b/git_fleximod/cli.py @@ -3,7 +3,6 @@ __version__ = "0.5.0" - def find_root_dir(filename=".git"): d = Path.cwd() root = Path(d.root) diff --git a/src/git-fleximod b/git_fleximod/git-fleximod.py similarity index 98% rename from src/git-fleximod rename to git_fleximod/git-fleximod.py index 38551581..9e4969d6 100755 --- a/src/git-fleximod +++ b/git_fleximod/git-fleximod.py @@ -4,10 +4,10 @@ import shutil import logging import textwrap -from classes import utils -from classes import cli -from classes.gitinterface import GitInterface -from classes.gitmodules import GitModules +from git_fleximod import utils +from git_fleximod import cli +from git_fleximod.gitinterface import GitInterface +from git_fleximod.gitmodules import GitModules from configparser import NoOptionError # logger variable is global @@ -343,7 +343,7 @@ def submodules_test(gitmodules, root_dir): return testfails + localmods -def _main_func(): +def main(): ( root_dir, file_name, @@ -393,4 +393,4 @@ def _main_func(): if __name__ == "__main__": - sys.exit(_main_func()) + sys.exit(main()) diff --git a/src/classes/gitinterface.py b/git_fleximod/gitinterface.py similarity index 98% rename from src/classes/gitinterface.py rename to git_fleximod/gitinterface.py index 35c593a0..eff155d5 100644 --- a/src/classes/gitinterface.py +++ b/git_fleximod/gitinterface.py @@ -1,6 +1,5 @@ import os -from classes import utils - +from . import utils class GitInterface: def __init__(self, repo_path, logger): diff --git a/src/classes/gitmodules.py b/git_fleximod/gitmodules.py similarity index 98% rename from src/classes/gitmodules.py rename to git_fleximod/gitmodules.py index dd2e1fc9..ae0ebe12 100644 --- a/src/classes/gitmodules.py +++ b/git_fleximod/gitmodules.py @@ -1,7 +1,7 @@ import os import shutil from configparser import ConfigParser -from classes.lstripreader import LstripReader +from .lstripreader import LstripReader class GitModules(ConfigParser): diff --git a/src/classes/lstripreader.py b/git_fleximod/lstripreader.py similarity index 100% rename from src/classes/lstripreader.py rename to git_fleximod/lstripreader.py diff --git a/src/classes/utils.py b/git_fleximod/utils.py similarity index 100% rename from src/classes/utils.py rename to git_fleximod/utils.py diff --git a/pyproject.toml b/pyproject.toml index 8b4246e2..66617893 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,16 +8,12 @@ license = "MIT" readme = "README.md" homepage = "https://github.com/jedwards4b/git-fleximod" keywords = ["git", "submodule", "sparse-checkout"] -packages = [ - { include = "src/git-fleximod" }, - { include = "src/classes/*.py" }, - { include = "License" }, - { include = "tests" }, - { include = "doc/*.rst" }, - { include = "doc/Makefile" }, - { include = "doc/make.bat" }, - { include = "doc/conf.py" }, -] + +[[tool.poetry.packages]] +include = "git_fleximod" + +[tool.poetry.scripts] +git-fleximod = "git_fleximod.git-fleximod:main" [tool.poetry.dependencies] python = "^3.8" diff --git a/src/classes/__init__.py b/src/classes/__init__.py deleted file mode 100644 index e69de29b..00000000 From 7b2a86dcfdcc039034c2ad2e2b69103ac5bf319f Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sun, 4 Feb 2024 09:22:09 -0700 Subject: [PATCH 081/159] add a simple import test --- tests/test_import.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/test_import.py b/tests/test_import.py index becf7514..d5ca878d 100644 --- a/tests/test_import.py +++ b/tests/test_import.py @@ -1,9 +1,8 @@ # pylint: disable=unused-import -from classes import utils -from classes.gitinterface import GitInterface -from classes.gitmodules import GitModules -from classes.version import __version__ - +from git_fleximod import cli +from git_fleximod import utils +from git_fleximod.gitinterface import GitInterface +from git_fleximod.gitmodules import GitModules def test_import(): print("here") From e7fdd39fd8df3b829196f276e21c7945fd71401e Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sun, 4 Feb 2024 10:30:30 -0700 Subject: [PATCH 082/159] now includes man pages --- doc/index.rst | 2 +- pyproject.toml | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/doc/index.rst b/doc/index.rst index fa534d0d..0f9c1a7f 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -11,7 +11,7 @@ Welcome to git-fleximod's documentation! :caption: Contents: .. module:: sphinxcontrib.autoprogram .. sphinx_argparse_cli:: - :module: classes.cli + :module: git_fleximod.cli :func: get_parser :prog: git-fleximod diff --git a/pyproject.toml b/pyproject.toml index 66617893..17843e0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,18 +8,23 @@ license = "MIT" readme = "README.md" homepage = "https://github.com/jedwards4b/git-fleximod" keywords = ["git", "submodule", "sparse-checkout"] - -[[tool.poetry.packages]] -include = "git_fleximod" +packages = [ +{ include = "git_fleximod"}, +{ include = "doc"}, +] [tool.poetry.scripts] git-fleximod = "git_fleximod.git-fleximod:main" +fsspec = "fsspec.fuse:main" [tool.poetry.dependencies] python = "^3.8" GitPython = "^3.1.0" +sphinx = "^5.0.0" +fsspec = "^2023.12.2" +wheel = "^0.42.0" -[tools.poetry.urls] +[tool.poetry.urls] "Bug Tracker" = "https://github.com/jedwards4b/git-fleximod/issues" [build-system] From 26739ada8bf21eda4aefabdf8219ade9e09e473e Mon Sep 17 00:00:00 2001 From: James Edwards Date: Mon, 5 Feb 2024 08:09:44 -0700 Subject: [PATCH 083/159] update tbump path --- tbump.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tbump.toml b/tbump.toml index 18e21a69..c3720e25 100644 --- a/tbump.toml +++ b/tbump.toml @@ -23,7 +23,7 @@ tag_template = "v{new_version}" # section containing the path of the file, relative to the # tbump.toml location. [[file]] -src = "src/classes/cli.py" +src = "git_fleximod/cli.py" [[file]] src = "pyproject.toml" From 25bcc7ea7ac8cf9e02c5dfbcd81f257c6f50b21a Mon Sep 17 00:00:00 2001 From: James Edwards Date: Mon, 5 Feb 2024 08:09:53 -0700 Subject: [PATCH 084/159] Bump to 0.6.0 --- git_fleximod/cli.py | 2 +- pyproject.toml | 2 +- tbump.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/git_fleximod/cli.py b/git_fleximod/cli.py index 822b3143..e1c6d2d5 100644 --- a/git_fleximod/cli.py +++ b/git_fleximod/cli.py @@ -1,7 +1,7 @@ from pathlib import Path import argparse -__version__ = "0.5.0" +__version__ = "0.6.0" def find_root_dir(filename=".git"): d = Path.cwd() diff --git a/pyproject.toml b/pyproject.toml index 17843e0b..eabb7657 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "git-fleximod" -version = "0.5.0" +version = "0.6.0" description = "Extended support for git-submodule and git-sparse-checkout" authors = ["Jim Edwards "] maintainers = ["Jim Edwards "] diff --git a/tbump.toml b/tbump.toml index c3720e25..b41ec17e 100644 --- a/tbump.toml +++ b/tbump.toml @@ -2,7 +2,7 @@ github_url = "https://github.com/jedwards4b/git-fleximod/" [version] -current = "0.5.0" +current = "0.6.0" # Example of a semver regexp. # Make sure this matches current_version before From ed01cb7a0f07e1c79136b50f6448463a45538acb Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Mon, 5 Feb 2024 09:06:54 -0700 Subject: [PATCH 085/159] fix issue with hyphen --- .../{git-fleximod.py => git_fleximod.py} | 0 poetry.lock | 682 ++++++++++++++++++ pyproject.toml | 1 + 3 files changed, 683 insertions(+) rename git_fleximod/{git-fleximod.py => git_fleximod.py} (100%) create mode 100644 poetry.lock diff --git a/git_fleximod/git-fleximod.py b/git_fleximod/git_fleximod.py similarity index 100% rename from git_fleximod/git-fleximod.py rename to git_fleximod/git_fleximod.py diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 00000000..6c31fac4 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,682 @@ +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. + +[[package]] +name = "alabaster" +version = "0.7.13" +description = "A configurable sidebar-enabled Sphinx theme" +optional = false +python-versions = ">=3.6" +files = [ + {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, + {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, +] + +[[package]] +name = "babel" +version = "2.14.0" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"}, + {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"}, +] + +[package.dependencies] +pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} + +[package.extras] +dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] + +[[package]] +name = "certifi" +version = "2024.2.2" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "docutils" +version = "0.19" +description = "Docutils -- Python Documentation Utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc"}, + {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.0" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, + {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "fsspec" +version = "2023.12.2" +description = "File-system specification" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fsspec-2023.12.2-py3-none-any.whl", hash = "sha256:d800d87f72189a745fa3d6b033b9dc4a34ad069f60ca60b943a63599f5501960"}, + {file = "fsspec-2023.12.2.tar.gz", hash = "sha256:8548d39e8810b59c38014934f6b31e57f40c1b20f911f4cc2b85389c7e9bf0cb"}, +] + +[package.extras] +abfs = ["adlfs"] +adl = ["adlfs"] +arrow = ["pyarrow (>=1)"] +dask = ["dask", "distributed"] +devel = ["pytest", "pytest-cov"] +dropbox = ["dropbox", "dropboxdrivefs", "requests"] +full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs", "smbprotocol", "tqdm"] +fuse = ["fusepy"] +gcs = ["gcsfs"] +git = ["pygit2"] +github = ["requests"] +gs = ["gcsfs"] +gui = ["panel"] +hdfs = ["pyarrow (>=1)"] +http = ["aiohttp (!=4.0.0a0,!=4.0.0a1)", "requests"] +libarchive = ["libarchive-c"] +oci = ["ocifs"] +s3 = ["s3fs"] +sftp = ["paramiko"] +smb = ["smbprotocol"] +ssh = ["paramiko"] +tqdm = ["tqdm"] + +[[package]] +name = "gitdb" +version = "4.0.11" +description = "Git Object Database" +optional = false +python-versions = ">=3.7" +files = [ + {file = "gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4"}, + {file = "gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b"}, +] + +[package.dependencies] +smmap = ">=3.0.1,<6" + +[[package]] +name = "gitpython" +version = "3.1.41" +description = "GitPython is a Python library used to interact with Git repositories" +optional = false +python-versions = ">=3.7" +files = [ + {file = "GitPython-3.1.41-py3-none-any.whl", hash = "sha256:c36b6634d069b3f719610175020a9aed919421c87552185b085e04fbbdb10b7c"}, + {file = "GitPython-3.1.41.tar.gz", hash = "sha256:ed66e624884f76df22c8e16066d567aaa5a37d5b5fa19db2c6df6f7156db9048"}, +] + +[package.dependencies] +gitdb = ">=4.0.1,<5" + +[package.extras] +test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "sumtypes"] + +[[package]] +name = "idna" +version = "3.6" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + +[[package]] +name = "importlib-metadata" +version = "7.0.1" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"}, + {file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "jinja2" +version = "3.1.3" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, + {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + +[[package]] +name = "packaging" +version = "23.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, + {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, +] + +[[package]] +name = "pluggy" +version = "1.4.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pygments" +version = "2.17.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, + {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, +] + +[package.extras] +plugins = ["importlib-metadata"] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pytest" +version = "8.0.0" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.0.0-py3-none-any.whl", hash = "sha256:50fb9cbe836c3f20f0dfa99c565201fb75dc54c8d76373cd1bde06b06657bdb6"}, + {file = "pytest-8.0.0.tar.gz", hash = "sha256:249b1b0864530ba251b7438274c4d251c58d868edaaec8762893ad4a0d71c36c"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.3.0,<2.0" +tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytz" +version = "2024.1" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, + {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, +] + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "smmap" +version = "5.0.1" +description = "A pure Python implementation of a sliding window memory map manager" +optional = false +python-versions = ">=3.7" +files = [ + {file = "smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da"}, + {file = "smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62"}, +] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +optional = false +python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + +[[package]] +name = "sphinx" +version = "5.3.0" +description = "Python documentation generator" +optional = false +python-versions = ">=3.6" +files = [ + {file = "Sphinx-5.3.0.tar.gz", hash = "sha256:51026de0a9ff9fc13c05d74913ad66047e104f56a129ff73e174eb5c3ee794b5"}, + {file = "sphinx-5.3.0-py3-none-any.whl", hash = "sha256:060ca5c9f7ba57a08a1219e547b269fadf125ae25b06b9fa7f66768efb652d6d"}, +] + +[package.dependencies] +alabaster = ">=0.7,<0.8" +babel = ">=2.9" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +docutils = ">=0.14,<0.20" +imagesize = ">=1.3" +importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} +Jinja2 = ">=3.0" +packaging = ">=21.0" +Pygments = ">=2.12" +requests = ">=2.5.0" +snowballstemmer = ">=2.0" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = ">=1.1.5" + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-bugbear", "flake8-comprehensions", "flake8-simplify", "isort", "mypy (>=0.981)", "sphinx-lint", "types-requests", "types-typed-ast"] +test = ["cython", "html5lib", "pytest (>=4.6)", "typed_ast"] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "1.0.4" +description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, + {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "1.0.2" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, + {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.0.1" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, + {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "1.0.3" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, + {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "1.1.5" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, + {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "urllib3" +version = "2.2.0" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.0-py3-none-any.whl", hash = "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224"}, + {file = "urllib3-2.2.0.tar.gz", hash = "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "wheel" +version = "0.42.0" +description = "A built-package format for Python" +optional = false +python-versions = ">=3.7" +files = [ + {file = "wheel-0.42.0-py3-none-any.whl", hash = "sha256:177f9c9b0d45c47873b619f5b650346d632cdc35fb5e4d25058e09c9e581433d"}, + {file = "wheel-0.42.0.tar.gz", hash = "sha256:c45be39f7882c9d34243236f2d63cbd58039e360f85d0913425fbd7ceea617a8"}, +] + +[package.extras] +test = ["pytest (>=6.0.0)", "setuptools (>=65)"] + +[[package]] +name = "zipp" +version = "3.17.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, + {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.8" +content-hash = "f20e29bada880bbb0aad20d2a7ac282d09c3e89d4aac06072cb82a9157f4023a" diff --git a/pyproject.toml b/pyproject.toml index eabb7657..c6ccff26 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ GitPython = "^3.1.0" sphinx = "^5.0.0" fsspec = "^2023.12.2" wheel = "^0.42.0" +pytest = "^8.0.0" [tool.poetry.urls] "Bug Tracker" = "https://github.com/jedwards4b/git-fleximod/issues" From 69a5dc5ae30514b2cefe8ee827b6d2a5e4322db9 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Mon, 5 Feb 2024 09:07:10 -0700 Subject: [PATCH 086/159] Bump to 0.6.1 --- git_fleximod/cli.py | 2 +- pyproject.toml | 2 +- tbump.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/git_fleximod/cli.py b/git_fleximod/cli.py index e1c6d2d5..39e83b97 100644 --- a/git_fleximod/cli.py +++ b/git_fleximod/cli.py @@ -1,7 +1,7 @@ from pathlib import Path import argparse -__version__ = "0.6.0" +__version__ = "0.6.1" def find_root_dir(filename=".git"): d = Path.cwd() diff --git a/pyproject.toml b/pyproject.toml index c6ccff26..225f7b39 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "git-fleximod" -version = "0.6.0" +version = "0.6.1" description = "Extended support for git-submodule and git-sparse-checkout" authors = ["Jim Edwards "] maintainers = ["Jim Edwards "] diff --git a/tbump.toml b/tbump.toml index b41ec17e..03b63f15 100644 --- a/tbump.toml +++ b/tbump.toml @@ -2,7 +2,7 @@ github_url = "https://github.com/jedwards4b/git-fleximod/" [version] -current = "0.6.0" +current = "0.6.1" # Example of a semver regexp. # Make sure this matches current_version before From f456ca059ccd6bfacd15dce2b302eafb6c09ce2c Mon Sep 17 00:00:00 2001 From: James Edwards Date: Tue, 6 Feb 2024 11:45:42 -0700 Subject: [PATCH 087/159] fix issue with sparse checkout dir path --- git_fleximod/git_fleximod.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index 9e4969d6..59656eeb 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -65,8 +65,8 @@ def commandline_arguments(args=None): def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master"): # first create the module directory - if not os.path.isdir(path): - os.makedirs(path) + if not os.path.isdir(os.path.join(root_dir,path)): + os.makedirs(os.path.join(root_dir,path)) # Check first if the module is already defined # and the sparse-checkout file exists git = GitInterface(root_dir, logger) From ac3e6ddc7f1fddc3066d81a61aa7a042683315cb Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Tue, 6 Feb 2024 13:12:28 -0700 Subject: [PATCH 088/159] Bump to 0.6.2 --- git_fleximod/cli.py | 2 +- pyproject.toml | 2 +- tbump.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/git_fleximod/cli.py b/git_fleximod/cli.py index 39e83b97..719b1562 100644 --- a/git_fleximod/cli.py +++ b/git_fleximod/cli.py @@ -1,7 +1,7 @@ from pathlib import Path import argparse -__version__ = "0.6.1" +__version__ = "0.6.2" def find_root_dir(filename=".git"): d = Path.cwd() diff --git a/pyproject.toml b/pyproject.toml index 225f7b39..f5828a58 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "git-fleximod" -version = "0.6.1" +version = "0.6.2" description = "Extended support for git-submodule and git-sparse-checkout" authors = ["Jim Edwards "] maintainers = ["Jim Edwards "] diff --git a/tbump.toml b/tbump.toml index 03b63f15..583cef45 100644 --- a/tbump.toml +++ b/tbump.toml @@ -2,7 +2,7 @@ github_url = "https://github.com/jedwards4b/git-fleximod/" [version] -current = "0.6.1" +current = "0.6.2" # Example of a semver regexp. # Make sure this matches current_version before From 96ac371975428ddebf7744bf9304f7496c66a959 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Fri, 9 Feb 2024 14:41:10 -0700 Subject: [PATCH 089/159] add tests --- git_fleximod/gitinterface.py | 11 +++++-- poetry.lock | 13 +++++++- pyproject.toml | 3 +- tests/{test_import.py => test_a_import.py} | 0 tests/test_checkout.py | 32 ++++++++++++++++++ tests/test_sparse_checkout.py | 38 ++++++++++++++++++++++ 6 files changed, 93 insertions(+), 4 deletions(-) rename tests/{test_import.py => test_a_import.py} (100%) create mode 100644 tests/test_checkout.py create mode 100644 tests/test_sparse_checkout.py diff --git a/git_fleximod/gitinterface.py b/git_fleximod/gitinterface.py index eff155d5..4d4c4f4b 100644 --- a/git_fleximod/gitinterface.py +++ b/git_fleximod/gitinterface.py @@ -1,4 +1,5 @@ import os +import sys from . import utils class GitInterface: @@ -26,7 +27,10 @@ def __init__(self, repo_path, logger): def _git_command(self, operation, *args): self.logger.info(operation) if self._use_module and operation != "submodule": - return getattr(self.repo.git, operation)(*args) + try: + return getattr(self.repo.git, operation)(*args) + except Exception as e: + sys.exit(e) else: return ["git", "-C", self.repo_path, operation] + list(args) @@ -42,7 +46,10 @@ def git_operation(self, operation, *args, **kwargs): command = self._git_command(operation, *args) self.logger.info(command) if isinstance(command, list): - return utils.execute_subprocess(command, output_to_caller=True) + try: + return utils.execute_subprocess(command, output_to_caller=True) + except Exception as e: + sys.exit(e) else: return command diff --git a/poetry.lock b/poetry.lock index 6c31fac4..b59ed394 100644 --- a/poetry.lock +++ b/poetry.lock @@ -404,6 +404,17 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "pyfakefs" +version = "5.3.5" +description = "pyfakefs implements a fake file system that mocks the Python file system modules." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyfakefs-5.3.5-py3-none-any.whl", hash = "sha256:751015c1de94e1390128c82b48cdedc3f088bbdbe4bc713c79d02a27f0f61e69"}, + {file = "pyfakefs-5.3.5.tar.gz", hash = "sha256:7cdc500b35a214cb7a614e1940543acc6650e69a94ac76e30f33c9373bd9cf90"}, +] + [[package]] name = "pygments" version = "2.17.2" @@ -679,4 +690,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "f20e29bada880bbb0aad20d2a7ac282d09c3e89d4aac06072cb82a9157f4023a" +content-hash = "25ee2ae1d74abedde3a6637a60d4a3095ea5cf9731960875741bbc2ba84a475d" diff --git a/pyproject.toml b/pyproject.toml index f5828a58..6d0fa421 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ packages = [ ] [tool.poetry.scripts] -git-fleximod = "git_fleximod.git-fleximod:main" +git-fleximod = "git_fleximod.git_fleximod:main" fsspec = "fsspec.fuse:main" [tool.poetry.dependencies] @@ -24,6 +24,7 @@ sphinx = "^5.0.0" fsspec = "^2023.12.2" wheel = "^0.42.0" pytest = "^8.0.0" +pyfakefs = "^5.3.5" [tool.poetry.urls] "Bug Tracker" = "https://github.com/jedwards4b/git-fleximod/issues" diff --git a/tests/test_import.py b/tests/test_a_import.py similarity index 100% rename from tests/test_import.py rename to tests/test_a_import.py diff --git a/tests/test_checkout.py b/tests/test_checkout.py new file mode 100644 index 00000000..79158762 --- /dev/null +++ b/tests/test_checkout.py @@ -0,0 +1,32 @@ +import pytest +from pathlib import Path + +def test_basic_checkout(git_fleximod, test_repo): + # Prepare a simple .gitmodules + + gitmodules_content = """ + [submodule "test_submodule"] + path = modules/test + url = https://github.com/ESMCI/mpi-serial.git + fxtag = MPIserial_2.4.0 + fxurl = https://github.com/ESMCI/mpi-serial.git + fxrequired = T:T + """ + (test_repo / ".gitmodules").write_text(gitmodules_content) + + # Run the command + result = git_fleximod("checkout") + + # Assertions + assert result.returncode == 0 + assert Path(test_repo / "modules/test").exists() # Did the submodule directory get created? + + status = git_fleximod("status") + + assert "test_submodule d82ce7c is out of sync with .gitmodules MPIserial_2.4.0" in status.stdout + + result = git_fleximod("update") + assert result.returncode == 0 + + status = git_fleximod("status") + assert "test_submodule at tag MPIserial_2.4.0" in status.stdout diff --git a/tests/test_sparse_checkout.py b/tests/test_sparse_checkout.py new file mode 100644 index 00000000..0633802c --- /dev/null +++ b/tests/test_sparse_checkout.py @@ -0,0 +1,38 @@ +import pytest +from shutil import rmtree +from pathlib import Path +from git_fleximod.gitinterface import GitInterface + +def test_sparse_checkout(git_fleximod, test_repo_base): + # Prepare a simple .gitmodules + gitmodules_content = (test_repo_base / ".gitmodules").read_text() + """ + [submodule "test_sparse_submodule"] + path = modules/sparse_test + url = https://github.com/ESMCI/mpi-serial.git + fxtag = MPIserial_2.5.0 + fxurl = https://github.com/ESMCI/mpi-serial.git + fxsparse = ../.sparse_file_list + """ + (test_repo_base / ".gitmodules").write_text(gitmodules_content) + + # Add the sparse checkout file + sparse_content = """m4 +""" + (test_repo_base / "modules" / ".sparse_file_list").write_text(sparse_content) + + result = git_fleximod("checkout") + + # Assertions + assert result.returncode == 0 + assert Path(test_repo_base / "modules/sparse_test").exists() # Did the submodule directory get created? + assert Path(test_repo_base / "modules/sparse_test/m4").exists() # Did the submodule sparse directory get created? + assert not Path(test_repo_base / "modules/sparse_test/README").exists() # Did only the submodule sparse directory get created? + status = git_fleximod("status test_sparse_submodule") + + assert "test_sparse_submodule at tag MPIserial_2.5.0" in status.stdout + + result = git_fleximod("update") + assert result.returncode == 0 + + status = git_fleximod("status test_sparse_submodule") + assert "test_sparse_submodule at tag MPIserial_2.5.0" in status.stdout From 8e04947c985c02063c246d20ac5b1ef4ecd18fd4 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Fri, 9 Feb 2024 15:51:58 -0700 Subject: [PATCH 090/159] add pytest to workflow --- .github/workflows/pytest.yaml | 73 +++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 .github/workflows/pytest.yaml diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml new file mode 100644 index 00000000..8d2c2cb6 --- /dev/null +++ b/.github/workflows/pytest.yaml @@ -0,0 +1,73 @@ +# Run this job on pushes to `main`, and for pull requests. If you don't specify +# `branches: [main], then this actions runs _twice_ on pull requests, which is +# annoying. + +on: + push: + branches: [main] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # If you wanted to use multiple Python versions, you'd have specify a matrix in the job and + # reference the matrixe python version here. + - uses: actions/setup-python@v5 + with: + python-version: '3.9' + + # Cache the installation of Poetry itself, e.g. the next step. This prevents the workflow + # from installing Poetry every time, which can be slow. Note the use of the Poetry version + # number in the cache key, and the "-0" suffix: this allows you to invalidate the cache + # manually if/when you want to upgrade Poetry, or if something goes wrong. This could be + # mildly cleaner by using an environment variable, but I don't really care. + - name: cache poetry install + uses: actions/cache@v4 + with: + path: ~/.local + key: poetry-1.7.1 + + # Install Poetry. You could do this manually, or there are several actions that do this. + # `snok/install-poetry` seems to be minimal yet complete, and really just calls out to + # Poetry's default install script, which feels correct. I pin the Poetry version here + # because Poetry does occasionally change APIs between versions and I don't want my + # actions to break if it does. + # + # The key configuration value here is `virtualenvs-in-project: true`: this creates the + # venv as a `.venv` in your testing directory, which allows the next step to easily + # cache it. + - uses: snok/install-poetry@v1 + with: + version: 1.7.1 + virtualenvs-create: true + virtualenvs-in-project: true + + # Cache your dependencies (i.e. all the stuff in your `pyproject.toml`). Note the cache + # key: if you're using multiple Python versions, or multiple OSes, you'd need to include + # them in the cache key. I'm not, so it can be simple and just depend on the poetry.lock. + - name: cache deps + id: cache-deps + uses: actions/cache@v4 + with: + path: .venv + key: pydeps-${{ hashFiles('**/poetry.lock') }} + + # Install dependencies. `--no-root` means "install all dependencies but not the project + # itself", which is what you want to avoid caching _your_ code. The `if` statement + # ensures this only runs on a cache miss. + - run: poetry install --no-interaction --no-root + if: steps.cache-deps.outputs.cache-hit != 'true' + + # Now install _your_ project. This isn't necessary for many types of projects -- particularly + # things like Django apps don't need this. But it's a good idea since it fully-exercises the + # pyproject.toml and makes that if you add things like console-scripts at some point that + # they'll be installed and working. + - run: poetry install --no-interaction + + # And finally run tests. I'm using pytest and all my pytest config is in my `pyproject.toml` + # so this line is super-simple. But it could be as complex as you need. + - run: poetry run pytest + From c1aef37f7b1e0b30399ed20d093a0756e658b25c Mon Sep 17 00:00:00 2001 From: James Edwards Date: Fri, 9 Feb 2024 15:52:42 -0700 Subject: [PATCH 091/159] add pytest to workflow --- .github/workflows/pytest.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 8d2c2cb6..4809a278 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -6,6 +6,7 @@ on: push: branches: [main] pull_request: + branches: [main] jobs: test: From e8b6d3cc382785d787ee5fc482a974ef0c1be2b8 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Fri, 9 Feb 2024 15:55:32 -0700 Subject: [PATCH 092/159] add pytest fixtures --- tests/conftest.py | 51 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 tests/conftest.py diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..1cb059da --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,51 @@ +import pytest +from git_fleximod.gitinterface import GitInterface +import os +import subprocess +import logging +from pathlib import Path + +@pytest.fixture(scope='session') +def logger(): + logging.basicConfig( + level=logging.INFO, format="%(name)s - %(levelname)s - %(message)s", handlers=[logging.StreamHandler()] + ) + logger = logging.getLogger(__name__) + return logger + +@pytest.fixture +def test_repo_base(tmp_path, logger): + test_dir = tmp_path / "test_repo" + test_dir.mkdir() + str_path = str(test_dir) + gitp = GitInterface(str_path, logger) + #subprocess.run(["git", "init"], cwd=test_dir) + assert test_dir.joinpath(".git").is_dir() + return test_dir + +@pytest.fixture(params=["modules/test"]) +def test_repo(request, test_repo_base, logger): + subrepo_path = request.param + gitp = GitInterface(str(test_repo_base), logger) + gitp.git_operation("submodule", "add", "--depth","1","--name","test_submodule", "https://github.com/ESMCI/mpi-serial.git", subrepo_path) + # subprocess.run( + assert test_repo_base.joinpath(".gitmodules").is_file() + return test_repo_base + +@pytest.fixture +def git_fleximod(test_repo): + def _run_fleximod(args, input=None): + cmd = ["git", "fleximod"] + args.split() + result = subprocess.run(cmd, cwd=test_repo, input=input, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + text=True) + return result + return _run_fleximod + + +@pytest.fixture +def deinit_submodule(test_repo, logger): + def _deinit(submodule_path): + gitp = GitInterface(str(test_repo), logger) + gitp.git_operation( "submodule", "deinit", "-f", submodule_path) + yield _deinit From c1edd2c894d6f28903c70caa2ff5c7a6e892da26 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sun, 11 Feb 2024 10:49:54 -0700 Subject: [PATCH 093/159] all working --- README.md | 18 +++++---- git_fleximod/git_fleximod.py | 7 ++-- pyproject.toml | 6 +++ tests/conftest.py | 73 +++++++++++++++++++++++++---------- tests/test_checkout.py | 54 ++++++++++++++------------ tests/test_sparse_checkout.py | 52 ++++++++++++------------- 6 files changed, 127 insertions(+), 83 deletions(-) diff --git a/README.md b/README.md index 7c968e70..259ba8b5 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # git-fleximod -Flexible Submodule Management for Git +Flexible, Enhanced Submodule Management for Git ## Overview -Git-fleximod is a Python-based tool that extends Git's submodule capabilities, offering additional features for managing submodules in a more flexible and efficient way. +Git-fleximod is a Python-based tool that extends Git's submodule and sparse checkout capabilities, offering additional features for managing submodules in a more flexible and efficient way. ## Installation @@ -30,10 +30,10 @@ Git-fleximod is a Python-based tool that extends Git's submodule capabilities, o fxtag: Specify a specific tag or branch to checkout for a submodule. fxrequired: Mark a submodule's checkout behavior, with allowed values: - - T:T: Top-level and required (checked out only when this is the Toplevel module). - - T:F: Top-level and optional (checked out with --optional flag if this is the Toplevel module). - - I:T: Internal and required (always checked out). - - I:F: Internal and optional (checked out with --optional flag). + - ToplevelOnlyRequired: Top-level and required (checked out only when this is the Toplevel module). + - ToplevelOnlyOptional: Top-level and optional (checked out with --optional flag if this is the Toplevel module). + - AlwaysRequired: Always required (always checked out). + - AlwaysOptional: Always optional (checked out with --optional flag). fxsparse: Enable sparse checkout for a submodule, pointing to a file containing sparse checkout paths. fxurl: This is the url used in the test subcommand to assure that protected branches do not point to forks @@ -71,6 +71,7 @@ Example .gitmodules entry: path = src/physics/cosp2/src url = https://github.com/CFMIP/COSPv2.0 fxsparse = ../.cosp_sparse_checkout + fxrequired = AlwaysRequired fxtag = v2.1.4cesm ``` Explanation: @@ -80,14 +81,15 @@ should be checked out into the directory src/physics/cosp2/src relative to the .gitmodules directory. It should be checked out from the URL https://github.com/CFMIP/COSPv2.0 and use sparse checkout as described in the file ../.cosp_sparse_checkout relative to the path -directory. +directory. It should be checked out anytime this .gitmodules entry is +read. Additional example: ```ini, toml [submodule "cime"] path = cime url = https://github.com/jedwards4b/cime - fxrequired = T:T + fxrequired = ToplevelOnlyRequired fxtag = cime6.0.198_rme01 ``` diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index 1b6c357b..df76127b 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -24,9 +24,9 @@ def commandline_arguments(args=None): # explicitly listing a component overrides the optional flag if options.optional or options.components: - fxrequired = ["T:T", "T:F", "I:T"] + fxrequired = ["ToplevelOnlyRequired", "ToplevelOnlyOptional", "AlwaysRequired", "AlwaysOptional"] else: - fxrequired = ["T:T", "I:T"] + fxrequired = ["ToplevelOnlyRequired", "AlwaysRequired"] action = options.action if not action: @@ -66,6 +66,7 @@ def commandline_arguments(args=None): def submodule_sparse_checkout( root_dir, name, url, path, sparsefile, tag="master", fxhash=None ): + logger.info("Called sparse_checkout for {}".format(name)) # first create the module directory if not os.path.isdir(os.path.join(root_dir,path)): os.makedirs(os.path.join(root_dir,path)) @@ -344,7 +345,7 @@ def submodules_checkout(gitmodules, root_dir, requiredlist, force=False): url = gitmodules.get(name, "url") if fxrequired and fxrequired not in requiredlist: - if "T:F" == fxrequired: + if "Optional" in fxrequired: print("Skipping optional component {}".format(name)) continue diff --git a/pyproject.toml b/pyproject.toml index 6d0fa421..772b1542 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,12 @@ pyfakefs = "^5.3.5" [tool.poetry.urls] "Bug Tracker" = "https://github.com/jedwards4b/git-fleximod/issues" +[tool.pytest.ini_options] +markers = [ + "skip_after_first: only run on first iteration" +] + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" + diff --git a/tests/conftest.py b/tests/conftest.py index 1cb059da..d6fec44f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,39 +13,72 @@ def logger(): logger = logging.getLogger(__name__) return logger +@pytest.fixture(params=[ + {"subrepo_path": "modules/test", "submodule_name": "test_submodule", "gitmodules_content" : """ + [submodule "test_submodule"] + path = modules/test + url = https://github.com/ESMCI/mpi-serial.git + fxtag = MPIserial_2.4.0 + fxurl = https://github.com/ESMCI/mpi-serial.git + fxrequired = ToplevelOnlyRequired +"""}, + {"subrepo_path": "modules/test_optional", "submodule_name": "test_optional", "gitmodules_content": """ + [submodule "test_optional"] + path = modules/test_optional + url = https://github.com/ESMCI/mpi-serial.git + fxtag = MPIserial_2.4.0 + fxurl = https://github.com/ESMCI/mpi-serial.git + fxrequired = ToplevelOnlyRequired +"""}, + {"subrepo_path": "modules/test_alwaysoptional", "submodule_name": "test_alwaysoptional", "gitmodules_content": """ + [submodule "test_alwaysoptional"] + path = modules/test_alwaysoptional + url = https://github.com/ESMCI/mpi-serial.git + fxtag = MPIserial_2.3.0 + fxurl = https://github.com/ESMCI/mpi-serial.git + fxrequired = AlwaysOptional +"""}, + {"subrepo_path": "modules/test_sparse", "submodule_name": "test_sparse", "gitmodules_content": """ + [submodule "test_sparse"] + path = modules/test_sparse + url = https://github.com/ESMCI/mpi-serial.git + fxtag = MPIserial_2.5.0 + fxurl = https://github.com/ESMCI/mpi-serial.git + fxrequired = AlwaysRequired + fxsparse = ../.sparse_file_list +"""}, +]) + +def shared_repos(request): + return request.param + +@pytest.fixture +def test_repo(shared_repos, test_repo_base, logger): + subrepo_path = shared_repos["subrepo_path"] + submodule_name = shared_repos["submodule_name"] + + gitp = GitInterface(str(test_repo_base), logger) + gitp.git_operation("submodule", "add", "--depth","1","--name", submodule_name, "https://github.com/ESMCI/mpi-serial.git", subrepo_path) + assert test_repo_base.joinpath(".gitmodules").is_file() + return test_repo_base + @pytest.fixture def test_repo_base(tmp_path, logger): - test_dir = tmp_path / "test_repo" + test_dir = tmp_path / "testrepo" test_dir.mkdir() str_path = str(test_dir) gitp = GitInterface(str_path, logger) - #subprocess.run(["git", "init"], cwd=test_dir) assert test_dir.joinpath(".git").is_dir() + (test_dir / "modules").mkdir() return test_dir -@pytest.fixture(params=["modules/test"]) -def test_repo(request, test_repo_base, logger): - subrepo_path = request.param - gitp = GitInterface(str(test_repo_base), logger) - gitp.git_operation("submodule", "add", "--depth","1","--name","test_submodule", "https://github.com/ESMCI/mpi-serial.git", subrepo_path) - # subprocess.run( - assert test_repo_base.joinpath(".gitmodules").is_file() - return test_repo_base - @pytest.fixture -def git_fleximod(test_repo): +def git_fleximod(test_repo_base): def _run_fleximod(args, input=None): cmd = ["git", "fleximod"] + args.split() - result = subprocess.run(cmd, cwd=test_repo, input=input, + result = subprocess.run(cmd, cwd=test_repo_base, input=input, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) return result return _run_fleximod - -@pytest.fixture -def deinit_submodule(test_repo, logger): - def _deinit(submodule_path): - gitp = GitInterface(str(test_repo), logger) - gitp.git_operation( "submodule", "deinit", "-f", submodule_path) - yield _deinit diff --git a/tests/test_checkout.py b/tests/test_checkout.py index 79158762..6d17ce18 100644 --- a/tests/test_checkout.py +++ b/tests/test_checkout.py @@ -1,32 +1,38 @@ import pytest from pathlib import Path -def test_basic_checkout(git_fleximod, test_repo): - # Prepare a simple .gitmodules - - gitmodules_content = """ - [submodule "test_submodule"] +@pytest.fixture(params=[ + {"subrepo_path": "modules/test", "submodule_name": "test_submodule", "gitmodules_content" : """ [submodule "test_submodule"] path = modules/test url = https://github.com/ESMCI/mpi-serial.git fxtag = MPIserial_2.4.0 fxurl = https://github.com/ESMCI/mpi-serial.git - fxrequired = T:T - """ - (test_repo / ".gitmodules").write_text(gitmodules_content) + fxrequired = ToplevelOnlyRequired +"""}, +]) +def test_config(request): + return request.param + +def test_basic_checkout(git_fleximod, test_repo, test_config): + # Prepare a simple .gitmodules + gm = test_config['gitmodules_content'] + file_path = (test_repo / ".gitmodules") + if not file_path.exists(): + file_path.write_text(gm) - # Run the command - result = git_fleximod("checkout") - - # Assertions - assert result.returncode == 0 - assert Path(test_repo / "modules/test").exists() # Did the submodule directory get created? - - status = git_fleximod("status") - - assert "test_submodule d82ce7c is out of sync with .gitmodules MPIserial_2.4.0" in status.stdout - - result = git_fleximod("update") - assert result.returncode == 0 - - status = git_fleximod("status") - assert "test_submodule at tag MPIserial_2.4.0" in status.stdout + # Run the command + result = git_fleximod("checkout test_submodule") + + # Assertions + assert result.returncode == 0 + assert Path(test_repo / "modules/test").exists() # Did the submodule directory get created? + + status = git_fleximod("status") + + assert "test_submodule d82ce7c is out of sync with .gitmodules MPIserial_2.4.0" in status.stdout + + result = git_fleximod("update") + assert result.returncode == 0 + + status = git_fleximod("status") + assert "test_submodule at tag MPIserial_2.4.0" in status.stdout diff --git a/tests/test_sparse_checkout.py b/tests/test_sparse_checkout.py index 0633802c..7276e18d 100644 --- a/tests/test_sparse_checkout.py +++ b/tests/test_sparse_checkout.py @@ -3,36 +3,32 @@ from pathlib import Path from git_fleximod.gitinterface import GitInterface -def test_sparse_checkout(git_fleximod, test_repo_base): - # Prepare a simple .gitmodules - gitmodules_content = (test_repo_base / ".gitmodules").read_text() + """ - [submodule "test_sparse_submodule"] - path = modules/sparse_test - url = https://github.com/ESMCI/mpi-serial.git - fxtag = MPIserial_2.5.0 - fxurl = https://github.com/ESMCI/mpi-serial.git - fxsparse = ../.sparse_file_list - """ - (test_repo_base / ".gitmodules").write_text(gitmodules_content) +def test_sparse_checkout(shared_repos, git_fleximod, test_repo_base): + repo_name = shared_repos["submodule_name"] + if repo_name == "test_sparse": + gm = shared_repos["gitmodules_content"] + (test_repo_base / ".gitmodules").write_text(gm) - # Add the sparse checkout file - sparse_content = """m4 + # Add the sparse checkout file + sparse_content = """m4 """ - (test_repo_base / "modules" / ".sparse_file_list").write_text(sparse_content) - result = git_fleximod("checkout") - - # Assertions - assert result.returncode == 0 - assert Path(test_repo_base / "modules/sparse_test").exists() # Did the submodule directory get created? - assert Path(test_repo_base / "modules/sparse_test/m4").exists() # Did the submodule sparse directory get created? - assert not Path(test_repo_base / "modules/sparse_test/README").exists() # Did only the submodule sparse directory get created? - status = git_fleximod("status test_sparse_submodule") - - assert "test_sparse_submodule at tag MPIserial_2.5.0" in status.stdout + (test_repo_base / "modules" / ".sparse_file_list").write_text(sparse_content) - result = git_fleximod("update") - assert result.returncode == 0 + result = git_fleximod(f"checkout {repo_name}") + + # Assertions + assert result.returncode == 0 + assert Path(test_repo_base / f"modules/{repo_name}").exists() # Did the submodule directory get created? + assert Path(test_repo_base / f"modules/{repo_name}/m4").exists() # Did the submodule sparse directory get created? + assert not Path(test_repo_base / f"modules/{repo_name}/README").exists() # Did only the submodule sparse directory get created? + status = git_fleximod(f"status {repo_name}") + + assert f"{repo_name} at tag MPIserial_2.5.0" in status.stdout + + result = git_fleximod(f"update {repo_name}") + assert result.returncode == 0 + + status = git_fleximod(f"status {repo_name}") + assert f"{repo_name} at tag MPIserial_2.5.0" in status.stdout - status = git_fleximod("status test_sparse_submodule") - assert "test_sparse_submodule at tag MPIserial_2.5.0" in status.stdout From 03156218c7ddf4387fd5a8b74b8550ddae7d08da Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sun, 11 Feb 2024 11:47:20 -0700 Subject: [PATCH 094/159] clean up and combine tests --- tests/conftest.py | 50 +++++++++++++++++++++++----------- tests/test_checkout.py | 51 +++++++++++++++-------------------- tests/test_sparse_checkout.py | 34 ----------------------- 3 files changed, 57 insertions(+), 78 deletions(-) delete mode 100644 tests/test_sparse_checkout.py diff --git a/tests/conftest.py b/tests/conftest.py index d6fec44f..ffff6895 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,7 +14,11 @@ def logger(): return logger @pytest.fixture(params=[ - {"subrepo_path": "modules/test", "submodule_name": "test_submodule", "gitmodules_content" : """ + {"subrepo_path": "modules/test", + "submodule_name": "test_submodule", + "status1" : "test_submodule d82ce7c is out of sync with .gitmodules MPIserial_2.4.0", + "status2" : "test_submodule at tag MPIserial_2.4.0", + "gitmodules_content" : """ [submodule "test_submodule"] path = modules/test url = https://github.com/ESMCI/mpi-serial.git @@ -22,7 +26,11 @@ def logger(): fxurl = https://github.com/ESMCI/mpi-serial.git fxrequired = ToplevelOnlyRequired """}, - {"subrepo_path": "modules/test_optional", "submodule_name": "test_optional", "gitmodules_content": """ + {"subrepo_path": "modules/test_optional", + "submodule_name": "test_optional", + "status1" : "test_optional d82ce7c is out of sync with .gitmodules MPIserial_2.4.0", + "status2" : "test_optional at tag MPIserial_2.4.0", + "gitmodules_content": """ [submodule "test_optional"] path = modules/test_optional url = https://github.com/ESMCI/mpi-serial.git @@ -30,7 +38,11 @@ def logger(): fxurl = https://github.com/ESMCI/mpi-serial.git fxrequired = ToplevelOnlyRequired """}, - {"subrepo_path": "modules/test_alwaysoptional", "submodule_name": "test_alwaysoptional", "gitmodules_content": """ + {"subrepo_path": "modules/test_alwaysoptional", + "submodule_name": "test_alwaysoptional", + "status1" : "test_alwaysoptional d82ce7c is out of sync with .gitmodules MPIserial_2.3.0", + "status2" : "test_alwaysoptional at tag MPIserial_2.3.0", + "gitmodules_content": """ [submodule "test_alwaysoptional"] path = modules/test_alwaysoptional url = https://github.com/ESMCI/mpi-serial.git @@ -38,7 +50,11 @@ def logger(): fxurl = https://github.com/ESMCI/mpi-serial.git fxrequired = AlwaysOptional """}, - {"subrepo_path": "modules/test_sparse", "submodule_name": "test_sparse", "gitmodules_content": """ + {"subrepo_path": "modules/test_sparse", + "submodule_name": "test_sparse", + "status1" : "test_sparse at tag MPIserial_2.5.0", + "status2" : "test_sparse at tag MPIserial_2.5.0", + "gitmodules_content": """ [submodule "test_sparse"] path = modules/test_sparse url = https://github.com/ESMCI/mpi-serial.git @@ -53,30 +69,34 @@ def shared_repos(request): return request.param @pytest.fixture -def test_repo(shared_repos, test_repo_base, logger): +def test_repo(shared_repos, tmp_path, logger): subrepo_path = shared_repos["subrepo_path"] submodule_name = shared_repos["submodule_name"] - - gitp = GitInterface(str(test_repo_base), logger) - gitp.git_operation("submodule", "add", "--depth","1","--name", submodule_name, "https://github.com/ESMCI/mpi-serial.git", subrepo_path) - assert test_repo_base.joinpath(".gitmodules").is_file() - return test_repo_base - -@pytest.fixture -def test_repo_base(tmp_path, logger): test_dir = tmp_path / "testrepo" test_dir.mkdir() str_path = str(test_dir) gitp = GitInterface(str_path, logger) assert test_dir.joinpath(".git").is_dir() (test_dir / "modules").mkdir() + if "sparse" in submodule_name: + (test_dir / subrepo_path).mkdir() + # Add the sparse checkout file + sparse_content = """m4 +""" + (test_dir / "modules" / ".sparse_file_list").write_text(sparse_content) + else: + gitp = GitInterface(str(test_dir), logger) + gitp.git_operation("submodule", "add", "--depth","1","--name", submodule_name, "https://github.com/ESMCI/mpi-serial.git", subrepo_path) + assert test_dir.joinpath(".gitmodules").is_file() + + return test_dir @pytest.fixture -def git_fleximod(test_repo_base): +def git_fleximod(test_repo): def _run_fleximod(args, input=None): cmd = ["git", "fleximod"] + args.split() - result = subprocess.run(cmd, cwd=test_repo_base, input=input, + result = subprocess.run(cmd, cwd=test_repo, input=input, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) return result diff --git a/tests/test_checkout.py b/tests/test_checkout.py index 6d17ce18..38bc5f9b 100644 --- a/tests/test_checkout.py +++ b/tests/test_checkout.py @@ -1,38 +1,31 @@ import pytest from pathlib import Path - -@pytest.fixture(params=[ - {"subrepo_path": "modules/test", "submodule_name": "test_submodule", "gitmodules_content" : """ [submodule "test_submodule"] - path = modules/test - url = https://github.com/ESMCI/mpi-serial.git - fxtag = MPIserial_2.4.0 - fxurl = https://github.com/ESMCI/mpi-serial.git - fxrequired = ToplevelOnlyRequired -"""}, -]) -def test_config(request): - return request.param -def test_basic_checkout(git_fleximod, test_repo, test_config): +def test_basic_checkout(git_fleximod, test_repo, shared_repos): # Prepare a simple .gitmodules - gm = test_config['gitmodules_content'] + gm = shared_repos['gitmodules_content'] file_path = (test_repo / ".gitmodules") - if not file_path.exists(): - file_path.write_text(gm) + repo_name = shared_repos["submodule_name"] + repo_path = shared_repos["subrepo_path"] + + file_path.write_text(gm) - # Run the command - result = git_fleximod("checkout test_submodule") - - # Assertions - assert result.returncode == 0 - assert Path(test_repo / "modules/test").exists() # Did the submodule directory get created? - - status = git_fleximod("status") + # Run the command + result = git_fleximod(f"checkout {repo_name}") + + # Assertions + assert result.returncode == 0 + assert Path(test_repo / repo_path).exists() # Did the submodule directory get created? + 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(f"status {repo_name}") - assert "test_submodule d82ce7c is out of sync with .gitmodules MPIserial_2.4.0" in status.stdout + assert shared_repos["status1"] in status.stdout - result = git_fleximod("update") - assert result.returncode == 0 + result = git_fleximod(f"update {repo_name}") + assert result.returncode == 0 - status = git_fleximod("status") - assert "test_submodule at tag MPIserial_2.4.0" in status.stdout + status = git_fleximod(f"status {repo_name}") + assert shared_repos["status2"] in status.stdout diff --git a/tests/test_sparse_checkout.py b/tests/test_sparse_checkout.py deleted file mode 100644 index 7276e18d..00000000 --- a/tests/test_sparse_checkout.py +++ /dev/null @@ -1,34 +0,0 @@ -import pytest -from shutil import rmtree -from pathlib import Path -from git_fleximod.gitinterface import GitInterface - -def test_sparse_checkout(shared_repos, git_fleximod, test_repo_base): - repo_name = shared_repos["submodule_name"] - if repo_name == "test_sparse": - gm = shared_repos["gitmodules_content"] - (test_repo_base / ".gitmodules").write_text(gm) - - # Add the sparse checkout file - sparse_content = """m4 -""" - - (test_repo_base / "modules" / ".sparse_file_list").write_text(sparse_content) - - result = git_fleximod(f"checkout {repo_name}") - - # Assertions - assert result.returncode == 0 - assert Path(test_repo_base / f"modules/{repo_name}").exists() # Did the submodule directory get created? - assert Path(test_repo_base / f"modules/{repo_name}/m4").exists() # Did the submodule sparse directory get created? - assert not Path(test_repo_base / f"modules/{repo_name}/README").exists() # Did only the submodule sparse directory get created? - status = git_fleximod(f"status {repo_name}") - - assert f"{repo_name} at tag MPIserial_2.5.0" in status.stdout - - result = git_fleximod(f"update {repo_name}") - assert result.returncode == 0 - - status = git_fleximod(f"status {repo_name}") - assert f"{repo_name} at tag MPIserial_2.5.0" in status.stdout - From 9602eb0b777c3e174027bc9ea121da907879bac6 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sun, 11 Feb 2024 14:43:38 -0700 Subject: [PATCH 095/159] all tests passing --- tests/conftest.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index ffff6895..a6ba8508 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,8 +16,10 @@ def logger(): @pytest.fixture(params=[ {"subrepo_path": "modules/test", "submodule_name": "test_submodule", - "status1" : "test_submodule d82ce7c is out of sync with .gitmodules MPIserial_2.4.0", + "status1" : "test_submodule MPIserial_2.5.0-3-gd82ce7c is out of sync with .gitmodules MPIserial_2.4.0", "status2" : "test_submodule at tag MPIserial_2.4.0", + "status3" : "test_submodule MPIserial_2.5.0-3-gd82ce7c is out of sync with .gitmodules MPIserial_2.4.0", + "status4" : "test_submodule at tag MPIserial_2.4.0", "gitmodules_content" : """ [submodule "test_submodule"] path = modules/test @@ -28,20 +30,24 @@ def logger(): """}, {"subrepo_path": "modules/test_optional", "submodule_name": "test_optional", - "status1" : "test_optional d82ce7c is out of sync with .gitmodules MPIserial_2.4.0", + "status1" : "test_optional MPIserial_2.5.0-3-gd82ce7c is out of sync with .gitmodules MPIserial_2.4.0", "status2" : "test_optional at tag MPIserial_2.4.0", + "status3" : "test_optional not checked out, aligned at tag MPIserial_2.4.0", + "status4" : "test_optional at tag MPIserial_2.4.0", "gitmodules_content": """ [submodule "test_optional"] path = modules/test_optional url = https://github.com/ESMCI/mpi-serial.git fxtag = MPIserial_2.4.0 fxurl = https://github.com/ESMCI/mpi-serial.git - fxrequired = ToplevelOnlyRequired + fxrequired = ToplevelOnlyOptional """}, {"subrepo_path": "modules/test_alwaysoptional", "submodule_name": "test_alwaysoptional", - "status1" : "test_alwaysoptional d82ce7c is out of sync with .gitmodules MPIserial_2.3.0", + "status1" : "test_alwaysoptional MPIserial_2.5.0-3-gd82ce7c is out of sync with .gitmodules MPIserial_2.3.0", "status2" : "test_alwaysoptional at tag MPIserial_2.3.0", + "status3" : "test_alwaysoptional not checked out, aligned at tag MPIserial_2.3.0", + "status4" : "test_alwaysoptional at tag MPIserial_2.3.0", "gitmodules_content": """ [submodule "test_alwaysoptional"] path = modules/test_alwaysoptional @@ -54,6 +60,8 @@ def logger(): "submodule_name": "test_sparse", "status1" : "test_sparse at tag MPIserial_2.5.0", "status2" : "test_sparse at tag MPIserial_2.5.0", + "status3" : "test_sparse at tag MPIserial_2.5.0", + "status4" : "test_sparse at tag MPIserial_2.5.0", "gitmodules_content": """ [submodule "test_sparse"] path = modules/test_sparse @@ -84,13 +92,18 @@ def test_repo(shared_repos, tmp_path, logger): sparse_content = """m4 """ (test_dir / "modules" / ".sparse_file_list").write_text(sparse_content) + gitp.git_operation("add","modules/.sparse_file_list") else: gitp = GitInterface(str(test_dir), logger) gitp.git_operation("submodule", "add", "--depth","1","--name", submodule_name, "https://github.com/ESMCI/mpi-serial.git", subrepo_path) assert test_dir.joinpath(".gitmodules").is_file() + gitp.git_operation("add",subrepo_path) + gitp.git_operation("commit","-a","-m","\"add submod\"") + test_dir2 = tmp_path / "testrepo2" + gitp.git_operation("clone",test_dir,test_dir2) + return test_dir2 - - return test_dir + @pytest.fixture def git_fleximod(test_repo): From b5c4515d0e9d1a48de659f96da99e6a8f3c20ed5 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sun, 11 Feb 2024 15:52:38 -0700 Subject: [PATCH 096/159] thought it was working?? --- tests/test_required.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/test_required.py diff --git a/tests/test_required.py b/tests/test_required.py new file mode 100644 index 00000000..0997db69 --- /dev/null +++ b/tests/test_required.py @@ -0,0 +1,33 @@ +import pytest +from pathlib import Path + +def test_required(git_fleximod, test_repo, shared_repos): + file_path = (test_repo / ".gitmodules") + gm = shared_repos["gitmodules_content"] + repo_name = shared_repos["submodule_name"] + + status = git_fleximod(f"status {repo_name}") + print(f"stdout is {status.stdout}") + if file_path.exists(): + with file_path.open("r") as f: + gitmodules_content = f.read() + # add the entry if it does not exist + if repo_name not in gitmodules_content: + file_path.write_text(gitmodules_content+gm) + # or if it is incomplete + elif gm not in gitmodules_content: + file_path.write_text(gm) + else: + file_path.write_text(gm) + status = git_fleximod(f"status {repo_name}") + result = git_fleximod("checkout") + assert result.returncode == 0 + status = git_fleximod(f"status {repo_name}") + assert shared_repos["status3"] in status.stdout + if "not checked out" in status.stdout: + status = git_fleximod(f"checkout {repo_name}") + assert result.returncode == 0 + status = git_fleximod(f"update {repo_name}") + assert result.returncode == 0 + status = git_fleximod(f"status {repo_name}") + assert shared_repos["status4"] in status.stdout From a3ce6116065af2e8e86bd3d7d6de06bc215a6e58 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Tue, 13 Feb 2024 15:50:55 -0700 Subject: [PATCH 097/159] test_complex is still a work in progress --- tests/conftest.py | 18 +++++++++++++----- tests/test_checkout.py | 2 +- tests/test_complex.py | 35 +++++++++++++++++++++++++++++++++++ tests/test_required.py | 11 ++++------- 4 files changed, 53 insertions(+), 13 deletions(-) create mode 100644 tests/test_complex.py diff --git a/tests/conftest.py b/tests/conftest.py index a6ba8508..9bd6a758 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,7 +13,7 @@ def logger(): logger = logging.getLogger(__name__) return logger -@pytest.fixture(params=[ +all_repos=[ {"subrepo_path": "modules/test", "submodule_name": "test_submodule", "status1" : "test_submodule MPIserial_2.5.0-3-gd82ce7c is out of sync with .gitmodules MPIserial_2.4.0", @@ -71,11 +71,21 @@ def logger(): fxrequired = AlwaysRequired fxsparse = ../.sparse_file_list """}, -]) +] +@pytest.fixture(params=all_repos) def shared_repos(request): return request.param +@pytest.fixture +def get_all_repos(): + return all_repos + +def write_sparse_checkout_file(fp): + sparse_content = """m4 +""" + fp.write_text(sparse_content) + @pytest.fixture def test_repo(shared_repos, tmp_path, logger): subrepo_path = shared_repos["subrepo_path"] @@ -89,9 +99,7 @@ def test_repo(shared_repos, tmp_path, logger): if "sparse" in submodule_name: (test_dir / subrepo_path).mkdir() # Add the sparse checkout file - sparse_content = """m4 -""" - (test_dir / "modules" / ".sparse_file_list").write_text(sparse_content) + write_sparse_checkout_file(test_dir / "modules" / ".sparse_file_list") gitp.git_operation("add","modules/.sparse_file_list") else: gitp = GitInterface(str(test_dir), logger) diff --git a/tests/test_checkout.py b/tests/test_checkout.py index 38bc5f9b..fd8ffc19 100644 --- a/tests/test_checkout.py +++ b/tests/test_checkout.py @@ -14,7 +14,7 @@ def test_basic_checkout(git_fleximod, test_repo, shared_repos): result = git_fleximod(f"checkout {repo_name}") # Assertions - assert result.returncode == 0 + assert result.returncode == 0 assert Path(test_repo / repo_path).exists() # Did the submodule directory get created? if "sparse" in repo_name: assert Path(test_repo / f"{repo_path}/m4").exists() # Did the submodule sparse directory get created? diff --git a/tests/test_complex.py b/tests/test_complex.py new file mode 100644 index 00000000..628a92d8 --- /dev/null +++ b/tests/test_complex.py @@ -0,0 +1,35 @@ +import pytest +from pathlib import Path +from git_fleximod.gitinterface import GitInterface + +def test_complex_checkout(git_fleximod, get_all_repos, test_repo, request, logger): + gitp = None + for repo in get_all_repos: + repo_name = repo["submodule_name"] + gm = repo["gitmodules_content"] + if "shared_repos0" in request.node.name: + if not gitp: + gitp = GitInterface(str(test_repo), logger) + file_path = (test_repo / ".gitmodules") + if file_path.exists(): + with file_path.open("r") as f: + gitmodules_content = f.read() + print(f"content={gitmodules_content}") + print(f"gm={gm}") + # add the entry if it does not exist + if repo_name not in gitmodules_content: + file_path.write_text(gitmodules_content+gm) + # or if it is incomplete + elif gm not in gitmodules_content: + file_path.write_text(gm) + + else: + file_path.write_text(gm) + if "sparse" in repo_name: + print(f"writing sparse_file_list in {test_repo}") + write_sparse_checkout_file(test_repo / "modules" / ".sparse_file_list") + gitp.git_operation("add","modules/.sparse_file_list") + gitp.git_operation("commit","-a","-m","\"add submod\"") + + + assert(False) diff --git a/tests/test_required.py b/tests/test_required.py index 0997db69..b4d99c56 100644 --- a/tests/test_required.py +++ b/tests/test_required.py @@ -5,17 +5,14 @@ def test_required(git_fleximod, test_repo, shared_repos): file_path = (test_repo / ".gitmodules") gm = shared_repos["gitmodules_content"] repo_name = shared_repos["submodule_name"] - - status = git_fleximod(f"status {repo_name}") - print(f"stdout is {status.stdout}") if file_path.exists(): with file_path.open("r") as f: gitmodules_content = f.read() # add the entry if it does not exist - if repo_name not in gitmodules_content: - file_path.write_text(gitmodules_content+gm) - # or if it is incomplete - elif gm not in gitmodules_content: + if repo_name not in gitmodules_content: + file_path.write_text(gitmodules_content+gm) + # or if it is incomplete + elif gm not in gitmodules_content: file_path.write_text(gm) else: file_path.write_text(gm) From 8af16188fe7c531f3e651000505c06751784e988 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Tue, 13 Feb 2024 16:08:33 -0700 Subject: [PATCH 098/159] update should checkout if needed --- git_fleximod/git_fleximod.py | 34 +++++++++++++++++-- ...arse_checkout.py => test_sparse_update.py} | 7 +--- tests/{test_checkout.py => test_update.py} | 7 +--- 3 files changed, 33 insertions(+), 15 deletions(-) rename tests/{test_sparse_checkout.py => test_sparse_update.py} (86%) rename tests/{test_checkout.py => test_update.py} (82%) diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index 1b6c357b..593a8b3b 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -275,7 +275,7 @@ def submodules_status(gitmodules, root_dir): return testfails, localmods -def submodules_update(gitmodules, root_dir, force): +def submodules_update(gitmodules, root_dir, requiredlist, force): _, localmods = submodules_status(gitmodules, root_dir) print("") if localmods and not force: @@ -289,6 +289,34 @@ def submodules_update(gitmodules, root_dir, force): path = gitmodules.get(name, "path") url = gitmodules.get(name, "url") logger.info("name={} path={} url={} fxtag={}".format(name, path, url, fxtag)) + if not os.path.exists(os.path.join(path, ".git")): + fxrequired = gitmodules.get(name, "fxrequired") + fxsparse = gitmodules.get(name, "fxsparse") + + if fxrequired and fxrequired not in requiredlist: + if "T:F" == fxrequired: + print("Skipping optional component {}".format(name)) + continue + + if fxsparse: + logger.debug( + "Callng submodule_sparse_checkout({}, {}, {}, {}, {}, {}".format( + root_dir, name, url, path, fxsparse, fxtag + ) + ) + submodule_sparse_checkout( + root_dir, name, url, path, fxsparse, tag=fxtag, fxhash=fxhash + ) + else: + logger.debug( + "Calling submodule_checkout({},{},{})".format(root_dir, name, path) + ) + + single_submodule_checkout( + root_dir, name, path, url=url, tag=fxtag, force=force, fxhash=fxhash + ) + + if os.path.exists(os.path.join(path, ".git")): submoddir = os.path.join(root_dir, path) with utils.pushd(submoddir): @@ -326,7 +354,7 @@ def submodules_update(gitmodules, root_dir, force): else: print(f"{name:>20} up to date.") - +# checkout is done by update if required so this function may be depricated def submodules_checkout(gitmodules, root_dir, requiredlist, force=False): _, localmods = submodules_status(gitmodules, root_dir) print("") @@ -424,7 +452,7 @@ def main(): sys.exit("No submodule components found") retval = 0 if action == "update": - submodules_update(gitmodules, root_dir, force) + submodules_update(gitmodules, root_dir, fxrequired, force) elif action == "checkout": submodules_checkout(gitmodules, root_dir, fxrequired, force) elif action == "status": diff --git a/tests/test_sparse_checkout.py b/tests/test_sparse_update.py similarity index 86% rename from tests/test_sparse_checkout.py rename to tests/test_sparse_update.py index 0633802c..1e6ee83b 100644 --- a/tests/test_sparse_checkout.py +++ b/tests/test_sparse_update.py @@ -20,7 +20,7 @@ def test_sparse_checkout(git_fleximod, test_repo_base): """ (test_repo_base / "modules" / ".sparse_file_list").write_text(sparse_content) - result = git_fleximod("checkout") + result = git_fleximod("update") # Assertions assert result.returncode == 0 @@ -31,8 +31,3 @@ def test_sparse_checkout(git_fleximod, test_repo_base): assert "test_sparse_submodule at tag MPIserial_2.5.0" in status.stdout - result = git_fleximod("update") - assert result.returncode == 0 - - status = git_fleximod("status test_sparse_submodule") - assert "test_sparse_submodule at tag MPIserial_2.5.0" in status.stdout diff --git a/tests/test_checkout.py b/tests/test_update.py similarity index 82% rename from tests/test_checkout.py rename to tests/test_update.py index 79158762..baaea760 100644 --- a/tests/test_checkout.py +++ b/tests/test_update.py @@ -15,7 +15,7 @@ def test_basic_checkout(git_fleximod, test_repo): (test_repo / ".gitmodules").write_text(gitmodules_content) # Run the command - result = git_fleximod("checkout") + result = git_fleximod("update") # Assertions assert result.returncode == 0 @@ -23,10 +23,5 @@ def test_basic_checkout(git_fleximod, test_repo): status = git_fleximod("status") - assert "test_submodule d82ce7c is out of sync with .gitmodules MPIserial_2.4.0" in status.stdout - - result = git_fleximod("update") - assert result.returncode == 0 - status = git_fleximod("status") assert "test_submodule at tag MPIserial_2.4.0" in status.stdout From 7c47c05ffbd4d329e94ada655c9a467e8f968585 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Wed, 14 Feb 2024 09:18:43 -0700 Subject: [PATCH 099/159] restructure of tests working --- tests/conftest.py | 20 +++++++++++++++----- tests/test_b_update.py | 4 ++-- tests/test_c_required.py | 12 ++++-------- tests/test_d_complex.py | 34 +++------------------------------- 4 files changed, 24 insertions(+), 46 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 9bd6a758..fd3678f9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,7 +18,7 @@ def logger(): "submodule_name": "test_submodule", "status1" : "test_submodule MPIserial_2.5.0-3-gd82ce7c is out of sync with .gitmodules MPIserial_2.4.0", "status2" : "test_submodule at tag MPIserial_2.4.0", - "status3" : "test_submodule MPIserial_2.5.0-3-gd82ce7c is out of sync with .gitmodules MPIserial_2.4.0", + "status3" : "test_submodule at tag MPIserial_2.4.0", "status4" : "test_submodule at tag MPIserial_2.4.0", "gitmodules_content" : """ [submodule "test_submodule"] @@ -84,6 +84,7 @@ def get_all_repos(): def write_sparse_checkout_file(fp): sparse_content = """m4 """ + print(f"writing sparse_file_list \n") fp.write_text(sparse_content) @pytest.fixture @@ -110,14 +111,23 @@ def test_repo(shared_repos, tmp_path, logger): test_dir2 = tmp_path / "testrepo2" gitp.git_operation("clone",test_dir,test_dir2) return test_dir2 - + +@pytest.fixture +def complex_repo(tmp_path, logger): + test_dir = tmp_path / "testcomplex" + test_dir.mkdir() + str_path = str(test_dir) + gitp = GitInterface(str_path, logger) + gitp.git_operation("remote", "add", "origin", "https://github.com/jedwards4b/fleximod-test") + gitp.git_operation("fetch", "origin", "main") + return test_dir @pytest.fixture -def git_fleximod(test_repo): - def _run_fleximod(args, input=None): +def git_fleximod(): + def _run_fleximod(path, args, input=None): cmd = ["git", "fleximod"] + args.split() - result = subprocess.run(cmd, cwd=test_repo, input=input, + result = subprocess.run(cmd, cwd=path, input=input, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) return result diff --git a/tests/test_b_update.py b/tests/test_b_update.py index e03daa39..159f1cfa 100644 --- a/tests/test_b_update.py +++ b/tests/test_b_update.py @@ -11,7 +11,7 @@ def test_basic_checkout(git_fleximod, test_repo, shared_repos): file_path.write_text(gm) # Run the command - result = git_fleximod(f"update {repo_name}") + result = git_fleximod(test_repo, f"update {repo_name}") # Assertions assert result.returncode == 0 @@ -20,7 +20,7 @@ def test_basic_checkout(git_fleximod, test_repo, shared_repos): 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(f"status {repo_name}") + status = git_fleximod(test_repo, f"status {repo_name}") assert shared_repos["status2"] in status.stdout diff --git a/tests/test_c_required.py b/tests/test_c_required.py index b4d99c56..d6cc040b 100644 --- a/tests/test_c_required.py +++ b/tests/test_c_required.py @@ -16,15 +16,11 @@ def test_required(git_fleximod, test_repo, shared_repos): file_path.write_text(gm) else: file_path.write_text(gm) - status = git_fleximod(f"status {repo_name}") - result = git_fleximod("checkout") + result = git_fleximod(test_repo, "update") assert result.returncode == 0 - status = git_fleximod(f"status {repo_name}") + status = git_fleximod(test_repo, f"status {repo_name}") assert shared_repos["status3"] in status.stdout - if "not checked out" in status.stdout: - status = git_fleximod(f"checkout {repo_name}") - assert result.returncode == 0 - status = git_fleximod(f"update {repo_name}") + status = git_fleximod(test_repo, f"update {repo_name}") assert result.returncode == 0 - status = git_fleximod(f"status {repo_name}") + status = git_fleximod(test_repo, f"status {repo_name}") assert shared_repos["status4"] in status.stdout diff --git a/tests/test_d_complex.py b/tests/test_d_complex.py index 628a92d8..2983b019 100644 --- a/tests/test_d_complex.py +++ b/tests/test_d_complex.py @@ -2,34 +2,6 @@ from pathlib import Path from git_fleximod.gitinterface import GitInterface -def test_complex_checkout(git_fleximod, get_all_repos, test_repo, request, logger): - gitp = None - for repo in get_all_repos: - repo_name = repo["submodule_name"] - gm = repo["gitmodules_content"] - if "shared_repos0" in request.node.name: - if not gitp: - gitp = GitInterface(str(test_repo), logger) - file_path = (test_repo / ".gitmodules") - if file_path.exists(): - with file_path.open("r") as f: - gitmodules_content = f.read() - print(f"content={gitmodules_content}") - print(f"gm={gm}") - # add the entry if it does not exist - if repo_name not in gitmodules_content: - file_path.write_text(gitmodules_content+gm) - # or if it is incomplete - elif gm not in gitmodules_content: - file_path.write_text(gm) - - else: - file_path.write_text(gm) - if "sparse" in repo_name: - print(f"writing sparse_file_list in {test_repo}") - write_sparse_checkout_file(test_repo / "modules" / ".sparse_file_list") - gitp.git_operation("add","modules/.sparse_file_list") - gitp.git_operation("commit","-a","-m","\"add submod\"") - - - assert(False) +def test_complex_checkout(git_fleximod, complex_repo, logger): + status = git_fleximod(complex_repo, "status") + print(status) From e6d4838ebeee5a7ce408aa098d9cfab0cd34d405 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Fri, 16 Feb 2024 17:40:26 -0700 Subject: [PATCH 100/159] all tests are passing and complete --- README.md | 6 +- git_fleximod/git_fleximod.py | 167 +++++++++++++++++++---------------- tests/conftest.py | 13 +-- tests/test_c_required.py | 4 + tests/test_d_complex.py | 62 ++++++++++++- 5 files changed, 167 insertions(+), 85 deletions(-) diff --git a/README.md b/README.md index 259ba8b5..94d85c41 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,8 @@ Git-fleximod is a Python-based tool that extends Git's submodule and sparse chec fxtag: Specify a specific tag or branch to checkout for a submodule. fxrequired: Mark a submodule's checkout behavior, with allowed values: - - ToplevelOnlyRequired: Top-level and required (checked out only when this is the Toplevel module). - - ToplevelOnlyOptional: Top-level and optional (checked out with --optional flag if this is the Toplevel module). + - ToplevelRequired: Top-level and required (checked out only when this is the Toplevel module). + - ToplevelOptional: Top-level and optional (checked out with --optional flag if this is the Toplevel module). - AlwaysRequired: Always required (always checked out). - AlwaysOptional: Always optional (checked out with --optional flag). fxsparse: Enable sparse checkout for a submodule, pointing to a file containing sparse checkout paths. @@ -89,7 +89,7 @@ Additional example: [submodule "cime"] path = cime url = https://github.com/jedwards4b/cime - fxrequired = ToplevelOnlyRequired + fxrequired = ToplevelRequired fxtag = cime6.0.198_rme01 ``` diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index f81e7138..08f31fa7 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -12,7 +12,8 @@ # logger variable is global logger = None - +def fxrequired_allowed_values(): + return ['ToplevelRequired', 'ToplevelOptional', 'AlwaysRequired', 'AlwaysOptional'] def commandline_arguments(args=None): parser = cli.get_parser() @@ -24,9 +25,9 @@ def commandline_arguments(args=None): # explicitly listing a component overrides the optional flag if options.optional or options.components: - fxrequired = ["ToplevelOnlyRequired", "ToplevelOnlyOptional", "AlwaysRequired", "AlwaysOptional"] + fxrequired = ["ToplevelRequired", "ToplevelOptional", "AlwaysRequired", "AlwaysOptional"] else: - fxrequired = ["ToplevelOnlyRequired", "AlwaysRequired"] + fxrequired = ["ToplevelRequired", "AlwaysRequired"] action = options.action if not action: @@ -67,12 +68,16 @@ def submodule_sparse_checkout( root_dir, name, url, path, sparsefile, tag="master", fxhash=None ): logger.info("Called sparse_checkout for {}".format(name)) + rgit = GitInterface(root_dir, logger) + superroot = rgit.git_operation("rev-parse", "--show-superproject-working-tree") + if superroot: + gitroot = superroot + else: + gitroot = root_dir + assert(os.path.isdir(os.path.join(gitroot,".git"))) # first create the module directory if not os.path.isdir(os.path.join(root_dir,path)): os.makedirs(os.path.join(root_dir,path)) - # Check first if the module is already defined - # and the sparse-checkout file exists - git = GitInterface(root_dir, logger) # initialize a new git repo and set the sparse checkout flag sprep_repo = os.path.join(root_dir, path) @@ -92,36 +97,39 @@ def submodule_sparse_checkout( sprepo_git.config_set_value("core", "sparseCheckout", "true") # set the repository remote - sprepo_git.git_operation("remote", "add", "origin", url) - superroot = git.git_operation("rev-parse", "--show-superproject-working-tree") - if os.path.isfile(os.path.join(root_dir, ".git")): + logger.info("Setting remote origin in {}/{}".format(root_dir,path)) + status = sprepo_git.git_operation("remote", "-v") + if url not in status: + sprepo_git.git_operation("remote", "add", "origin", url) + + topgit = os.path.join(gitroot,".git") + + if gitroot != root_dir and os.path.isfile(os.path.join(root_dir, ".git")): with open(os.path.join(root_dir, ".git")) as f: - gitpath = os.path.abspath(os.path.join(root_dir, f.read().split()[1])) - topgit = os.path.abspath(os.path.join(gitpath, "modules")) + gitpath = os.path.relpath(os.path.join(root_dir, f.read().split()[1]), start=os.path.join(root_dir,path)) + topgit = os.path.join(gitpath, "modules") else: - topgit = os.path.abspath(os.path.join(root_dir, ".git", "modules")) - - if not os.path.isdir(topgit): - os.makedirs(topgit) - topgit = os.path.join(topgit, name) - logger.debug( - "root_dir is {} topgit is {} superroot is {}".format( - root_dir, topgit, superroot - ) - ) - + topgit = os.path.relpath(os.path.join(root_dir, ".git", "modules"), start=os.path.join(root_dir,path)) + + with utils.pushd(sprep_repo): + if not os.path.isdir(topgit): + os.makedirs(topgit) + topgit += os.sep + name + if os.path.isdir(os.path.join(root_dir, path, ".git")): - shutil.move(os.path.join(root_dir, path, ".git"), topgit) - with open(os.path.join(root_dir, path, ".git"), "w") as f: - f.write("gitdir: " + os.path.relpath(topgit, os.path.join(root_dir, path))) - - gitsparse = os.path.abspath(os.path.join(topgit, "info", "sparse-checkout")) - if os.path.isfile(gitsparse): - logger.warning("submodule {} is already initialized".format(name)) - return - - shutil.copy(os.path.join(root_dir, path, sparsefile), gitsparse) + with utils.pushd(sprep_repo): + shutil.move(".git", topgit) + with open(".git", "w") as f: + f.write("gitdir: " + os.path.relpath(topgit)) + # assert(os.path.isdir(os.path.relpath(topgit, start=sprep_repo))) + gitsparse = os.path.abspath(os.path.join(topgit, "info", "sparse-checkout")) + if os.path.isfile(gitsparse): + logger.warning("submodule {} is already initialized {}".format(name, topgit)) + return + + with utils.pushd(sprep_repo): + shutil.copy(sparsefile, gitsparse) # Finally checkout the repo if fxhash: @@ -132,28 +140,31 @@ def submodule_sparse_checkout( sprepo_git.git_operation("fetch", "--depth=1", "origin", "--tags") sprepo_git.git_operation("checkout", tag) print(f"Successfully checked out {name:>20} at {tag}") - + rgit.config_set_value(f'submodule "{name}"',"active","true") + rgit.config_set_value(f'submodule "{name}"',"url",url) def single_submodule_checkout( - root, name, path, url=None, tag=None, force=False, fxhash=None + root, name, path, url=None, tag=None, force=False, fxhash=None, optional=False ): git = GitInterface(root, logger) repodir = os.path.join(root, path) - if os.path.exists(os.path.join(repodir, ".git")): - logger.info("Submodule {} already checked out".format(name)) - return + logger.info("Checkout {} into {}/{}".format(name,root,path)) # if url is provided update to the new url tmpurl = None + repo_exists = False + if os.path.exists(os.path.join(repodir, ".git")): + logger.info("Submodule {} already checked out".format(name)) + repo_exists = True # Look for a .gitmodules file in the newly checkedout repo - if url: + if not repo_exists and url: # ssh urls cause problems for those who dont have git accounts with ssh keys defined # but cime has one since e3sm prefers ssh to https, because the .gitmodules file was # opened with a GitModules object we don't need to worry about restoring the file here # it will be done by the GitModules class if url.startswith("git@"): tmpurl = url - url = url.replace("git@github.com:", "https://github.com") + url = url.replace("git@github.com:", "https://github.com/") git.git_operation("clone", url, path) smgit = GitInterface(repodir, logger) if not tag and not fxhash: @@ -170,26 +181,26 @@ def single_submodule_checkout( if line.startswith("gitdir: "): rootdotgit = line[8:].rstrip() - newpath = os.path.abspath(os.path.join(root, rootdotgit, "modules", path)) - if not os.path.isdir(os.path.join(newpath, os.pardir)): - os.makedirs(os.path.abspath(os.path.join(newpath, os.pardir))) - + newpath = os.path.abspath(os.path.join(root, rootdotgit, "modules", name)) shutil.move(os.path.join(repodir, ".git"), newpath) with open(os.path.join(repodir, ".git"), "w") as f: - f.write("gitdir: " + newpath) + f.write("gitdir: " + os.path.relpath(newpath, start=repodir)) - if not tmpurl: + if not repo_exists or not tmpurl: logger.debug(git.git_operation("submodule", "update", "--init", "--", path)) if os.path.exists(os.path.join(repodir, ".gitmodules")): # recursively handle this checkout print(f"Recursively checking out submodules of {name} {repodir} {url}") gitmodules = GitModules(logger, confpath=repodir) - submodules_checkout(gitmodules, repodir, ["I:T"], force=force) + requiredlist = ["AlwaysRequired"] + if optional: + requiredlist.append("AlwaysOptional") + submodules_checkout(gitmodules, repodir, requiredlist, force=force) if os.path.exists(os.path.join(repodir, ".git")): - print(f"Successfully checked out {name}") + print(f"Successfully checked out {name} {repodir}") else: - utils.fatal_error(f"Failed to checkout {name}") + utils.fatal_error(f"Failed to checkout {name} {repo_exists} {tmpurl} {repodir} {path}") if tmpurl: print(git.git_operation("restore", ".gitmodules")) @@ -289,33 +300,37 @@ def submodules_update(gitmodules, root_dir, requiredlist, force): fxhash = gitmodules.get(name, "fxhash") path = gitmodules.get(name, "path") url = gitmodules.get(name, "url") - logger.info("name={} path={} url={} fxtag={}".format(name, path, url, fxtag)) - if not os.path.exists(os.path.join(path, ".git")): - fxrequired = gitmodules.get(name, "fxrequired") - fxsparse = gitmodules.get(name, "fxsparse") - - if fxrequired and fxrequired not in requiredlist: - if "T:F" == fxrequired: - print("Skipping optional component {}".format(name)) - continue - - if fxsparse: - logger.debug( - "Callng submodule_sparse_checkout({}, {}, {}, {}, {}, {}".format( - root_dir, name, url, path, fxsparse, fxtag - ) - ) - submodule_sparse_checkout( - root_dir, name, url, path, fxsparse, tag=fxtag, fxhash=fxhash - ) - else: - logger.debug( - "Calling submodule_checkout({},{},{})".format(root_dir, name, path) + logger.info("name={} path={} url={} fxtag={} requiredlist={}".format(name,os.path.join(root_dir, path), url, fxtag, requiredlist)) +# if not os.path.exists(os.path.join(root_dir,path, ".git")): + fxrequired = gitmodules.get(name, "fxrequired") + assert(fxrequired in fxrequired_allowed_values()) + rgit = GitInterface(root_dir, logger) + superroot = rgit.git_operation("rev-parse", "--show-superproject-working-tree") + + fxsparse = gitmodules.get(name, "fxsparse") + + if fxrequired and (superroot and "Toplevel" in fxrequired) or fxrequired not in requiredlist: + if "ToplevelOptional" == fxrequired: + print("Skipping optional component {}".format(name)) + continue + if fxsparse: + logger.debug( + "Callng submodule_sparse_checkout({}, {}, {}, {}, {}, {}".format( + root_dir, name, url, path, fxsparse, fxtag ) + ) + submodule_sparse_checkout( + root_dir, name, url, path, fxsparse, tag=fxtag, fxhash=fxhash + ) + else: + logger.info( + "Calling submodule_checkout({},{},{},{})".format(root_dir, name, path,url) + ) - single_submodule_checkout( - root_dir, name, path, url=url, tag=fxtag, force=force, fxhash=fxhash - ) + single_submodule_checkout( + root_dir, name, path, url=url, tag=fxtag, force=force, + fxhash=fxhash, optional=("AlwaysOptional" in requiredlist) + ) if os.path.exists(os.path.join(path, ".git")): @@ -371,7 +386,6 @@ def submodules_checkout(gitmodules, root_dir, requiredlist, force=False): fxhash = gitmodules.get(name, "fxhash") path = gitmodules.get(name, "path") url = gitmodules.get(name, "url") - if fxrequired and fxrequired not in requiredlist: if "Optional" in fxrequired: print("Skipping optional component {}".format(name)) @@ -392,7 +406,8 @@ def submodules_checkout(gitmodules, root_dir, requiredlist, force=False): ) single_submodule_checkout( - root_dir, name, path, url=url, tag=fxtag, force=force, fxhash=fxhash + root_dir, name, path, url=url, tag=fxtag, force=force, fxhash=fxhash, + optional = "AlwaysOptional" in requiredlist ) @@ -441,7 +456,7 @@ def main(): ) root_dir = os.path.dirname(file_path) - logger.info("root_dir is {}".format(root_dir)) + logger.info("root_dir is {} includelist={} excludelist={}".format(root_dir, includelist, excludelist)) gitmodules = GitModules( logger, confpath=root_dir, diff --git a/tests/conftest.py b/tests/conftest.py index fd3678f9..b88fa61d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -26,7 +26,7 @@ def logger(): url = https://github.com/ESMCI/mpi-serial.git fxtag = MPIserial_2.4.0 fxurl = https://github.com/ESMCI/mpi-serial.git - fxrequired = ToplevelOnlyRequired + fxrequired = ToplevelRequired """}, {"subrepo_path": "modules/test_optional", "submodule_name": "test_optional", @@ -35,12 +35,12 @@ def logger(): "status3" : "test_optional not checked out, aligned at tag MPIserial_2.4.0", "status4" : "test_optional at tag MPIserial_2.4.0", "gitmodules_content": """ - [submodule "test_optional"] + [submodule "test_optional"] path = modules/test_optional url = https://github.com/ESMCI/mpi-serial.git fxtag = MPIserial_2.4.0 fxurl = https://github.com/ESMCI/mpi-serial.git - fxrequired = ToplevelOnlyOptional + fxrequired = ToplevelOptional """}, {"subrepo_path": "modules/test_alwaysoptional", "submodule_name": "test_alwaysoptional", @@ -84,7 +84,6 @@ def get_all_repos(): def write_sparse_checkout_file(fp): sparse_content = """m4 """ - print(f"writing sparse_file_list \n") fp.write_text(sparse_content) @pytest.fixture @@ -119,8 +118,9 @@ def complex_repo(tmp_path, logger): test_dir.mkdir() str_path = str(test_dir) gitp = GitInterface(str_path, logger) - gitp.git_operation("remote", "add", "origin", "https://github.com/jedwards4b/fleximod-test") + gitp.git_operation("remote", "add", "origin", "https://github.com/jedwards4b/fleximod-test2") gitp.git_operation("fetch", "origin", "main") + gitp.git_operation("checkout", "main") return test_dir @pytest.fixture @@ -130,6 +130,9 @@ def _run_fleximod(path, args, input=None): result = subprocess.run(cmd, cwd=path, input=input, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + if result.returncode: + print(result.stdout) + print(result.stderr) return result return _run_fleximod diff --git a/tests/test_c_required.py b/tests/test_c_required.py index d6cc040b..89ab8d29 100644 --- a/tests/test_c_required.py +++ b/tests/test_c_required.py @@ -20,6 +20,10 @@ def test_required(git_fleximod, test_repo, shared_repos): assert result.returncode == 0 status = git_fleximod(test_repo, f"status {repo_name}") assert shared_repos["status3"] in 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 status = git_fleximod(test_repo, f"update {repo_name}") assert result.returncode == 0 status = git_fleximod(test_repo, f"status {repo_name}") diff --git a/tests/test_d_complex.py b/tests/test_d_complex.py index 2983b019..fdce5162 100644 --- a/tests/test_d_complex.py +++ b/tests/test_d_complex.py @@ -4,4 +4,64 @@ def test_complex_checkout(git_fleximod, complex_repo, logger): status = git_fleximod(complex_repo, "status") - print(status) + assert("ToplevelOptional not checked out, aligned at tag v5.3.2" in status.stdout) + assert("ToplevelRequired not checked out, aligned at tag MPIserial_2.5.0" in status.stdout) + assert("AlwaysRequired not checked out, aligned at tag MPIserial_2.4.0" in status.stdout) + assert("Complex not checked out, aligned at tag testtag01" in status.stdout) + assert("AlwaysOptional not checked out, aligned at tag MPIserial_2.3.0" in status.stdout) + + # This should checkout and update test_submodule and complex_sub + result = git_fleximod(complex_repo, "update") + assert result.returncode == 0 + + status = git_fleximod(complex_repo, "status") + assert("ToplevelOptional not checked out, aligned at tag v5.3.2" in status.stdout) + assert("ToplevelRequired at tag MPIserial_2.5.0" in status.stdout) + assert("AlwaysRequired at tag MPIserial_2.4.0" in status.stdout) + assert("Complex at tag testtag01" in status.stdout) + + # now check the complex_sub + root = (complex_repo / "modules" / "complex") + assert(not (root / "libraries" / "gptl" / ".git").exists()) + assert(not (root / "libraries" / "mpi-serial" / ".git").exists()) + assert((root / "modules" / "mpi-serial" / ".git").exists()) + assert(not (root / "modules" / "mpi-serial2" / ".git").exists()) + assert((root / "modules" / "mpi-sparse" / ".git").exists()) + assert((root / "modules" / "mpi-sparse" / "m4").exists()) + assert(not (root / "modules" / "mpi-sparse" / "README").exists()) + + # update a single optional submodule + + result = git_fleximod(complex_repo, "update ToplevelOptional") + assert result.returncode == 0 + + status = git_fleximod(complex_repo, "status") + assert("ToplevelOptional at tag v5.3.2" in status.stdout) + assert("ToplevelRequired at tag MPIserial_2.5.0" in status.stdout) + assert("AlwaysRequired at tag MPIserial_2.4.0" in status.stdout) + assert("Complex at tag testtag01" in status.stdout) + assert("AlwaysOptional not checked out, aligned at tag MPIserial_2.3.0" in status.stdout) + + + # Finally update optional + result = git_fleximod(complex_repo, "update --optional") + assert result.returncode == 0 + + status = git_fleximod(complex_repo, "status") + assert("ToplevelOptional at tag v5.3.2" in status.stdout) + assert("ToplevelRequired at tag MPIserial_2.5.0" in status.stdout) + assert("AlwaysRequired at tag MPIserial_2.4.0" in status.stdout) + assert("Complex at tag testtag01" in status.stdout) + assert("AlwaysOptional at tag MPIserial_2.3.0" in status.stdout) + + # now check the complex_sub + root = (complex_repo / "modules" / "complex" ) + assert(not (root / "libraries" / "gptl" / ".git").exists()) + assert(not (root / "libraries" / "mpi-serial" / ".git").exists()) + assert((root / "modules" / "mpi-serial" / ".git").exists()) + assert((root / "modules" / "mpi-serial2" / ".git").exists()) + assert((root / "modules" / "mpi-sparse" / ".git").exists()) + assert((root / "modules" / "mpi-sparse" / "m4").exists()) + assert(not (root / "modules" / "mpi-sparse" / "README").exists()) + + From 194d845ad2131c7ce50e1f5a14fc5476799c87fb Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sat, 17 Feb 2024 08:08:57 -0700 Subject: [PATCH 101/159] add git user info to tests --- .github/workflows/pytest.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 4809a278..0868dd9a 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -70,5 +70,8 @@ jobs: # And finally run tests. I'm using pytest and all my pytest config is in my `pyproject.toml` # so this line is super-simple. But it could be as complex as you need. - - run: poetry run pytest + - run: | + git config --global user.name "${GITHUB_ACTOR}" + git config --global user.email "${GITHUB_ACTOR_ID}+${GITHUB_ACTOR}@users.noreply.github.com" + poetry run pytest From d64c5c6dd311364cc98ffc5ca39be425437e377c Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sat, 17 Feb 2024 08:14:37 -0700 Subject: [PATCH 102/159] remove checkout user option --- git_fleximod/cli.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git_fleximod/cli.py b/git_fleximod/cli.py index 719b1562..80dbd58d 100644 --- a/git_fleximod/cli.py +++ b/git_fleximod/cli.py @@ -25,11 +25,11 @@ def get_parser(): # # user options # - choices = ["update", "checkout", "status", "test"] + choices = ["update", "status", "test"] parser.add_argument( "action", choices=choices, - default="checkout", + default="update", help=f"Subcommand of git-fleximod, choices are {choices[:-1]}", ) From 6bac7946376f7ae85511b4ed02ad4881f9e0440d Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sat, 17 Feb 2024 09:43:54 -0700 Subject: [PATCH 103/159] remove fxhash, putting hash in tag works fine --- git_fleximod/git_fleximod.py | 87 +++++++++++------------------------- tests/conftest.py | 10 ++--- 2 files changed, 32 insertions(+), 65 deletions(-) diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index 08f31fa7..7a4470e2 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -31,7 +31,7 @@ def commandline_arguments(args=None): action = options.action if not action: - action = "checkout" + action = "update" handlers = [logging.StreamHandler()] if options.debug: @@ -65,7 +65,7 @@ def commandline_arguments(args=None): def submodule_sparse_checkout( - root_dir, name, url, path, sparsefile, tag="master", fxhash=None + root_dir, name, url, path, sparsefile, tag="master" ): logger.info("Called sparse_checkout for {}".format(name)) rgit = GitInterface(root_dir, logger) @@ -132,19 +132,14 @@ def submodule_sparse_checkout( shutil.copy(sparsefile, gitsparse) # Finally checkout the repo - if fxhash: - sprepo_git.git_operation("fetch", "origin", "--tags") - sprepo_git.git_operation("checkout", fxhash) - print(f"Successfully checked out {name:>20} at {fxhash}") - else: - sprepo_git.git_operation("fetch", "--depth=1", "origin", "--tags") - sprepo_git.git_operation("checkout", tag) - print(f"Successfully checked out {name:>20} at {tag}") + sprepo_git.git_operation("fetch", "--depth=1", "origin", "--tags") + sprepo_git.git_operation("checkout", tag) + print(f"Successfully checked out {name:>20} at {tag}") rgit.config_set_value(f'submodule "{name}"',"active","true") rgit.config_set_value(f'submodule "{name}"',"url",url) def single_submodule_checkout( - root, name, path, url=None, tag=None, force=False, fxhash=None, optional=False + root, name, path, url=None, tag=None, force=False, optional=False ): git = GitInterface(root, logger) repodir = os.path.join(root, path) @@ -167,12 +162,9 @@ def single_submodule_checkout( url = url.replace("git@github.com:", "https://github.com/") git.git_operation("clone", url, path) smgit = GitInterface(repodir, logger) - if not tag and not fxhash: + if not tag: tag = smgit.git_operation("describe", "--tags", "--always").rstrip() - if fxhash: - smgit.git_operation("checkout", fxhash) - else: - smgit.git_operation("checkout", tag) + smgit.git_operation("checkout", tag) # Now need to move the .git dir to the submodule location rootdotgit = os.path.join(root, ".git") if os.path.isfile(rootdotgit): @@ -214,9 +206,6 @@ def submodules_status(gitmodules, root_dir): for name in gitmodules.sections(): path = gitmodules.get(name, "path") tag = gitmodules.get(name, "fxtag") - fxhash = gitmodules.get(name, "fxhash") - if tag and fxhash: - utils.fatal_error(f"{name:>20} cannot have both fxtag and fxhash") if not path: utils.fatal_error("No path found in .gitmodules for {}".format(name)) newpath = os.path.join(root_dir, path) @@ -234,48 +223,33 @@ def submodules_status(gitmodules, root_dir): if tag and tag == atag: print(f"e {name:>20} not checked out, aligned at tag {tag}") elif tag: - print( - f"e {name:>20} not checked out, out of sync at tag {atag}, expected tag is {tag}" - ) - testfails += 1 - elif fxhash: - n = len(fxhash) - smhash = rootgit.git_operation( - "ls-tree", "--object-only", f"--abbrev={n}", "HEAD", path - ) - if smhash == fxhash: - print(f" {name:>20} not checked out, aligned at hash {fxhash}") + ahash = rootgit.git_operation("submodule", "status", "{}".format(path)).rstrip() + ahash = ahash[1:len(tag)+1] + if tag == ahash: + print(f"e {name:>20} not checked out, aligned at hash {ahash}") else: print( - f"s {name:>20} not checked out, out of sync at hash {smhash}, expected hash is {fxhash}" + f"e {name:>20} not checked out, out of sync at tag {atag}, expected tag is {tag}" ) testfails += 1 else: - print(f"e {name:>20} has no fxtag nor fxhash defined in .gitmodules") + print(f"e {name:>20} has no fxtag defined in .gitmodules") testfails += 1 else: with utils.pushd(newpath): git = GitInterface(newpath, logger) atag = git.git_operation("describe", "--tags", "--always").rstrip() ahash = git.git_operation("status").partition("\n")[0].split()[-1] - if tag and atag != tag: - print(f"s {name:>20} {atag} is out of sync with .gitmodules {tag}") - testfails += 1 - elif tag: + if tag and atag == tag: print(f" {name:>20} at tag {tag}") - elif fxhash: - rootgit = GitInterface(root_dir, logger) - n = len(fxhash) - if ahash.startswith(fxhash): - print(f" {name:>20} at hash {fxhash}") - else: - print( - f"s {name:>20} {ahash} is out of sync with .gitmodules {fxhash}" - ) - testfails += 1 + elif tag and ahash == tag: + print(f" {name:>20} at hash {ahash}") + elif tag: + print(f"s {name:>20} {atag} {ahash} is out of sync with .gitmodules {tag}") + testfails += 1 else: print( - f"e {name:>20} has no fxtag nor fxhash defined in .gitmodules, module at {atag}" + f"e {name:>20} has no fxtag defined in .gitmodules, module at {atag}" ) testfails += 1 @@ -297,7 +271,6 @@ def submodules_update(gitmodules, root_dir, requiredlist, force): return for name in gitmodules.sections(): fxtag = gitmodules.get(name, "fxtag") - fxhash = gitmodules.get(name, "fxhash") path = gitmodules.get(name, "path") url = gitmodules.get(name, "url") logger.info("name={} path={} url={} fxtag={} requiredlist={}".format(name,os.path.join(root_dir, path), url, fxtag, requiredlist)) @@ -320,7 +293,7 @@ def submodules_update(gitmodules, root_dir, requiredlist, force): ) ) submodule_sparse_checkout( - root_dir, name, url, path, fxsparse, tag=fxtag, fxhash=fxhash + root_dir, name, url, path, fxsparse, tag=fxtag ) else: logger.info( @@ -329,7 +302,7 @@ def submodules_update(gitmodules, root_dir, requiredlist, force): single_submodule_checkout( root_dir, name, path, url=url, tag=fxtag, force=force, - fxhash=fxhash, optional=("AlwaysOptional" in requiredlist) + optional=("AlwaysOptional" in requiredlist) ) @@ -362,11 +335,8 @@ def submodules_update(gitmodules, root_dir, requiredlist, force): if fxtag and fxtag != atag: print(f"{name:>20} updated to {fxtag}") git.git_operation("checkout", fxtag) - elif fxhash: - print(f"{name:>20} updated to {fxhash}") - git.git_operation("checkout", fxhash) - elif not (fxtag or fxhash): - print(f"No fxtag nor fxhash found for submodule {name:>20}") + elif not fxtag: + print(f"No fxtag found for submodule {name:>20}") else: print(f"{name:>20} up to date.") @@ -383,7 +353,6 @@ def submodules_checkout(gitmodules, root_dir, requiredlist, force=False): fxrequired = gitmodules.get(name, "fxrequired") fxsparse = gitmodules.get(name, "fxsparse") fxtag = gitmodules.get(name, "fxtag") - fxhash = gitmodules.get(name, "fxhash") path = gitmodules.get(name, "path") url = gitmodules.get(name, "url") if fxrequired and fxrequired not in requiredlist: @@ -398,7 +367,7 @@ def submodules_checkout(gitmodules, root_dir, requiredlist, force=False): ) ) submodule_sparse_checkout( - root_dir, name, url, path, fxsparse, tag=fxtag, fxhash=fxhash + root_dir, name, url, path, fxsparse, tag=fxtag ) else: logger.debug( @@ -406,7 +375,7 @@ def submodules_checkout(gitmodules, root_dir, requiredlist, force=False): ) single_submodule_checkout( - root_dir, name, path, url=url, tag=fxtag, force=force, fxhash=fxhash, + root_dir, name, path, url=url, tag=fxtag, force=force, optional = "AlwaysOptional" in requiredlist ) @@ -469,8 +438,6 @@ def main(): retval = 0 if action == "update": submodules_update(gitmodules, root_dir, fxrequired, force) - elif action == "checkout": - submodules_checkout(gitmodules, root_dir, fxrequired, force) elif action == "status": submodules_status(gitmodules, root_dir) elif action == "test": diff --git a/tests/conftest.py b/tests/conftest.py index b88fa61d..5fed907c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -44,15 +44,15 @@ def logger(): """}, {"subrepo_path": "modules/test_alwaysoptional", "submodule_name": "test_alwaysoptional", - "status1" : "test_alwaysoptional MPIserial_2.5.0-3-gd82ce7c is out of sync with .gitmodules MPIserial_2.3.0", - "status2" : "test_alwaysoptional at tag MPIserial_2.3.0", - "status3" : "test_alwaysoptional not checked out, aligned at tag MPIserial_2.3.0", - "status4" : "test_alwaysoptional at tag MPIserial_2.3.0", + "status1" : "test_alwaysoptional MPIserial_2.3.0 is out of sync with .gitmodules e5cf35c", + "status2" : "test_alwaysoptional at hash e5cf35c", + "status3" : "test_alwaysoptional not checked out, out of sync at tag MPIserial_2.3.0", + "status4" : "test_alwaysoptional at hash e5cf35c", "gitmodules_content": """ [submodule "test_alwaysoptional"] path = modules/test_alwaysoptional url = https://github.com/ESMCI/mpi-serial.git - fxtag = MPIserial_2.3.0 + fxtag = e5cf35c fxurl = https://github.com/ESMCI/mpi-serial.git fxrequired = AlwaysOptional """}, From 678bae800ca818a0dd458dbc39334007da8f973d Mon Sep 17 00:00:00 2001 From: James Edwards Date: Sun, 18 Feb 2024 10:09:05 -0700 Subject: [PATCH 104/159] update version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 772b1542..e8c7bd58 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "git-fleximod" -version = "0.6.2" +version = "0.7.0" description = "Extended support for git-submodule and git-sparse-checkout" authors = ["Jim Edwards "] maintainers = ["Jim Edwards "] From 8d0ed18d3a7f30048aa4b302eb607e27bf1d45ff Mon Sep 17 00:00:00 2001 From: James Edwards Date: Thu, 22 Feb 2024 09:25:59 -0700 Subject: [PATCH 105/159] add translater from manage_externals --- git_fleximod/gitinterface.py | 22 ++-- git_fleximod/gitmodules.py | 39 +++--- git_fleximod/metoflexi.py | 242 +++++++++++++++++++++++++++++++++++ pyproject.toml | 1 + 4 files changed, 281 insertions(+), 23 deletions(-) create mode 100755 git_fleximod/metoflexi.py diff --git a/git_fleximod/gitinterface.py b/git_fleximod/gitinterface.py index 4d4c4f4b..203c5003 100644 --- a/git_fleximod/gitinterface.py +++ b/git_fleximod/gitinterface.py @@ -1,25 +1,31 @@ import os import sys from . import utils +from pathlib import Path class GitInterface: def __init__(self, repo_path, logger): logger.debug("Initialize GitInterface for {}".format(repo_path)) - self.repo_path = repo_path + if isinstance(repo_path, str): + self.repo_path = Path(repo_path).resolve() + elif isinstance(repo_path, Path): + self.repo_path = repo_path.resolve() + else: + raise TypeError("repo_path must be a str or Path object") self.logger = logger try: import git self._use_module = True try: - self.repo = git.Repo(repo_path) # Initialize GitPython repo + self.repo = git.Repo(str(self.repo_path)) # Initialize GitPython repo except git.exc.InvalidGitRepositoryError: self.git = git self._init_git_repo() msg = "Using GitPython interface to git" except ImportError: self._use_module = False - if not os.path.exists(os.path.join(repo_path, ".git")): + if not (repo_path / ".git").exists(): self._init_git_repo() msg = "Using shell interface to git" self.logger.info(msg) @@ -32,13 +38,13 @@ def _git_command(self, operation, *args): except Exception as e: sys.exit(e) else: - return ["git", "-C", self.repo_path, operation] + list(args) + return ["git", "-C", str(self.repo_path), operation] + list(args) def _init_git_repo(self): if self._use_module: - self.repo = self.git.Repo.init(self.repo_path) + self.repo = self.git.Repo.init(str(self.repo_path)) else: - command = ("git", "-C", self.repo_path, "init") + command = ("git", "-C", str(self.repo_path), "init") utils.execute_subprocess(command) # pylint: disable=unused-argument @@ -58,7 +64,7 @@ def config_get_value(self, section, name): config = self.repo.config_reader() return config.get_value(section, name) else: - cmd = ("git", "-C", self.repo_path, "config", "--get", f"{section}.{name}") + cmd = ("git", "-C", str(self.repo_path), "config", "--get", f"{section}.{name}") output = utils.execute_subprocess(cmd, output_to_caller=True) return output.strip() @@ -68,6 +74,6 @@ def config_set_value(self, section, name, value): writer.set_value(section, name, value) writer.release() # Ensure changes are saved else: - cmd = ("git", "-C", self.repo_path, "config", f"{section}.{name}", value) + cmd = ("git", "-C", str(self.repo_path), "config", f"{section}.{name}", value) self.logger.info(cmd) utils.execute_subprocess(cmd, output_to_caller=True) diff --git a/git_fleximod/gitmodules.py b/git_fleximod/gitmodules.py index ae0ebe12..68c82d06 100644 --- a/git_fleximod/gitmodules.py +++ b/git_fleximod/gitmodules.py @@ -1,14 +1,14 @@ -import os import shutil -from configparser import ConfigParser +from pathlib import Path +from configparser import RawConfigParser, ConfigParser from .lstripreader import LstripReader -class GitModules(ConfigParser): +class GitModules(RawConfigParser): def __init__( self, logger, - confpath=os.getcwd(), + confpath=Path.cwd(), conffile=".gitmodules", includelist=None, excludelist=None, @@ -25,25 +25,32 @@ def __init__( confpath, conffile, includelist, excludelist ) ) - ConfigParser.__init__(self) - self.conf_file = os.path.join(confpath, conffile) - # first create a backup of this file to be restored on deletion of the object - shutil.copy(self.conf_file, self.conf_file + ".save") - self.read_file(LstripReader(self.conf_file), source=conffile) + super().__init__() + self.conf_file = (Path(confpath) / Path(conffile)) + if self.conf_file.exists(): + self.read_file(LstripReader(str(self.conf_file)), source=conffile) self.includelist = includelist self.excludelist = excludelist + self.isdirty = False + + def reload(self): + self.clear() + if self.conf_file.exists(): + self.read_file(LstripReader(str(self.conf_file)), source=self.conf_file) + def set(self, name, option, value): """ Sets a configuration value for a specific submodule: Ensures the appropriate section exists for the submodule. Calls the parent class's set method to store the value. """ + self.isdirty = True self.logger.debug("set called {} {} {}".format(name, option, value)) section = f'submodule "{name}"' if not self.has_section(section): self.add_section(section) - ConfigParser.set(self, section, option, str(value)) + super().set(section, option, str(value)) # pylint: disable=redefined-builtin, arguments-differ def get(self, name, option, raw=False, vars=None, fallback=None): @@ -62,12 +69,14 @@ def get(self, name, option, raw=False, vars=None, fallback=None): return None def save(self): - print("Called gitmodules save, not expected") - # self.write(open(self.conf_file, "w")) - + if self.isdirty: + self.logger.info("Writing {}".format(self.conf_file)) + with open(self.conf_file, "w") as fd: + self.write(fd) + self.isdirty = False + def __del__(self): - self.logger.debug("Destroying GitModules object") - shutil.move(self.conf_file + ".save", self.conf_file) + self.save() def sections(self): """Strip the submodule part out of section and just use the name""" diff --git a/git_fleximod/metoflexi.py b/git_fleximod/metoflexi.py new file mode 100755 index 00000000..1d56bc9a --- /dev/null +++ b/git_fleximod/metoflexi.py @@ -0,0 +1,242 @@ +#!/usr/bin/env python +from configparser import ConfigParser +import sys +import shutil +from pathlib import Path +import argparse +import logging +from git_fleximod.gitinterface import GitInterface +from git_fleximod.gitmodules import GitModules +from git_fleximod import utils + +logger = None + +def find_root_dir(filename=".git"): + d = Path.cwd() + root = Path(d.root) + while d != root: + attempt = d / filename + if attempt.is_dir(): + return attempt + d = d.parent + return None + + +def get_parser(): + description = """ + %(prog)s manages checking out groups of gitsubmodules with addtional support for Earth System Models + """ + parser = argparse.ArgumentParser( + description=description, formatter_class=argparse.RawDescriptionHelpFormatter + ) + + parser.add_argument('-e', '--externals', nargs='?', + default='Externals.cfg', + help='The externals description filename. ' + 'Default: %(default)s.') + + parser.add_argument( + "-C", + "--path", + default=find_root_dir(), + help="Toplevel repository directory. Defaults to top git directory relative to current.", + ) + + parser.add_argument( + "-g", + "--gitmodules", + nargs="?", + default=".gitmodules", + help="The submodule description filename. " "Default: %(default)s.", + ) + parser.add_argument( + "-v", + "--verbose", + action="count", + default=0, + help="Output additional information to " + "the screen and log file. This flag can be " + "used up to two times, increasing the " + "verbosity level each time.", + ) + parser.add_argument( + "-d", + "--debug", + action="store_true", + default=False, + help="DEVELOPER: output additional debugging " + "information to the screen and log file.", + ) + + return parser + +def commandline_arguments(args=None): + parser = get_parser() + + options = parser.parse_args(args) + handlers = [logging.StreamHandler()] + + if options.debug: + try: + open("fleximod.log", "w") + except PermissionError: + sys.exit("ABORT: Could not write file fleximod.log") + level = logging.DEBUG + handlers.append(logging.FileHandler("fleximod.log")) + elif options.verbose: + level = logging.INFO + else: + level = logging.WARNING + # Configure the root logger + logging.basicConfig( + level=level, format="%(name)s - %(levelname)s - %(message)s", handlers=handlers + ) + + return( + options.path, + options.gitmodules, + options.externals + ) + +class ExternalRepoTranslator: + """ + Translates external repositories configured in an INI-style externals file. + """ + + def __init__(self, rootpath, gitmodules, externals): + self.rootpath = rootpath + if gitmodules: + self.gitmodules = GitModules(logger, confpath=rootpath) + self.externals = (rootpath / Path(externals)).resolve() + print(f"Translating {self.externals}") + self.git = GitInterface(rootpath, logger) + +# def __del__(self): +# if (self.rootpath / "save.gitignore"): + + + def translate_single_repo(self, section, tag, url, path, efile, hash_, sparse, protocol): + """ + Translates a single repository based on configuration details. + + Args: + rootpath (str): Root path of the main repository. + gitmodules (str): Path to the .gitmodules file. + tag (str): The tag to use for the external repository. + url (str): The URL of the external repository. + path (str): The relative path within the main repository for the external repository. + efile (str): The external file or file containing submodules. + hash_ (str): The commit hash to checkout (if applicable). + sparse (str): Boolean indicating whether to use sparse checkout (if applicable). + protocol (str): The protocol to use (e.g., 'git', 'http'). + """ + assert protocol != "svn", "SVN protocol is not currently supported" + print(f"Translating repository {section}") + if efile: + file_path = Path(path) / Path(efile) + newroot = (self.rootpath / file_path).parent.resolve() + if not newroot.exists(): + newroot.mkdir(parents=True) + logger.info("Newroot is {}".format(newroot)) + newt = ExternalRepoTranslator(newroot, ".gitmodules", efile) + newt.translate_repo() + if protocol == "externals_only": + if tag: + self.gitmodules.set(section, "fxtag", tag) + if hash_: + self.gitmodules.set(section, "fxtag", hash_) + + self.gitmodules.set(section, "fxurl", url) + if sparse: + self.gitmodules.set(section, "fxsparse", sparse) + self.gitmodules.set(section, "fxrequired", "ToplevelRequired") + + return + + newpath = (self.rootpath / Path(path)) + if newpath.exists(): + shutil.rmtree(newpath) + logger.info("Creating directory {}".format(newpath)) + newpath.mkdir(parents=True) + if tag: + logger.info("cloning {}".format(section)) + try: + self.git.git_operation("clone", "-b", tag, "--depth", "1", url, path) + except: + self.git.git_operation("clone", url, path) + with utils.pushd(newpath): + ngit = GitInterface(newpath, logger) + ngit.git_operation("checkout", tag) + + if (newpath / ".gitignore").exists(): + logger.info("Moving .gitignore file in {}".format(newpath)) + (newpath / ".gitignore").rename((newpath / "save.gitignore")) + + if hash_: + self.git.git_operation("clone", url, path) + git = GitInterface(newpath, logger) + git.git_operation("fetch", "origin") + git.git_operation("checkout", hash_) + if sparse: + print("setting as sparse submodule {}".format(section)) + sparsefile = (newpath / Path(sparse)) + newfile = (newpath / ".git" / "info" / "sparse-checkout") + print(f"sparsefile {sparsefile} newfile {newfile}") + shutil.copy(sparsefile, newfile) + logger.info("adding submodule {}".format(section)) + self.gitmodules.save() + self.git.git_operation("submodule", "add", "-f", "--name", section, url, path) + self.git.git_operation("submodule","absorbgitdirs") + self.gitmodules.reload() + if tag: + self.gitmodules.set(section, "fxtag", tag) + if hash_: + self.gitmodules.set(section, "fxtag", hash_) + + self.gitmodules.set(section, "fxurl", url) + if sparse: + self.gitmodules.set(section, "fxsparse", sparse) + self.gitmodules.set(section, "fxrequired", "ToplevelRequired") + + + def translate_repo(self): + """ + Translates external repositories defined within an external file. + + Args: + rootpath (str): Root path of the main repository. + gitmodules (str): Path to the .gitmodules file. + external_file (str): The path to the external file containing repository definitions. + """ + econfig = ConfigParser() + econfig.read((self.rootpath / Path(self.externals))) + + for section in econfig.sections(): + if section == "externals_description": + logger.info("skipping section {}".format(section)) + return + logger.info("Translating section {}".format(section)) + tag = econfig.get(section, "tag", raw=False, fallback=None) + url = econfig.get(section, "repo_url", raw=False, fallback=None) + path = econfig.get(section, "local_path", raw=False, fallback=None) + efile = econfig.get(section, "externals", raw=False, fallback=None) + hash_ = econfig.get(section, "hash", raw=False, fallback=None) + sparse = econfig.get(section, "sparse", raw=False, fallback=None) + protocol = econfig.get(section, "protocol", raw=False, fallback=None) + + self.translate_single_repo(section, tag, url, path, efile, hash_, sparse, protocol) + + + +def _main(): + rootpath, gitmodules, externals = commandline_arguments() + global logger + logger = logging.getLogger(__name__) + with utils.pushd(rootpath): + t = ExternalRepoTranslator(Path(rootpath), gitmodules, externals) + logger.info("Translating {}".format(rootpath)) + t.translate_repo() + + +if __name__ == "__main__": + sys.exit(_main()) diff --git a/pyproject.toml b/pyproject.toml index e8c7bd58..212b014d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,7 @@ packages = [ [tool.poetry.scripts] git-fleximod = "git_fleximod.git_fleximod:main" +me2flexi = "git_fleximod.metoflexi:_main" fsspec = "fsspec.fuse:main" [tool.poetry.dependencies] From 91df5a0ea5f8601c01c0462cf7e9a8aca84f2aa3 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Wed, 28 Feb 2024 16:00:42 -0700 Subject: [PATCH 106/159] update README and improve me2flexi --- README.md | 5 ++--- git_fleximod/git_fleximod.py | 5 +++-- git_fleximod/metoflexi.py | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 94d85c41..c17c5b6f 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,6 @@ Git-fleximod is a Python-based tool that extends Git's submodule and sparse chec Basic Usage: git fleximod [options] Available Commands: - checkout: Checkout submodules according to git submodule hash configuration. status: Display the status of submodules. update: Update submodules to the tag indicated in .gitmodules variable fxtag. test: Make sure that fxtags and submodule hashes are consistant, @@ -55,9 +54,9 @@ Git-fleximod is a Python-based tool that extends Git's submodule and sparse chec Here are some common usage examples: -Checkout submodules, including optional ones: +Update all submodules, including optional ones: ```bash - git fleximod checkout --optional + git fleximod update --optional ``` Updating a specific submodule to the fxtag indicated in .gitmodules: diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index 7a4470e2..1f60b182 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -132,8 +132,9 @@ def submodule_sparse_checkout( shutil.copy(sparsefile, gitsparse) # Finally checkout the repo - sprepo_git.git_operation("fetch", "--depth=1", "origin", "--tags") + sprepo_git.git_operation("fetch", "origin", "--tags") sprepo_git.git_operation("checkout", tag) + print(f"Successfully checked out {name:>20} at {tag}") rgit.config_set_value(f'submodule "{name}"',"active","true") rgit.config_set_value(f'submodule "{name}"',"url",url) @@ -242,7 +243,7 @@ def submodules_status(gitmodules, root_dir): ahash = git.git_operation("status").partition("\n")[0].split()[-1] if tag and atag == tag: print(f" {name:>20} at tag {tag}") - elif tag and ahash == tag: + elif tag and ahash[:len(tag)] == tag: print(f" {name:>20} at hash {ahash}") elif tag: print(f"s {name:>20} {atag} {ahash} is out of sync with .gitmodules {tag}") diff --git a/git_fleximod/metoflexi.py b/git_fleximod/metoflexi.py index 1d56bc9a..b607ad92 100755 --- a/git_fleximod/metoflexi.py +++ b/git_fleximod/metoflexi.py @@ -168,9 +168,9 @@ def translate_single_repo(self, section, tag, url, path, efile, hash_, sparse, p ngit = GitInterface(newpath, logger) ngit.git_operation("checkout", tag) - if (newpath / ".gitignore").exists(): - logger.info("Moving .gitignore file in {}".format(newpath)) - (newpath / ".gitignore").rename((newpath / "save.gitignore")) +# if (newpath / ".gitignore").exists(): +# logger.info("Moving .gitignore file in {}".format(newpath)) +# (newpath / ".gitignore").rename((newpath / "save.gitignore")) if hash_: self.git.git_operation("clone", url, path) From b7914f26c22704b0aa984bad0978eeb7d88e55e0 Mon Sep 17 00:00:00 2001 From: James Edwards Date: Thu, 29 Feb 2024 15:25:23 -0700 Subject: [PATCH 107/159] correcting version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 212b014d..cbbe83a4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "git-fleximod" -version = "0.7.0" +version = "0.6.2" description = "Extended support for git-submodule and git-sparse-checkout" authors = ["Jim Edwards "] maintainers = ["Jim Edwards "] From ff3fcb9587fabfe28e924b2249bcee89bb68d97c Mon Sep 17 00:00:00 2001 From: James Edwards Date: Thu, 29 Feb 2024 15:25:38 -0700 Subject: [PATCH 108/159] Bump to 0.7.0 --- git_fleximod/cli.py | 2 +- pyproject.toml | 2 +- tbump.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/git_fleximod/cli.py b/git_fleximod/cli.py index 80dbd58d..f9cd67cf 100644 --- a/git_fleximod/cli.py +++ b/git_fleximod/cli.py @@ -1,7 +1,7 @@ from pathlib import Path import argparse -__version__ = "0.6.2" +__version__ = "0.7.0" def find_root_dir(filename=".git"): d = Path.cwd() diff --git a/pyproject.toml b/pyproject.toml index cbbe83a4..212b014d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "git-fleximod" -version = "0.6.2" +version = "0.7.0" description = "Extended support for git-submodule and git-sparse-checkout" authors = ["Jim Edwards "] maintainers = ["Jim Edwards "] diff --git a/tbump.toml b/tbump.toml index 583cef45..568226df 100644 --- a/tbump.toml +++ b/tbump.toml @@ -2,7 +2,7 @@ github_url = "https://github.com/jedwards4b/git-fleximod/" [version] -current = "0.6.2" +current = "0.7.0" # Example of a semver regexp. # Make sure this matches current_version before From fd53786233fd742ec7959908cc8a543dd5315d8f Mon Sep 17 00:00:00 2001 From: James Edwards Date: Fri, 1 Mar 2024 07:09:45 -0700 Subject: [PATCH 109/159] code cleanup --- git_fleximod/git_fleximod.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index 1f60b182..8f2b1199 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -184,14 +184,14 @@ def single_submodule_checkout( if os.path.exists(os.path.join(repodir, ".gitmodules")): # recursively handle this checkout - print(f"Recursively checking out submodules of {name} {repodir} {url}") + print(f"Recursively checking out submodules of {name}") gitmodules = GitModules(logger, confpath=repodir) requiredlist = ["AlwaysRequired"] if optional: requiredlist.append("AlwaysOptional") submodules_checkout(gitmodules, repodir, requiredlist, force=force) if os.path.exists(os.path.join(repodir, ".git")): - print(f"Successfully checked out {name} {repodir}") + print(f"Successfully checked out {name:>20}") else: utils.fatal_error(f"Failed to checkout {name} {repo_exists} {tmpurl} {repodir} {path}") @@ -207,6 +207,8 @@ def submodules_status(gitmodules, root_dir): for name in gitmodules.sections(): path = gitmodules.get(name, "path") tag = gitmodules.get(name, "fxtag") + required = gitmodules.get(name, "fxrequired") + level = required and "Toplevel" in required if not path: utils.fatal_error("No path found in .gitmodules for {}".format(name)) newpath = os.path.join(root_dir, path) @@ -217,6 +219,8 @@ def submodules_status(gitmodules, root_dir): url = gitmodules.get(name, "url") tags = rootgit.git_operation("ls-remote", "--tags", url) atag = None + if level: + continue for htag in tags.split("\n"): if tag and tag in htag: atag = (htag.split()[1])[10:] From 0c26ad0ce55a4d080ef211662e9f0d544c4ceada Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Thu, 14 Mar 2024 12:43:23 -0600 Subject: [PATCH 110/159] update cleanup --- git_fleximod/git_fleximod.py | 12 +++--- git_fleximod/metoflexi.py | 84 +++++++++++++++++------------------- 2 files changed, 46 insertions(+), 50 deletions(-) diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index 8f2b1199..69301e72 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -201,7 +201,7 @@ def single_submodule_checkout( return -def submodules_status(gitmodules, root_dir): +def submodules_status(gitmodules, root_dir, toplevel=False): testfails = 0 localmods = 0 for name in gitmodules.sections(): @@ -219,7 +219,7 @@ def submodules_status(gitmodules, root_dir): url = gitmodules.get(name, "url") tags = rootgit.git_operation("ls-remote", "--tags", url) atag = None - if level: + if not toplevel and level: continue for htag in tags.split("\n"): if tag and tag in htag: @@ -268,18 +268,20 @@ def submodules_status(gitmodules, root_dir): def submodules_update(gitmodules, root_dir, requiredlist, force): _, localmods = submodules_status(gitmodules, root_dir) - print("") + if localmods and not force: print( "Repository has local mods, cowardly refusing to continue, fix issues or use --force to override" ) return + elif not localmods: + return for name in gitmodules.sections(): fxtag = gitmodules.get(name, "fxtag") path = gitmodules.get(name, "path") url = gitmodules.get(name, "url") logger.info("name={} path={} url={} fxtag={} requiredlist={}".format(name,os.path.join(root_dir, path), url, fxtag, requiredlist)) -# if not os.path.exists(os.path.join(root_dir,path, ".git")): + # if not os.path.exists(os.path.join(root_dir,path, ".git")): fxrequired = gitmodules.get(name, "fxrequired") assert(fxrequired in fxrequired_allowed_values()) rgit = GitInterface(root_dir, logger) @@ -444,7 +446,7 @@ def main(): if action == "update": submodules_update(gitmodules, root_dir, fxrequired, force) elif action == "status": - submodules_status(gitmodules, root_dir) + submodules_status(gitmodules, root_dir, toplevel=True) elif action == "test": retval = submodules_test(gitmodules, root_dir) else: diff --git a/git_fleximod/metoflexi.py b/git_fleximod/metoflexi.py index b607ad92..1a4f8421 100755 --- a/git_fleximod/metoflexi.py +++ b/git_fleximod/metoflexi.py @@ -150,53 +150,47 @@ def translate_single_repo(self, section, tag, url, path, efile, hash_, sparse, p if sparse: self.gitmodules.set(section, "fxsparse", sparse) self.gitmodules.set(section, "fxrequired", "ToplevelRequired") - - return - - newpath = (self.rootpath / Path(path)) - if newpath.exists(): - shutil.rmtree(newpath) - logger.info("Creating directory {}".format(newpath)) - newpath.mkdir(parents=True) - if tag: - logger.info("cloning {}".format(section)) - try: - self.git.git_operation("clone", "-b", tag, "--depth", "1", url, path) - except: + else: + newpath = (self.rootpath / Path(path)) + if newpath.exists(): + shutil.rmtree(newpath) + logger.info("Creating directory {}".format(newpath)) + newpath.mkdir(parents=True) + if tag: + logger.info("cloning {}".format(section)) + try: + self.git.git_operation("clone", "-b", tag, "--depth", "1", url, path) + except: + self.git.git_operation("clone", url, path) + with utils.pushd(newpath): + ngit = GitInterface(newpath, logger) + ngit.git_operation("checkout", tag) + if hash_: self.git.git_operation("clone", url, path) - with utils.pushd(newpath): - ngit = GitInterface(newpath, logger) - ngit.git_operation("checkout", tag) - -# if (newpath / ".gitignore").exists(): -# logger.info("Moving .gitignore file in {}".format(newpath)) -# (newpath / ".gitignore").rename((newpath / "save.gitignore")) - - if hash_: - self.git.git_operation("clone", url, path) - git = GitInterface(newpath, logger) - git.git_operation("fetch", "origin") - git.git_operation("checkout", hash_) - if sparse: - print("setting as sparse submodule {}".format(section)) - sparsefile = (newpath / Path(sparse)) - newfile = (newpath / ".git" / "info" / "sparse-checkout") - print(f"sparsefile {sparsefile} newfile {newfile}") - shutil.copy(sparsefile, newfile) - logger.info("adding submodule {}".format(section)) - self.gitmodules.save() - self.git.git_operation("submodule", "add", "-f", "--name", section, url, path) - self.git.git_operation("submodule","absorbgitdirs") - self.gitmodules.reload() - if tag: - self.gitmodules.set(section, "fxtag", tag) - if hash_: - self.gitmodules.set(section, "fxtag", hash_) + git = GitInterface(newpath, logger) + git.git_operation("fetch", "origin") + git.git_operation("checkout", hash_) + if sparse: + print("setting as sparse submodule {}".format(section)) + sparsefile = (newpath / Path(sparse)) + newfile = (newpath / ".git" / "info" / "sparse-checkout") + print(f"sparsefile {sparsefile} newfile {newfile}") + shutil.copy(sparsefile, newfile) + + logger.info("adding submodule {}".format(section)) + self.gitmodules.save() + self.git.git_operation("submodule", "add", "-f", "--name", section, url, path) + self.git.git_operation("submodule","absorbgitdirs") + self.gitmodules.reload() + if tag: + self.gitmodules.set(section, "fxtag", tag) + if hash_: + self.gitmodules.set(section, "fxtag", hash_) - self.gitmodules.set(section, "fxurl", url) - if sparse: - self.gitmodules.set(section, "fxsparse", sparse) - self.gitmodules.set(section, "fxrequired", "ToplevelRequired") + self.gitmodules.set(section, "fxurl", url) + if sparse: + self.gitmodules.set(section, "fxsparse", sparse) + self.gitmodules.set(section, "fxrequired", "ToplevelRequired") def translate_repo(self): From 17dc6d3a0abbc7a6f69c79197f6892501b7e2e8d Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Thu, 14 Mar 2024 12:45:58 -0600 Subject: [PATCH 111/159] update cleanup --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c17c5b6f..f8afb40b 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,8 @@ Git-fleximod is a Python-based tool that extends Git's submodule and sparse chec - AlwaysOptional: Always optional (checked out with --optional flag). fxsparse: Enable sparse checkout for a submodule, pointing to a file containing sparse checkout paths. fxurl: This is the url used in the test subcommand to assure that protected branches do not point to forks + **NOTE** the fxurl variable is only used to identify the official project repository and should not be + changed by users. Use the url variable to change to a fork if desired. ## Sparse Checkouts From a56b7f7cec1372dec15962865123f9d14f8fd4bf Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Thu, 14 Mar 2024 14:33:45 -0600 Subject: [PATCH 112/159] all tests passing --- git_fleximod/git_fleximod.py | 52 +++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 6 deletions(-) diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index 69301e72..db788c14 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -67,6 +67,24 @@ def commandline_arguments(args=None): def submodule_sparse_checkout( root_dir, name, url, path, sparsefile, tag="master" ): + """ + This function performs a sparse checkout of a git submodule. It does so by first creating the .git/info/sparse-checkout fileq + in the submodule and then checking out the desired tag. If the submodule is already checked out, it will not be checked out again. + Creating the sparse-checkout file first prevents the entire submodule from being checked out and then removed. This is important + because the submodule may have a large number of files and checking out the entire submodule and then removing it would be time + and disk space consuming. + + Parameters: + root_dir (str): The root directory for the git operation. + name (str): The name of the submodule. + url (str): The URL of the submodule. + path (str): The path to the submodule. + sparsefile (str): The sparse file for the submodule. + tag (str, optional): The tag to checkout. Defaults to "master". + + Returns: + None + """ logger.info("Called sparse_checkout for {}".format(name)) rgit = GitInterface(root_dir, logger) superroot = rgit.git_operation("rev-parse", "--show-superproject-working-tree") @@ -140,8 +158,24 @@ def submodule_sparse_checkout( rgit.config_set_value(f'submodule "{name}"',"url",url) def single_submodule_checkout( - root, name, path, url=None, tag=None, force=False, optional=False + root, name, path, url=None, tag=None, force=False, optional=False ): + """ + This function checks out a single git submodule. + + Parameters: + root (str): The root directory for the git operation. + name (str): The name of the submodule. + path (str): The path to the submodule. + url (str, optional): The URL of the submodule. Defaults to None. + tag (str, optional): The tag to checkout. Defaults to None. + force (bool, optional): If set to True, forces the checkout operation. Defaults to False. + optional (bool, optional): If set to True, the submodule is considered optional. Defaults to False. + + Returns: + None + """ + # function implementation... git = GitInterface(root, logger) repodir = os.path.join(root, path) logger.info("Checkout {} into {}/{}".format(name,root,path)) @@ -204,6 +238,7 @@ def single_submodule_checkout( def submodules_status(gitmodules, root_dir, toplevel=False): testfails = 0 localmods = 0 + needsupdate = 0 for name in gitmodules.sections(): path = gitmodules.get(name, "path") tag = gitmodules.get(name, "fxtag") @@ -219,6 +254,7 @@ def submodules_status(gitmodules, root_dir, toplevel=False): url = gitmodules.get(name, "url") tags = rootgit.git_operation("ls-remote", "--tags", url) atag = None + needsupdate += 1 if not toplevel and level: continue for htag in tags.split("\n"): @@ -252,6 +288,7 @@ def submodules_status(gitmodules, root_dir, toplevel=False): elif tag: print(f"s {name:>20} {atag} {ahash} is out of sync with .gitmodules {tag}") testfails += 1 + needsupdate += 1 else: print( f"e {name:>20} has no fxtag defined in .gitmodules, module at {atag}" @@ -263,19 +300,20 @@ def submodules_status(gitmodules, root_dir, toplevel=False): localmods = localmods + 1 print("M" + textwrap.indent(status, " ")) - return testfails, localmods + return testfails, localmods, needsupdate def submodules_update(gitmodules, root_dir, requiredlist, force): - _, localmods = submodules_status(gitmodules, root_dir) + _, localmods, needsupdate = submodules_status(gitmodules, root_dir) if localmods and not force: print( "Repository has local mods, cowardly refusing to continue, fix issues or use --force to override" ) return - elif not localmods: + if needsupdate == 0: return + for name in gitmodules.sections(): fxtag = gitmodules.get(name, "fxtag") path = gitmodules.get(name, "path") @@ -349,13 +387,15 @@ def submodules_update(gitmodules, root_dir, requiredlist, force): # checkout is done by update if required so this function may be depricated def submodules_checkout(gitmodules, root_dir, requiredlist, force=False): - _, localmods = submodules_status(gitmodules, root_dir) + _, localmods, needsupdate = submodules_status(gitmodules, root_dir) print("") if localmods and not force: print( "Repository has local mods, cowardly refusing to continue, fix issues or use --force to override" ) return + if not needsupdate: + return for name in gitmodules.sections(): fxrequired = gitmodules.get(name, "fxrequired") fxsparse = gitmodules.get(name, "fxsparse") @@ -389,7 +429,7 @@ def submodules_checkout(gitmodules, root_dir, requiredlist, force=False): def submodules_test(gitmodules, root_dir): # First check that fxtags are present and in sync with submodule hashes - testfails, localmods = submodules_status(gitmodules, root_dir) + testfails, localmods, _ = submodules_status(gitmodules, root_dir) print("") # Then make sure that urls are consistant with fxurls (not forks and not ssh) # and that sparse checkout files exist From c31e17f4fbb17aaffd8afdd1475b19dcfb074409 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Thu, 14 Mar 2024 14:48:01 -0600 Subject: [PATCH 113/159] more cleanup and comments --- git_fleximod/git_fleximod.py | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index db788c14..89b0a6e0 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -387,8 +387,21 @@ def submodules_update(gitmodules, root_dir, requiredlist, force): # checkout is done by update if required so this function may be depricated def submodules_checkout(gitmodules, root_dir, requiredlist, force=False): - _, localmods, needsupdate = submodules_status(gitmodules, root_dir) + """ + This function checks out all git submodules based on the provided parameters. + + Parameters: + gitmodules (ConfigParser): The gitmodules configuration. + root_dir (str): The root directory for the git operation. + requiredlist (list): The list of required modules. + force (bool, optional): If set to True, forces the checkout operation. Defaults to False. + + Returns: + None + """ + # function implementation... print("") + _, localmods, needsupdate = submodules_status(gitmodules, root_dir) if localmods and not force: print( "Repository has local mods, cowardly refusing to continue, fix issues or use --force to override" @@ -426,10 +439,23 @@ def submodules_checkout(gitmodules, root_dir, requiredlist, force=False): optional = "AlwaysOptional" in requiredlist ) - def submodules_test(gitmodules, root_dir): + """ + This function tests the git submodules based on the provided parameters. + + It first checks that fxtags are present and in sync with submodule hashes. + Then it ensures that urls are consistent with fxurls (not forks and not ssh) + and that sparse checkout files exist. + + Parameters: + gitmodules (ConfigParser): The gitmodules configuration. + root_dir (str): The root directory for the git operation. + + Returns: + int: The number of test failures. + """ # First check that fxtags are present and in sync with submodule hashes - testfails, localmods, _ = submodules_status(gitmodules, root_dir) + testfails, localmods, needsupdate = submodules_status(gitmodules, root_dir) print("") # Then make sure that urls are consistant with fxurls (not forks and not ssh) # and that sparse checkout files exist @@ -444,7 +470,7 @@ def submodules_test(gitmodules, root_dir): if fxsparse and not os.path.isfile(os.path.join(root_dir, path, fxsparse)): print(f"{name:>20} sparse checkout file {fxsparse} not found") testfails += 1 - return testfails + localmods + return testfails + localmods + needsupdate def main(): From 82428601f52b15b27f67031ad093d91554d09d3e Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Tue, 19 Mar 2024 13:00:01 -0600 Subject: [PATCH 114/159] fix py3.11 issue in gitinterface --- git_fleximod/gitinterface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git_fleximod/gitinterface.py b/git_fleximod/gitinterface.py index 203c5003..93ae38ec 100644 --- a/git_fleximod/gitinterface.py +++ b/git_fleximod/gitinterface.py @@ -25,7 +25,7 @@ def __init__(self, repo_path, logger): msg = "Using GitPython interface to git" except ImportError: self._use_module = False - if not (repo_path / ".git").exists(): + if not (self.repo_path / ".git").exists(): self._init_git_repo() msg = "Using shell interface to git" self.logger.info(msg) From 3fba38563fd5a9ceec5d0abcd210ae90676b6309 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Thu, 28 Mar 2024 13:09:57 -0600 Subject: [PATCH 115/159] explain M flag --- git_fleximod/git_fleximod.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index 89b0a6e0..ebb719d4 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -307,9 +307,7 @@ def submodules_update(gitmodules, root_dir, requiredlist, force): _, localmods, needsupdate = submodules_status(gitmodules, root_dir) if localmods and not force: - print( - "Repository has local mods, cowardly refusing to continue, fix issues or use --force to override" - ) + local_mods_output() return if needsupdate == 0: return @@ -385,6 +383,22 @@ def submodules_update(gitmodules, root_dir, requiredlist, force): else: print(f"{name:>20} up to date.") +def local_mods_output(): + text = '''\ + The submodules labeled with 'M' above are not in a clean state. + The following are options for how to proceed: + (1) Go into each submodule which is not in a clean state and issue a 'git status' + Either revert or commit your changes so that the submodule is in a clean state. + (2) use the --force option to git-fleximod + (3) you can name the particular submodules to update using the git-fleximod command line + (4) As a last resort you can remove the submodule (via 'rm -fr [directory]') + then rerun git-fleximod update. +''' + print(text) + + + + # checkout is done by update if required so this function may be depricated def submodules_checkout(gitmodules, root_dir, requiredlist, force=False): """ @@ -403,9 +417,7 @@ def submodules_checkout(gitmodules, root_dir, requiredlist, force=False): print("") _, localmods, needsupdate = submodules_status(gitmodules, root_dir) if localmods and not force: - print( - "Repository has local mods, cowardly refusing to continue, fix issues or use --force to override" - ) + local_mods_output() return if not needsupdate: return @@ -512,7 +524,11 @@ def main(): if action == "update": submodules_update(gitmodules, root_dir, fxrequired, force) elif action == "status": - submodules_status(gitmodules, root_dir, toplevel=True) + tfails, lmods, updates = submodules_status(gitmodules, root_dir, toplevel=True) + 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) else: From b4e07beb2fef9e4c62514a9b7fe4b86e99e043b2 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Thu, 28 Mar 2024 13:37:26 -0600 Subject: [PATCH 116/159] fixes issue with ssh access to github (should not be required) --- git_fleximod/git_fleximod.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index ebb719d4..a0d3a761 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -185,7 +185,6 @@ def single_submodule_checkout( if os.path.exists(os.path.join(repodir, ".git")): logger.info("Submodule {} already checked out".format(name)) repo_exists = True - # Look for a .gitmodules file in the newly checkedout repo if not repo_exists and url: # ssh urls cause problems for those who dont have git accounts with ssh keys defined @@ -209,7 +208,11 @@ def single_submodule_checkout( rootdotgit = line[8:].rstrip() newpath = os.path.abspath(os.path.join(root, rootdotgit, "modules", name)) - shutil.move(os.path.join(repodir, ".git"), newpath) + if os.path.exists(newpath): + shutil.rmtree(os.path.join(repodir,".git")) + else: + shutil.move(os.path.join(repodir, ".git"), newpath) + with open(os.path.join(repodir, ".git"), "w") as f: f.write("gitdir: " + os.path.relpath(newpath, start=repodir)) @@ -252,6 +255,7 @@ def submodules_status(gitmodules, root_dir, toplevel=False): rootgit = GitInterface(root_dir, logger) # submodule commands use path, not name url = gitmodules.get(name, "url") + url = url.replace("git@github.com:", "https://github.com/") tags = rootgit.git_operation("ls-remote", "--tags", url) atag = None needsupdate += 1 @@ -445,7 +449,6 @@ def submodules_checkout(gitmodules, root_dir, requiredlist, force=False): logger.debug( "Calling submodule_checkout({},{},{})".format(root_dir, name, path) ) - single_submodule_checkout( root_dir, name, path, url=url, tag=fxtag, force=force, optional = "AlwaysOptional" in requiredlist From 3a9201dce1a482de86d7a00e6958cd8ce310b4ef Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Thu, 28 Mar 2024 13:59:06 -0600 Subject: [PATCH 117/159] better error handling for not a tag --- git_fleximod/git_fleximod.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index a0d3a761..8e479fc3 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -227,9 +227,7 @@ def single_submodule_checkout( if optional: requiredlist.append("AlwaysOptional") submodules_checkout(gitmodules, repodir, requiredlist, force=force) - if os.path.exists(os.path.join(repodir, ".git")): - print(f"Successfully checked out {name:>20}") - else: + if not os.path.exists(os.path.join(repodir, ".git")): utils.fatal_error(f"Failed to checkout {name} {repo_exists} {tmpurl} {repodir} {path}") if tmpurl: @@ -380,8 +378,11 @@ def submodules_update(gitmodules, root_dir, requiredlist, force): git.git_operation("fetch", newremote, "--tags") atag = git.git_operation("describe", "--tags", "--always").rstrip() if fxtag and fxtag != atag: - print(f"{name:>20} updated to {fxtag}") - git.git_operation("checkout", fxtag) + try: + git.git_operation("checkout", fxtag) + print(f"{name:>20} updated to {fxtag}") + except Exception as error: + print(error) elif not fxtag: print(f"No fxtag found for submodule {name:>20}") else: From ad6b84ee19be4983561fbfff5d9d9abe24fda74e Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Thu, 28 Mar 2024 14:15:45 -0600 Subject: [PATCH 118/159] Bump to 0.7.3 --- git_fleximod/cli.py | 2 +- pyproject.toml | 2 +- tbump.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/git_fleximod/cli.py b/git_fleximod/cli.py index f9cd67cf..169a1b33 100644 --- a/git_fleximod/cli.py +++ b/git_fleximod/cli.py @@ -1,7 +1,7 @@ from pathlib import Path import argparse -__version__ = "0.7.0" +__version__ = "0.7.3" def find_root_dir(filename=".git"): d = Path.cwd() diff --git a/pyproject.toml b/pyproject.toml index 212b014d..bb4d89a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "git-fleximod" -version = "0.7.0" +version = "0.7.3" description = "Extended support for git-submodule and git-sparse-checkout" authors = ["Jim Edwards "] maintainers = ["Jim Edwards "] diff --git a/tbump.toml b/tbump.toml index 568226df..7338278f 100644 --- a/tbump.toml +++ b/tbump.toml @@ -2,7 +2,7 @@ github_url = "https://github.com/jedwards4b/git-fleximod/" [version] -current = "0.7.0" +current = "0.7.3" # Example of a semver regexp. # Make sure this matches current_version before From fd0ce7ad12f73b7932b98c6531b127767a7a2a19 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Thu, 28 Mar 2024 14:40:25 -0600 Subject: [PATCH 119/159] set python 3.7 as min version requirement --- git_fleximod/git_fleximod.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index 8e479fc3..3c0ae076 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -1,5 +1,9 @@ #!/usr/bin/env python import sys +MIN_PYTHON = (3, 7) +if sys.version_info < MIN_PYTHON: + sys.exit("Python %s.%s or later is required."%MIN_PYTHON) + import os import shutil import logging From ca419c61c810d57f0f7b4ab0de165ad30e28360d Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Fri, 29 Mar 2024 14:53:16 -0600 Subject: [PATCH 120/159] rename fxurl --- git_fleximod/git_fleximod.py | 165 ++++++++++++++++++++++------------- 1 file changed, 105 insertions(+), 60 deletions(-) diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index 3c0ae076..e4e18ee2 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -1,8 +1,9 @@ #!/usr/bin/env python import sys + MIN_PYTHON = (3, 7) if sys.version_info < MIN_PYTHON: - sys.exit("Python %s.%s or later is required."%MIN_PYTHON) + sys.exit("Python %s.%s or later is required." % MIN_PYTHON) import os import shutil @@ -16,8 +17,11 @@ # logger variable is global logger = None + + def fxrequired_allowed_values(): - return ['ToplevelRequired', 'ToplevelOptional', 'AlwaysRequired', 'AlwaysOptional'] + return ["ToplevelRequired", "ToplevelOptional", "AlwaysRequired", "AlwaysOptional"] + def commandline_arguments(args=None): parser = cli.get_parser() @@ -29,7 +33,12 @@ def commandline_arguments(args=None): # explicitly listing a component overrides the optional flag if options.optional or options.components: - fxrequired = ["ToplevelRequired", "ToplevelOptional", "AlwaysRequired", "AlwaysOptional"] + fxrequired = [ + "ToplevelRequired", + "ToplevelOptional", + "AlwaysRequired", + "AlwaysOptional", + ] else: fxrequired = ["ToplevelRequired", "AlwaysRequired"] @@ -68,16 +77,14 @@ def commandline_arguments(args=None): ) -def submodule_sparse_checkout( - root_dir, name, url, path, sparsefile, tag="master" -): +def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master"): """ This function performs a sparse checkout of a git submodule. It does so by first creating the .git/info/sparse-checkout fileq in the submodule and then checking out the desired tag. If the submodule is already checked out, it will not be checked out again. Creating the sparse-checkout file first prevents the entire submodule from being checked out and then removed. This is important - because the submodule may have a large number of files and checking out the entire submodule and then removing it would be time + because the submodule may have a large number of files and checking out the entire submodule and then removing it would be time and disk space consuming. - + Parameters: root_dir (str): The root directory for the git operation. name (str): The name of the submodule. @@ -96,10 +103,10 @@ def submodule_sparse_checkout( gitroot = superroot else: gitroot = root_dir - assert(os.path.isdir(os.path.join(gitroot,".git"))) + assert os.path.isdir(os.path.join(gitroot, ".git")) # first create the module directory - if not os.path.isdir(os.path.join(root_dir,path)): - os.makedirs(os.path.join(root_dir,path)) + if not os.path.isdir(os.path.join(root_dir, path)): + os.makedirs(os.path.join(root_dir, path)) # initialize a new git repo and set the sparse checkout flag sprep_repo = os.path.join(root_dir, path) @@ -120,25 +127,31 @@ def submodule_sparse_checkout( # set the repository remote - logger.info("Setting remote origin in {}/{}".format(root_dir,path)) + logger.info("Setting remote origin in {}/{}".format(root_dir, path)) status = sprepo_git.git_operation("remote", "-v") if url not in status: sprepo_git.git_operation("remote", "add", "origin", url) - - topgit = os.path.join(gitroot,".git") - + + topgit = os.path.join(gitroot, ".git") + if gitroot != root_dir and os.path.isfile(os.path.join(root_dir, ".git")): with open(os.path.join(root_dir, ".git")) as f: - gitpath = os.path.relpath(os.path.join(root_dir, f.read().split()[1]), start=os.path.join(root_dir,path)) + gitpath = os.path.relpath( + os.path.join(root_dir, f.read().split()[1]), + start=os.path.join(root_dir, path), + ) topgit = os.path.join(gitpath, "modules") else: - topgit = os.path.relpath(os.path.join(root_dir, ".git", "modules"), start=os.path.join(root_dir,path)) - + topgit = os.path.relpath( + os.path.join(root_dir, ".git", "modules"), + start=os.path.join(root_dir, path), + ) + with utils.pushd(sprep_repo): if not os.path.isdir(topgit): os.makedirs(topgit) topgit += os.sep + name - + if os.path.isdir(os.path.join(root_dir, path, ".git")): with utils.pushd(sprep_repo): shutil.move(".git", topgit) @@ -147,7 +160,9 @@ def submodule_sparse_checkout( # assert(os.path.isdir(os.path.relpath(topgit, start=sprep_repo))) gitsparse = os.path.abspath(os.path.join(topgit, "info", "sparse-checkout")) if os.path.isfile(gitsparse): - logger.warning("submodule {} is already initialized {}".format(name, topgit)) + logger.warning( + "submodule {} is already initialized {}".format(name, topgit) + ) return with utils.pushd(sprep_repo): @@ -156,10 +171,11 @@ def submodule_sparse_checkout( # Finally checkout the repo sprepo_git.git_operation("fetch", "origin", "--tags") sprepo_git.git_operation("checkout", tag) - + print(f"Successfully checked out {name:>20} at {tag}") - rgit.config_set_value(f'submodule "{name}"',"active","true") - rgit.config_set_value(f'submodule "{name}"',"url",url) + rgit.config_set_value(f'submodule "{name}"', "active", "true") + rgit.config_set_value(f'submodule "{name}"', "url", url) + def single_submodule_checkout( root, name, path, url=None, tag=None, force=False, optional=False @@ -182,7 +198,7 @@ def single_submodule_checkout( # function implementation... git = GitInterface(root, logger) repodir = os.path.join(root, path) - logger.info("Checkout {} into {}/{}".format(name,root,path)) + logger.info("Checkout {} into {}/{}".format(name, root, path)) # if url is provided update to the new url tmpurl = None repo_exists = False @@ -213,7 +229,7 @@ def single_submodule_checkout( newpath = os.path.abspath(os.path.join(root, rootdotgit, "modules", name)) if os.path.exists(newpath): - shutil.rmtree(os.path.join(repodir,".git")) + shutil.rmtree(os.path.join(repodir, ".git")) else: shutil.move(os.path.join(repodir, ".git"), newpath) @@ -232,7 +248,9 @@ def single_submodule_checkout( requiredlist.append("AlwaysOptional") submodules_checkout(gitmodules, repodir, requiredlist, force=force) if not os.path.exists(os.path.join(repodir, ".git")): - utils.fatal_error(f"Failed to checkout {name} {repo_exists} {tmpurl} {repodir} {path}") + utils.fatal_error( + f"Failed to checkout {name} {repo_exists} {tmpurl} {repodir} {path}" + ) if tmpurl: print(git.git_operation("restore", ".gitmodules")) @@ -248,7 +266,7 @@ def submodules_status(gitmodules, root_dir, toplevel=False): path = gitmodules.get(name, "path") tag = gitmodules.get(name, "fxtag") required = gitmodules.get(name, "fxrequired") - level = required and "Toplevel" in required + level = required and "Toplevel" in required if not path: utils.fatal_error("No path found in .gitmodules for {}".format(name)) newpath = os.path.join(root_dir, path) @@ -270,8 +288,10 @@ def submodules_status(gitmodules, root_dir, toplevel=False): if tag and tag == atag: print(f"e {name:>20} not checked out, aligned at tag {tag}") elif tag: - ahash = rootgit.git_operation("submodule", "status", "{}".format(path)).rstrip() - ahash = ahash[1:len(tag)+1] + ahash = rootgit.git_operation( + "submodule", "status", "{}".format(path) + ).rstrip() + ahash = ahash[1 : len(tag) + 1] if tag == ahash: print(f"e {name:>20} not checked out, aligned at hash {ahash}") else: @@ -289,10 +309,12 @@ def submodules_status(gitmodules, root_dir, toplevel=False): ahash = git.git_operation("status").partition("\n")[0].split()[-1] if tag and atag == tag: print(f" {name:>20} at tag {tag}") - elif tag and ahash[:len(tag)] == tag: + elif tag and ahash[: len(tag)] == tag: print(f" {name:>20} at hash {ahash}") elif tag: - print(f"s {name:>20} {atag} {ahash} is out of sync with .gitmodules {tag}") + print( + f"s {name:>20} {atag} {ahash} is out of sync with .gitmodules {tag}" + ) testfails += 1 needsupdate += 1 else: @@ -317,21 +339,29 @@ def submodules_update(gitmodules, root_dir, requiredlist, force): return if needsupdate == 0: return - + for name in gitmodules.sections(): fxtag = gitmodules.get(name, "fxtag") path = gitmodules.get(name, "path") url = gitmodules.get(name, "url") - logger.info("name={} path={} url={} fxtag={} requiredlist={}".format(name,os.path.join(root_dir, path), url, fxtag, requiredlist)) + logger.info( + "name={} path={} url={} fxtag={} requiredlist={}".format( + name, os.path.join(root_dir, path), url, fxtag, requiredlist + ) + ) # if not os.path.exists(os.path.join(root_dir,path, ".git")): fxrequired = gitmodules.get(name, "fxrequired") - assert(fxrequired in fxrequired_allowed_values()) + assert fxrequired in fxrequired_allowed_values() rgit = GitInterface(root_dir, logger) superroot = rgit.git_operation("rev-parse", "--show-superproject-working-tree") - + fxsparse = gitmodules.get(name, "fxsparse") - - if fxrequired and (superroot and "Toplevel" in fxrequired) or fxrequired not in requiredlist: + + if ( + fxrequired + and (superroot and "Toplevel" in fxrequired) + or fxrequired not in requiredlist + ): if "ToplevelOptional" == fxrequired: print("Skipping optional component {}".format(name)) continue @@ -341,20 +371,24 @@ def submodules_update(gitmodules, root_dir, requiredlist, force): root_dir, name, url, path, fxsparse, fxtag ) ) - submodule_sparse_checkout( - root_dir, name, url, path, fxsparse, tag=fxtag - ) + submodule_sparse_checkout(root_dir, name, url, path, fxsparse, tag=fxtag) else: logger.info( - "Calling submodule_checkout({},{},{},{})".format(root_dir, name, path,url) + "Calling submodule_checkout({},{},{},{})".format( + root_dir, name, path, url + ) ) - + single_submodule_checkout( - root_dir, name, path, url=url, tag=fxtag, force=force, - optional=("AlwaysOptional" in requiredlist) + root_dir, + name, + path, + url=url, + tag=fxtag, + force=force, + optional=("AlwaysOptional" in requiredlist), ) - if os.path.exists(os.path.join(path, ".git")): submoddir = os.path.join(root_dir, path) with utils.pushd(submoddir): @@ -392,8 +426,9 @@ def submodules_update(gitmodules, root_dir, requiredlist, force): else: print(f"{name:>20} up to date.") + def local_mods_output(): - text = '''\ + text = """\ The submodules labeled with 'M' above are not in a clean state. The following are options for how to proceed: (1) Go into each submodule which is not in a clean state and issue a 'git status' @@ -402,12 +437,10 @@ def local_mods_output(): (3) you can name the particular submodules to update using the git-fleximod command line (4) As a last resort you can remove the submodule (via 'rm -fr [directory]') then rerun git-fleximod update. -''' +""" print(text) - - - + # checkout is done by update if required so this function may be depricated def submodules_checkout(gitmodules, root_dir, requiredlist, force=False): """ @@ -447,18 +480,22 @@ def submodules_checkout(gitmodules, root_dir, requiredlist, force=False): root_dir, name, url, path, fxsparse, fxtag ) ) - submodule_sparse_checkout( - root_dir, name, url, path, fxsparse, tag=fxtag - ) + submodule_sparse_checkout(root_dir, name, url, path, fxsparse, tag=fxtag) else: logger.debug( "Calling submodule_checkout({},{},{})".format(root_dir, name, path) ) single_submodule_checkout( - root_dir, name, path, url=url, tag=fxtag, force=force, - optional = "AlwaysOptional" in requiredlist + root_dir, + name, + path, + url=url, + tag=fxtag, + force=force, + optional="AlwaysOptional" in requiredlist, ) + def submodules_test(gitmodules, root_dir): """ This function tests the git submodules based on the provided parameters. @@ -481,10 +518,12 @@ def submodules_test(gitmodules, root_dir): # and that sparse checkout files exist for name in gitmodules.sections(): url = gitmodules.get(name, "url") - fxurl = gitmodules.get(name, "fxurl") + fxurl = gitmodules.get(name, "fxDONOTMODIFYurl") fxsparse = gitmodules.get(name, "fxsparse") path = gitmodules.get(name, "path") - if not fxurl or url != fxurl: + fxurl = fxurl[:-4] if fxurl.endswith(".git") else fxurl + url = url[:-4] if url.endswith(".git") else url + if not fxurl or url.lower() != fxurl.lower(): print(f"{name:>20} url {url} not in sync with required {fxurl}") testfails += 1 if fxsparse and not os.path.isfile(os.path.join(root_dir, path, fxsparse)): @@ -518,7 +557,11 @@ def main(): ) root_dir = os.path.dirname(file_path) - logger.info("root_dir is {} includelist={} excludelist={}".format(root_dir, includelist, excludelist)) + logger.info( + "root_dir is {} includelist={} excludelist={}".format( + root_dir, includelist, excludelist + ) + ) gitmodules = GitModules( logger, confpath=root_dir, @@ -534,9 +577,11 @@ def main(): elif action == "status": tfails, lmods, updates = submodules_status(gitmodules, root_dir, toplevel=True) if tfails + lmods + updates > 0: - print(f" testfails = {tfails}, local mods = {lmods}, needs updates {updates}\n") + print( + f" testfails = {tfails}, local mods = {lmods}, needs updates {updates}\n" + ) if lmods > 0: - local_mods_output() + local_mods_output() elif action == "test": retval = submodules_test(gitmodules, root_dir) else: From 152bad98fecc769308c782db5c80d95e51b33f61 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Wed, 3 Apr 2024 09:05:06 -0600 Subject: [PATCH 121/159] fix tests on izumi --- git_fleximod/git_fleximod.py | 4 ++-- git_fleximod/utils.py | 2 +- tests/conftest.py | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index e4e18ee2..7957001b 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -100,9 +100,9 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master rgit = GitInterface(root_dir, logger) superroot = rgit.git_operation("rev-parse", "--show-superproject-working-tree") if superroot: - gitroot = superroot + gitroot = superroot.strip() else: - gitroot = root_dir + gitroot = root_dir.strip() assert os.path.isdir(os.path.join(gitroot, ".git")) # first create the module directory if not os.path.isdir(os.path.join(root_dir, path)): diff --git a/git_fleximod/utils.py b/git_fleximod/utils.py index f0753367..7cc1de38 100644 --- a/git_fleximod/utils.py +++ b/git_fleximod/utils.py @@ -272,7 +272,7 @@ def execute_subprocess(commands, status_to_caller=False, output_to_caller=False) cwd = os.getcwd() msg = "In directory: {0}\nexecute_subprocess running command:".format(cwd) logging.info(msg) - commands_str = " ".join(commands) + commands_str = " ".join(str(element) for element in commands) logging.info(commands_str) return_to_caller = status_to_caller or output_to_caller status = -1 diff --git a/tests/conftest.py b/tests/conftest.py index 5fed907c..942a0efb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -25,7 +25,7 @@ def logger(): path = modules/test url = https://github.com/ESMCI/mpi-serial.git fxtag = MPIserial_2.4.0 - fxurl = https://github.com/ESMCI/mpi-serial.git + fxDONOTUSEurl = https://github.com/ESMCI/mpi-serial.git fxrequired = ToplevelRequired """}, {"subrepo_path": "modules/test_optional", @@ -39,7 +39,7 @@ def logger(): path = modules/test_optional url = https://github.com/ESMCI/mpi-serial.git fxtag = MPIserial_2.4.0 - fxurl = https://github.com/ESMCI/mpi-serial.git + fxDONOTUSEurl = https://github.com/ESMCI/mpi-serial.git fxrequired = ToplevelOptional """}, {"subrepo_path": "modules/test_alwaysoptional", @@ -53,7 +53,7 @@ def logger(): path = modules/test_alwaysoptional url = https://github.com/ESMCI/mpi-serial.git fxtag = e5cf35c - fxurl = https://github.com/ESMCI/mpi-serial.git + fxDONOTUSEurl = https://github.com/ESMCI/mpi-serial.git fxrequired = AlwaysOptional """}, {"subrepo_path": "modules/test_sparse", @@ -67,7 +67,7 @@ def logger(): path = modules/test_sparse url = https://github.com/ESMCI/mpi-serial.git fxtag = MPIserial_2.5.0 - fxurl = https://github.com/ESMCI/mpi-serial.git + fxDONOTUSEurl = https://github.com/ESMCI/mpi-serial.git fxrequired = AlwaysRequired fxsparse = ../.sparse_file_list """}, From 5a1e5f64b813208fb6c62dfdab6e466a5b4ef8d6 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Wed, 3 Apr 2024 09:50:57 -0600 Subject: [PATCH 122/159] fix issue in metoflexi --- git_fleximod/metoflexi.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/git_fleximod/metoflexi.py b/git_fleximod/metoflexi.py index 1a4f8421..cc347db2 100755 --- a/git_fleximod/metoflexi.py +++ b/git_fleximod/metoflexi.py @@ -17,7 +17,7 @@ def find_root_dir(filename=".git"): while d != root: attempt = d / filename if attempt.is_dir(): - return attempt + return d d = d.parent return None @@ -146,7 +146,7 @@ def translate_single_repo(self, section, tag, url, path, efile, hash_, sparse, p if hash_: self.gitmodules.set(section, "fxtag", hash_) - self.gitmodules.set(section, "fxurl", url) + self.gitmodules.set(section, "fxDONOTUSEurl", url) if sparse: self.gitmodules.set(section, "fxsparse", sparse) self.gitmodules.set(section, "fxrequired", "ToplevelRequired") @@ -187,7 +187,7 @@ def translate_single_repo(self, section, tag, url, path, efile, hash_, sparse, p if hash_: self.gitmodules.set(section, "fxtag", hash_) - self.gitmodules.set(section, "fxurl", url) + self.gitmodules.set(section, "fxDONOTUSEurl", url) if sparse: self.gitmodules.set(section, "fxsparse", sparse) self.gitmodules.set(section, "fxrequired", "ToplevelRequired") From 5398ef43fc6cdacdc894b7e6567bc9fb28b26da6 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Tue, 23 Apr 2024 15:18:24 -0600 Subject: [PATCH 123/159] if submodule does not exist add it --- git_fleximod/git_fleximod.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index 7957001b..adf575f9 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -235,10 +235,13 @@ def single_submodule_checkout( with open(os.path.join(repodir, ".git"), "w") as f: f.write("gitdir: " + os.path.relpath(newpath, start=repodir)) + + if not os.path.exists(repodir): + git.git_operation("submodule", "add", "--name", name, "--", url, path) if not repo_exists or not tmpurl: - logger.debug(git.git_operation("submodule", "update", "--init", "--", path)) - + git.git_operation("submodule", "update", "--init", "--", path) + if os.path.exists(os.path.join(repodir, ".gitmodules")): # recursively handle this checkout print(f"Recursively checking out submodules of {name}") @@ -311,6 +314,8 @@ def submodules_status(gitmodules, root_dir, toplevel=False): print(f" {name:>20} at tag {tag}") elif tag and ahash[: len(tag)] == tag: print(f" {name:>20} at hash {ahash}") + elif atag == ahash: + print(f" {name:>20} at hash {ahash}") elif tag: print( f"s {name:>20} {atag} {ahash} is out of sync with .gitmodules {tag}" From 46a74ac7f75b5d086fb1f3b3ffb117ae76cc46d4 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Tue, 23 Apr 2024 16:44:14 -0600 Subject: [PATCH 124/159] create parent if it doesnt exist --- git_fleximod/git_fleximod.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index adf575f9..103cc82a 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -237,6 +237,9 @@ def single_submodule_checkout( f.write("gitdir: " + os.path.relpath(newpath, start=repodir)) if not os.path.exists(repodir): + parent = os.path.dirname(repodir) + if not os.path.isdir(parent): + os.makedirs(parent) git.git_operation("submodule", "add", "--name", name, "--", url, path) if not repo_exists or not tmpurl: From eb932b015072ba3ec8cc7fadf5a387058f79fc4c Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Tue, 23 Apr 2024 16:49:47 -0600 Subject: [PATCH 125/159] Bump to 0.7.4 --- git_fleximod/cli.py | 2 +- pyproject.toml | 2 +- tbump.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/git_fleximod/cli.py b/git_fleximod/cli.py index 169a1b33..1fb959da 100644 --- a/git_fleximod/cli.py +++ b/git_fleximod/cli.py @@ -1,7 +1,7 @@ from pathlib import Path import argparse -__version__ = "0.7.3" +__version__ = "0.7.4" def find_root_dir(filename=".git"): d = Path.cwd() diff --git a/pyproject.toml b/pyproject.toml index bb4d89a3..2484552e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "git-fleximod" -version = "0.7.3" +version = "0.7.4" description = "Extended support for git-submodule and git-sparse-checkout" authors = ["Jim Edwards "] maintainers = ["Jim Edwards "] diff --git a/tbump.toml b/tbump.toml index 7338278f..d4b8eaee 100644 --- a/tbump.toml +++ b/tbump.toml @@ -2,7 +2,7 @@ github_url = "https://github.com/jedwards4b/git-fleximod/" [version] -current = "0.7.3" +current = "0.7.4" # Example of a semver regexp. # Make sure this matches current_version before From 94121295e57a1d6e713b8e3ba84261d9e9cfebe6 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Tue, 14 May 2024 13:48:01 -0600 Subject: [PATCH 126/159] update documentation --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f8afb40b..d1ef632f 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Git-fleximod is a Python-based tool that extends Git's submodule and sparse chec status: Display the status of submodules. update: Update submodules to the tag indicated in .gitmodules variable fxtag. test: Make sure that fxtags and submodule hashes are consistant, - make sure that official urls (as defined by fxurl) are set + make sure that official urls (as defined by fxDONOTUSEurl) are set make sure that fxtags are defined for all submodules Additional Options: See git fleximod --help for more details. @@ -34,8 +34,8 @@ Git-fleximod is a Python-based tool that extends Git's submodule and sparse chec - AlwaysRequired: Always required (always checked out). - AlwaysOptional: Always optional (checked out with --optional flag). fxsparse: Enable sparse checkout for a submodule, pointing to a file containing sparse checkout paths. - fxurl: This is the url used in the test subcommand to assure that protected branches do not point to forks - **NOTE** the fxurl variable is only used to identify the official project repository and should not be + fxDONOTUSEurl: This is the url used in the test subcommand to assure that protected branches do not point to forks + **NOTE** the fxDONOTUSEurl variable is only used to identify the official project repository and should not be changed by users. Use the url variable to change to a fork if desired. ## Sparse Checkouts From 4aa073c3482ad30b6952834ccc5eb99778f88c09 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Fri, 31 May 2024 09:56:18 -0600 Subject: [PATCH 127/159] update find_root_dir to work with git worktree --- git_fleximod/cli.py | 24 +++++++++++++++++------- git_fleximod/git_fleximod.py | 10 +++++++--- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/git_fleximod/cli.py b/git_fleximod/cli.py index 1fb959da..3eb90870 100644 --- a/git_fleximod/cli.py +++ b/git_fleximod/cli.py @@ -1,17 +1,27 @@ from pathlib import Path import argparse +from git_fleximod import utils __version__ = "0.7.4" -def find_root_dir(filename=".git"): +def find_root_dir(filename=".gitmodules"): + """ finds the highest directory in tree + which contains a file called filename """ d = Path.cwd() root = Path(d.root) - while d != root: - attempt = d / filename - if attempt.is_dir(): - return attempt - d = d.parent - return None + dirlist = [] + dl = d + while dl != root: + dirlist.append(dl) + dl = dl.parent + dirlist.append(root) + dirlist.reverse() + + for dl in dirlist: + attempt = dl / filename + if attempt.is_file(): + return dl + utils.fatal_error("No .gitmodules found in directory tree") def get_parser(): diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index 103cc82a..f080513a 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -312,7 +312,11 @@ def submodules_status(gitmodules, root_dir, toplevel=False): with utils.pushd(newpath): git = GitInterface(newpath, logger) atag = git.git_operation("describe", "--tags", "--always").rstrip() - ahash = git.git_operation("status").partition("\n")[0].split()[-1] + part = git.git_operation("status").partition("\n")[0] + # fake hash to initialize + ahash = "xxxx" + if part: + ahash = part.split()[-1] if tag and atag == tag: print(f" {name:>20} at tag {tag}") elif tag and ahash[: len(tag)] == tag: @@ -554,8 +558,8 @@ def main(): global logger logger = logging.getLogger(__name__) - logger.info("action is {}".format(action)) - + logger.info("action is {} root_dir={} file_name={}".format(action, root_dir, file_name)) + if not os.path.isfile(os.path.join(root_dir, file_name)): file_path = utils.find_upwards(root_dir, file_name) From 1fddaac99d83852380738c44ca33f615898123ca Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Fri, 31 May 2024 10:09:31 -0600 Subject: [PATCH 128/159] return str not Path --- License | 2 +- README.md | 2 -- git_fleximod/cli.py | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/License b/License index 2c6fe768..88bc2251 100644 --- a/License +++ b/License @@ -1,4 +1,4 @@ -Copyright 2024 National Center for Atmospheric Sciences (NCAR) +Copyright 2024 NSF National Center for Atmospheric Sciences (NCAR) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/README.md b/README.md index d1ef632f..53917da4 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,6 @@ Git-fleximod is a Python-based tool that extends Git's submodule and sparse chec ## Installation -#TODO Install using pip: -# pip install git-fleximod If you choose to locate git-fleximod in your path you can access it via command: git fleximod ## Usage diff --git a/git_fleximod/cli.py b/git_fleximod/cli.py index 3eb90870..d24d07c5 100644 --- a/git_fleximod/cli.py +++ b/git_fleximod/cli.py @@ -20,7 +20,7 @@ def find_root_dir(filename=".gitmodules"): for dl in dirlist: attempt = dl / filename if attempt.is_file(): - return dl + return str(dl) utils.fatal_error("No .gitmodules found in directory tree") From a354b0528228ac89ce103b3b42b2a203fe495ba5 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Fri, 31 May 2024 10:14:11 -0600 Subject: [PATCH 129/159] Bump to 0.7.5 --- git_fleximod/cli.py | 2 +- pyproject.toml | 2 +- tbump.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/git_fleximod/cli.py b/git_fleximod/cli.py index d24d07c5..4c3fb1a8 100644 --- a/git_fleximod/cli.py +++ b/git_fleximod/cli.py @@ -2,7 +2,7 @@ import argparse from git_fleximod import utils -__version__ = "0.7.4" +__version__ = "0.7.5" def find_root_dir(filename=".gitmodules"): """ finds the highest directory in tree diff --git a/pyproject.toml b/pyproject.toml index 2484552e..ac1684ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "git-fleximod" -version = "0.7.4" +version = "0.7.5" description = "Extended support for git-submodule and git-sparse-checkout" authors = ["Jim Edwards "] maintainers = ["Jim Edwards "] diff --git a/tbump.toml b/tbump.toml index d4b8eaee..e644fc4d 100644 --- a/tbump.toml +++ b/tbump.toml @@ -2,7 +2,7 @@ github_url = "https://github.com/jedwards4b/git-fleximod/" [version] -current = "0.7.4" +current = "0.7.5" # Example of a semver regexp. # Make sure this matches current_version before From 00b7b38174360d20b9c2bdeeb8d09f1ac0656fca Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Sat, 1 Jun 2024 11:41:49 -0600 Subject: [PATCH 130/159] some fixes when modifying url --- git_fleximod/git_fleximod.py | 49 ++++++++++++++++++++---------------- git_fleximod/gitmodules.py | 2 +- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index f080513a..a003c0da 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -263,6 +263,21 @@ def single_submodule_checkout( return +def add_remote(git, url): + remotes = git.git_operation("remote", "-v") + newremote = "newremote.00" + if url in remotes: + for line in remotes: + if url in line and "fetch" in line: + newremote = line.split()[0] + break + else: + i = 0 + while "newremote" in remotes: + i = i + 1 + newremote = f"newremote.{i:02d}" + git.git_operation("remote", "add", newremote, url) + return newremote def submodules_status(gitmodules, root_dir, toplevel=False): testfails = 0 @@ -271,6 +286,7 @@ def submodules_status(gitmodules, root_dir, toplevel=False): for name in gitmodules.sections(): path = gitmodules.get(name, "path") tag = gitmodules.get(name, "fxtag") + url = gitmodules.get(name, "url") required = gitmodules.get(name, "fxrequired") level = required and "Toplevel" in required if not path: @@ -280,7 +296,6 @@ def submodules_status(gitmodules, root_dir, toplevel=False): if not os.path.exists(os.path.join(newpath, ".git")): rootgit = GitInterface(root_dir, logger) # submodule commands use path, not name - url = gitmodules.get(name, "url") url = url.replace("git@github.com:", "https://github.com/") tags = rootgit.git_operation("ls-remote", "--tags", url) atag = None @@ -312,11 +327,11 @@ def submodules_status(gitmodules, root_dir, toplevel=False): with utils.pushd(newpath): git = GitInterface(newpath, logger) atag = git.git_operation("describe", "--tags", "--always").rstrip() - part = git.git_operation("status").partition("\n")[0] - # fake hash to initialize - ahash = "xxxx" - if part: - ahash = part.split()[-1] + ahash = git.git_operation("rev-list", "HEAD").partition("\n")[0] + rurl = git.git_operation("ls-remote","--get-url").rstrip() + if rurl != url: + remote = add_remote(git, url) + git.git_operation("fetch", remote) if tag and atag == tag: print(f" {name:>20} at tag {tag}") elif tag and ahash[: len(tag)] == tag: @@ -335,7 +350,7 @@ def submodules_status(gitmodules, root_dir, toplevel=False): ) testfails += 1 - status = git.git_operation("status", "--ignore-submodules") + status = git.git_operation("status", "--ignore-submodules", "-uno") if "nothing to commit" not in status: localmods = localmods + 1 print("M" + textwrap.indent(status, " ")) @@ -357,11 +372,11 @@ def submodules_update(gitmodules, root_dir, requiredlist, force): path = gitmodules.get(name, "path") url = gitmodules.get(name, "url") logger.info( - "name={} path={} url={} fxtag={} requiredlist={}".format( + "name={} path={} url={} fxtag={} requiredlist={} ".format( name, os.path.join(root_dir, path), url, fxtag, requiredlist ) ) - # if not os.path.exists(os.path.join(root_dir,path, ".git")): + fxrequired = gitmodules.get(name, "fxrequired") assert fxrequired in fxrequired_allowed_values() rgit = GitInterface(root_dir, logger) @@ -409,19 +424,7 @@ def submodules_update(gitmodules, root_dir, requiredlist, force): upstream = git.git_operation("ls-remote", "--get-url").rstrip() newremote = "origin" if upstream != url: - # TODO - this needs to be a unique name - remotes = git.git_operation("remote", "-v") - if url in remotes: - for line in remotes: - if url in line and "fetch" in line: - newremote = line.split()[0] - break - else: - i = 0 - while newremote in remotes: - i = i + 1 - newremote = f"newremote.{i:02d}" - git.git_operation("remote", "add", newremote, url) + add_remote(git, url) tags = git.git_operation("tag", "-l") if fxtag and fxtag not in tags: @@ -439,6 +442,8 @@ def submodules_update(gitmodules, root_dir, requiredlist, force): print(f"{name:>20} up to date.") + + def local_mods_output(): text = """\ The submodules labeled with 'M' above are not in a clean state. diff --git a/git_fleximod/gitmodules.py b/git_fleximod/gitmodules.py index 68c82d06..7e4e0539 100644 --- a/git_fleximod/gitmodules.py +++ b/git_fleximod/gitmodules.py @@ -59,7 +59,7 @@ def get(self, name, option, raw=False, vars=None, fallback=None): Uses the parent class's get method to access the value. Handles potential errors if the section or option doesn't exist. """ - self.logger.debug("get called {} {}".format(name, option)) + self.logger.debug("git get called {} {}".format(name, option)) section = f'submodule "{name}"' try: return ConfigParser.get( From a34070be02d10071b544be3839f6c9ed79662665 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Sat, 1 Jun 2024 11:46:24 -0600 Subject: [PATCH 131/159] Bump to 0.7.6 --- git_fleximod/cli.py | 2 +- pyproject.toml | 2 +- tbump.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/git_fleximod/cli.py b/git_fleximod/cli.py index 4c3fb1a8..7d09abd8 100644 --- a/git_fleximod/cli.py +++ b/git_fleximod/cli.py @@ -2,7 +2,7 @@ import argparse from git_fleximod import utils -__version__ = "0.7.5" +__version__ = "0.7.6" def find_root_dir(filename=".gitmodules"): """ finds the highest directory in tree diff --git a/pyproject.toml b/pyproject.toml index ac1684ea..3c70c6c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "git-fleximod" -version = "0.7.5" +version = "0.7.6" description = "Extended support for git-submodule and git-sparse-checkout" authors = ["Jim Edwards "] maintainers = ["Jim Edwards "] diff --git a/tbump.toml b/tbump.toml index e644fc4d..3854682d 100644 --- a/tbump.toml +++ b/tbump.toml @@ -2,7 +2,7 @@ github_url = "https://github.com/jedwards4b/git-fleximod/" [version] -current = "0.7.5" +current = "0.7.6" # Example of a semver regexp. # Make sure this matches current_version before From c304487aff29425777e08fc5e009320b50282149 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Wed, 5 Jun 2024 11:36:26 -0600 Subject: [PATCH 132/159] update complex test --- git_fleximod/cli.py | 2 +- git_fleximod/git_fleximod.py | 8 +++++--- tests/test_d_complex.py | 8 ++++---- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/git_fleximod/cli.py b/git_fleximod/cli.py index 7d09abd8..d4498b84 100644 --- a/git_fleximod/cli.py +++ b/git_fleximod/cli.py @@ -21,7 +21,7 @@ def find_root_dir(filename=".gitmodules"): attempt = dl / filename if attempt.is_file(): return str(dl) - utils.fatal_error("No .gitmodules found in directory tree") + return None def get_parser(): diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index a003c0da..48a9b69f 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -565,10 +565,12 @@ def main(): logger.info("action is {} root_dir={} file_name={}".format(action, root_dir, file_name)) - if not os.path.isfile(os.path.join(root_dir, file_name)): - file_path = utils.find_upwards(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) - if file_path is None: + if root_dir is None or file_path is None: + root_dir = "." utils.fatal_error( "No {} found in {} or any of it's parents".format(file_name, root_dir) ) diff --git a/tests/test_d_complex.py b/tests/test_d_complex.py index fdce5162..62889f2c 100644 --- a/tests/test_d_complex.py +++ b/tests/test_d_complex.py @@ -7,7 +7,7 @@ def test_complex_checkout(git_fleximod, complex_repo, logger): assert("ToplevelOptional not checked out, aligned at tag v5.3.2" in status.stdout) assert("ToplevelRequired not checked out, aligned at tag MPIserial_2.5.0" in status.stdout) assert("AlwaysRequired not checked out, aligned at tag MPIserial_2.4.0" in status.stdout) - assert("Complex not checked out, aligned at tag testtag01" in status.stdout) + assert("Complex not checked out, aligned at tag testtag02" in status.stdout) assert("AlwaysOptional not checked out, aligned at tag MPIserial_2.3.0" in status.stdout) # This should checkout and update test_submodule and complex_sub @@ -18,7 +18,7 @@ def test_complex_checkout(git_fleximod, complex_repo, logger): assert("ToplevelOptional not checked out, aligned at tag v5.3.2" in status.stdout) assert("ToplevelRequired at tag MPIserial_2.5.0" in status.stdout) assert("AlwaysRequired at tag MPIserial_2.4.0" in status.stdout) - assert("Complex at tag testtag01" in status.stdout) + assert("Complex at tag testtag02" in status.stdout) # now check the complex_sub root = (complex_repo / "modules" / "complex") @@ -39,7 +39,7 @@ def test_complex_checkout(git_fleximod, complex_repo, logger): assert("ToplevelOptional at tag v5.3.2" in status.stdout) assert("ToplevelRequired at tag MPIserial_2.5.0" in status.stdout) assert("AlwaysRequired at tag MPIserial_2.4.0" in status.stdout) - assert("Complex at tag testtag01" in status.stdout) + assert("Complex at tag testtag02" in status.stdout) assert("AlwaysOptional not checked out, aligned at tag MPIserial_2.3.0" in status.stdout) @@ -51,7 +51,7 @@ def test_complex_checkout(git_fleximod, complex_repo, logger): assert("ToplevelOptional at tag v5.3.2" in status.stdout) assert("ToplevelRequired at tag MPIserial_2.5.0" in status.stdout) assert("AlwaysRequired at tag MPIserial_2.4.0" in status.stdout) - assert("Complex at tag testtag01" in status.stdout) + assert("Complex at tag testtag02" in status.stdout) assert("AlwaysOptional at tag MPIserial_2.3.0" in status.stdout) # now check the complex_sub From d95f5d61ba381fbef6274eae5547e08d09e1a10d Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Wed, 5 Jun 2024 14:08:19 -0600 Subject: [PATCH 133/159] fix status issue --- git_fleximod/git_fleximod.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index 48a9b69f..a847c9fc 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -298,15 +298,22 @@ def submodules_status(gitmodules, root_dir, toplevel=False): # submodule commands use path, not name url = url.replace("git@github.com:", "https://github.com/") tags = rootgit.git_operation("ls-remote", "--tags", url) + ahash = rootgit.git_operation("submodule","status",newpath).split()[0][1:] + hhash = None atag = None needsupdate += 1 if not toplevel and level: continue for htag in tags.split("\n"): - if tag and tag in htag: + if htag.endswith('^{}'): + htag = htag[:-3] + if not atag and ahash in htag: atag = (htag.split()[1])[10:] + if not hhash and htag.endswith(tag): + hhash = htag.split()[0] + if hhash and atag: break - if tag and tag == atag: + if tag and (ahash == hhash or atag == tag): print(f"e {name:>20} not checked out, aligned at tag {tag}") elif tag: ahash = rootgit.git_operation( From 0593149d3c106509daa1fbb235fe5b747e26c9a5 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Wed, 5 Jun 2024 14:17:46 -0600 Subject: [PATCH 134/159] one more improvement --- git_fleximod/git_fleximod.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index a847c9fc..b0a5a330 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -309,7 +309,7 @@ def submodules_status(gitmodules, root_dir, toplevel=False): htag = htag[:-3] if not atag and ahash in htag: atag = (htag.split()[1])[10:] - if not hhash and htag.endswith(tag): + if tag and not hhash and htag.endswith(tag): hhash = htag.split()[0] if hhash and atag: break From 3ff935b835d1793e0840f00230e323efc52fba76 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Wed, 5 Jun 2024 14:47:54 -0600 Subject: [PATCH 135/159] update tests --- git_fleximod/git_fleximod.py | 7 +++++-- tests/conftest.py | 4 ++-- tests/test_d_complex.py | 5 ++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index b0a5a330..ca5f9062 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -298,7 +298,10 @@ def submodules_status(gitmodules, root_dir, toplevel=False): # submodule commands use path, not name url = url.replace("git@github.com:", "https://github.com/") tags = rootgit.git_operation("ls-remote", "--tags", url) - ahash = rootgit.git_operation("submodule","status",newpath).split()[0][1:] + result = rootgit.git_operation("submodule","status",newpath).split() + ahash = None + if result: + ahash = result[0][1:] hhash = None atag = None needsupdate += 1 @@ -307,7 +310,7 @@ def submodules_status(gitmodules, root_dir, toplevel=False): for htag in tags.split("\n"): if htag.endswith('^{}'): htag = htag[:-3] - if not atag and ahash in htag: + if ahash and not atag and ahash in htag: atag = (htag.split()[1])[10:] if tag and not hhash and htag.endswith(tag): hhash = htag.split()[0] diff --git a/tests/conftest.py b/tests/conftest.py index 942a0efb..1cc008eb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -32,7 +32,7 @@ def logger(): "submodule_name": "test_optional", "status1" : "test_optional MPIserial_2.5.0-3-gd82ce7c is out of sync with .gitmodules MPIserial_2.4.0", "status2" : "test_optional at tag MPIserial_2.4.0", - "status3" : "test_optional not checked out, aligned at tag MPIserial_2.4.0", + "status3" : "test_optional not checked out, out of sync at tag None, expected tag is MPIserial_2.4.0", "status4" : "test_optional at tag MPIserial_2.4.0", "gitmodules_content": """ [submodule "test_optional"] @@ -46,7 +46,7 @@ def logger(): "submodule_name": "test_alwaysoptional", "status1" : "test_alwaysoptional MPIserial_2.3.0 is out of sync with .gitmodules e5cf35c", "status2" : "test_alwaysoptional at hash e5cf35c", - "status3" : "test_alwaysoptional not checked out, out of sync at tag MPIserial_2.3.0", + "status3" : "out of sync at tag None, expected tag is e5cf35c", "status4" : "test_alwaysoptional at hash e5cf35c", "gitmodules_content": """ [submodule "test_alwaysoptional"] diff --git a/tests/test_d_complex.py b/tests/test_d_complex.py index 62889f2c..edde7d81 100644 --- a/tests/test_d_complex.py +++ b/tests/test_d_complex.py @@ -8,7 +8,7 @@ def test_complex_checkout(git_fleximod, complex_repo, logger): assert("ToplevelRequired not checked out, aligned at tag MPIserial_2.5.0" in status.stdout) assert("AlwaysRequired not checked out, aligned at tag MPIserial_2.4.0" in status.stdout) assert("Complex not checked out, aligned at tag testtag02" in status.stdout) - assert("AlwaysOptional not checked out, aligned at tag MPIserial_2.3.0" in status.stdout) + assert("AlwaysOptional not checked out, out of sync at tag None, expected tag is MPIserial_2.3.0" in status.stdout) # This should checkout and update test_submodule and complex_sub result = git_fleximod(complex_repo, "update") @@ -40,8 +40,7 @@ def test_complex_checkout(git_fleximod, complex_repo, logger): assert("ToplevelRequired at tag MPIserial_2.5.0" in status.stdout) assert("AlwaysRequired at tag MPIserial_2.4.0" in status.stdout) assert("Complex at tag testtag02" in status.stdout) - assert("AlwaysOptional not checked out, aligned at tag MPIserial_2.3.0" in status.stdout) - + assert("AlwaysOptional not checked out, out of sync at tag None, expected tag is MPIserial_2.3.0" in status.stdout) # Finally update optional result = git_fleximod(complex_repo, "update --optional") From 87640adf43cdd3c30acd258185f128e638cb41ac Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Thu, 6 Jun 2024 13:29:04 -0600 Subject: [PATCH 136/159] Bump to 0.7.7 --- git_fleximod/cli.py | 2 +- pyproject.toml | 2 +- tbump.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/git_fleximod/cli.py b/git_fleximod/cli.py index d4498b84..bc099fcb 100644 --- a/git_fleximod/cli.py +++ b/git_fleximod/cli.py @@ -2,7 +2,7 @@ import argparse from git_fleximod import utils -__version__ = "0.7.6" +__version__ = "0.7.7" def find_root_dir(filename=".gitmodules"): """ finds the highest directory in tree diff --git a/pyproject.toml b/pyproject.toml index 3c70c6c9..a316914b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "git-fleximod" -version = "0.7.6" +version = "0.7.7" description = "Extended support for git-submodule and git-sparse-checkout" authors = ["Jim Edwards "] maintainers = ["Jim Edwards "] diff --git a/tbump.toml b/tbump.toml index 3854682d..c22637cc 100644 --- a/tbump.toml +++ b/tbump.toml @@ -2,7 +2,7 @@ github_url = "https://github.com/jedwards4b/git-fleximod/" [version] -current = "0.7.6" +current = "0.7.7" # Example of a semver regexp. # Make sure this matches current_version before From 2ede182089784488065b93346d301a202df7b329 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Sat, 8 Jun 2024 08:40:03 -0600 Subject: [PATCH 137/159] add optional to status output --- git_fleximod/git_fleximod.py | 10 ++++++---- tests/conftest.py | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index ca5f9062..e1b8f484 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -304,6 +304,7 @@ def submodules_status(gitmodules, root_dir, toplevel=False): ahash = result[0][1:] hhash = None atag = None + needsupdate += 1 if not toplevel and level: continue @@ -316,22 +317,23 @@ def submodules_status(gitmodules, root_dir, toplevel=False): hhash = htag.split()[0] if hhash and atag: break + optional = " (optional)" if required and "Optional" in required else "" if tag and (ahash == hhash or atag == tag): - print(f"e {name:>20} not checked out, aligned at tag {tag}") + print(f"e {name:>20} not checked out, aligned at tag {tag}{optional}") elif tag: ahash = rootgit.git_operation( "submodule", "status", "{}".format(path) ).rstrip() ahash = ahash[1 : len(tag) + 1] if tag == ahash: - print(f"e {name:>20} not checked out, aligned at hash {ahash}") + print(f"e {name:>20} not checked out, aligned at hash {ahash}{optional}") else: print( - f"e {name:>20} not checked out, out of sync at tag {atag}, expected tag is {tag}" + f"e {name:>20} not checked out, out of sync at tag {atag}, expected tag is {tag}{optional}" ) testfails += 1 else: - print(f"e {name:>20} has no fxtag defined in .gitmodules") + print(f"e {name:>20} has no fxtag defined in .gitmodules{optional}") testfails += 1 else: with utils.pushd(newpath): diff --git a/tests/conftest.py b/tests/conftest.py index 1cc008eb..65ee85d2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -32,7 +32,7 @@ def logger(): "submodule_name": "test_optional", "status1" : "test_optional MPIserial_2.5.0-3-gd82ce7c is out of sync with .gitmodules MPIserial_2.4.0", "status2" : "test_optional at tag MPIserial_2.4.0", - "status3" : "test_optional not checked out, out of sync at tag None, expected tag is MPIserial_2.4.0", + "status3" : "test_optional not checked out, out of sync at tag None, expected tag is MPIserial_2.4.0 (optional)", "status4" : "test_optional at tag MPIserial_2.4.0", "gitmodules_content": """ [submodule "test_optional"] From 7ea36dc44c5646bfcf9940ea757c5f8d9f1d9778 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Mon, 10 Jun 2024 15:57:15 -0600 Subject: [PATCH 138/159] remove manage_externals error --- git_fleximod/utils.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/git_fleximod/utils.py b/git_fleximod/utils.py index 7cc1de38..1a2d5ccf 100644 --- a/git_fleximod/utils.py +++ b/git_fleximod/utils.py @@ -241,12 +241,12 @@ def _hanging_msg(working_directory, command): has taken {hanging_sec} seconds. It may be hanging. The command will continue to run, but you may want to abort -manage_externals with ^C and investigate. A possible cause of hangs is -when svn or git require authentication to access a private -repository. On some systems, svn and git requests for authentication -information will not be displayed to the user. In this case, the program -will appear to hang. Ensure you can run svn and git manually and access -all repositories without entering your authentication information. +git-fleximod with ^C and investigate. A possible cause of hangs is git +requires authentication to access a private repository. On some +systems, git requests for authentication information will not +be displayed to the user. In this case, the program will appear to +hang. Ensure you can run git manually and access all +repositories without entering your authentication information. """.format( command=command, From af12c80cade77d299629700f91a0095a5cd6545a Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Tue, 11 Jun 2024 06:28:49 -0600 Subject: [PATCH 139/159] fix spelling --- git_fleximod/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git_fleximod/cli.py b/git_fleximod/cli.py index bc099fcb..9034ac2d 100644 --- a/git_fleximod/cli.py +++ b/git_fleximod/cli.py @@ -26,7 +26,7 @@ def find_root_dir(filename=".gitmodules"): def get_parser(): description = """ - %(prog)s manages checking out groups of gitsubmodules with addtional support for Earth System Models + %(prog)s manages checking out groups of gitsubmodules with additional support for Earth System Models """ parser = argparse.ArgumentParser( description=description, formatter_class=argparse.RawDescriptionHelpFormatter From 8eae7e3fb53a3c7dff7010e90cd6990ae7f2021d Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Wed, 12 Jun 2024 11:24:55 -0600 Subject: [PATCH 140/159] Bump to 0.7.8 --- git_fleximod/cli.py | 2 +- pyproject.toml | 2 +- tbump.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/git_fleximod/cli.py b/git_fleximod/cli.py index 9034ac2d..a15a226d 100644 --- a/git_fleximod/cli.py +++ b/git_fleximod/cli.py @@ -2,7 +2,7 @@ import argparse from git_fleximod import utils -__version__ = "0.7.7" +__version__ = "0.7.8" def find_root_dir(filename=".gitmodules"): """ finds the highest directory in tree diff --git a/pyproject.toml b/pyproject.toml index a316914b..5b133254 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "git-fleximod" -version = "0.7.7" +version = "0.7.8" description = "Extended support for git-submodule and git-sparse-checkout" authors = ["Jim Edwards "] maintainers = ["Jim Edwards "] diff --git a/tbump.toml b/tbump.toml index c22637cc..c4f7ac96 100644 --- a/tbump.toml +++ b/tbump.toml @@ -2,7 +2,7 @@ github_url = "https://github.com/jedwards4b/git-fleximod/" [version] -current = "0.7.7" +current = "0.7.8" # Example of a semver regexp. # Make sure this matches current_version before From 0c3d68371e5a5e1cac6949fbee94713264a97bc5 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Wed, 26 Jun 2024 13:42:19 -0600 Subject: [PATCH 141/159] dependabot changes and issue #48 --- git_fleximod/git_fleximod.py | 2 +- poetry.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index e1b8f484..ed24c4e7 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -547,7 +547,7 @@ def submodules_test(gitmodules, root_dir): # and that sparse checkout files exist for name in gitmodules.sections(): url = gitmodules.get(name, "url") - fxurl = gitmodules.get(name, "fxDONOTMODIFYurl") + fxurl = gitmodules.get(name, "fxDONOTUSEurl") fxsparse = gitmodules.get(name, "fxsparse") path = gitmodules.get(name, "path") fxurl = fxurl[:-4] if fxurl.endswith(".git") else fxurl diff --git a/poetry.lock b/poetry.lock index b59ed394..4d36881b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -242,7 +242,7 @@ test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre [[package]] name = "idna" -version = "3.6" +version = "3.7" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" @@ -294,7 +294,7 @@ files = [ [[package]] name = "jinja2" -version = "3.1.3" +version = "3.1.4" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" @@ -465,7 +465,7 @@ files = [ [[package]] name = "requests" -version = "2.31.0" +version = "2.32.0" description = "Python HTTP for Humans." optional = false python-versions = ">=3.7" @@ -643,7 +643,7 @@ files = [ [[package]] name = "urllib3" -version = "2.2.0" +version = "2.2.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" From 46043f0104254dffd1b1da34c57ad940124e40d0 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Wed, 26 Jun 2024 13:46:07 -0600 Subject: [PATCH 142/159] add code of conduct (issue #35) --- CODE_OF_CONDUCT.md | 107 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..84f2925b --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,107 @@ +# Contributor Code of Conduct +_The Contributor Code of Conduct is for participants in our software projects and community._ + +## Our Pledge +We, as contributors, creators, stewards, and maintainers (participants), of **git-fleximod** pledge to make participation in +our software, system or hardware project and community a safe, productive, welcoming and inclusive experience for everyone. +All participants are required to abide by this Code of Conduct. +This includes respectful treatment of everyone regardless of age, body size, disability, ethnicity, gender identity or expression, +level of experience, nationality, political affiliation, veteran status, pregnancy, genetic information, physical appearance, race, +religion, or sexual orientation, as well as any other characteristic protected under applicable US federal or state law. + +## Our Standards +Examples of behaviors that contribute to a positive environment include: + +* All participants are treated with respect and consideration, valuing a diversity of views and opinions +* Be considerate, respectful, and collaborative +* Communicate openly with respect for others, critiquing ideas rather than individuals and gracefully accepting criticism +* Acknowledging the contributions of others +* Avoid personal attacks directed toward other participants +* Be mindful of your surroundings and of your fellow participants +* Alert UCAR staff and suppliers/vendors if you notice a dangerous situation or someone in distress +* Respect the rules and policies of the project and venue + +Examples of unacceptable behavior include, but are not limited to: + +* Harassment, intimidation, or discrimination in any form +* Physical, verbal, or written abuse by anyone to anyone, including repeated use of pronouns other than those requested +* Unwelcome sexual attention or advances +* Personal attacks directed at other guests, members, participants, etc. +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Alarming, intimidating, threatening, or hostile comments or conduct +* Inappropriate use of nudity and/or sexual images +* Threatening or stalking anyone, including a participant +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Scope +This Code of Conduct applies to all spaces managed by the Project whether they be physical, online or face-to-face. +This includes project code, code repository, associated web pages, documentation, mailing lists, project websites and wiki pages, +issue tracker, meetings, telecons, events, project social media accounts, and any other forums created by the project team which the +community uses for communication. +In addition, violations of this Code of Conduct outside these spaces may affect a person's ability to participate within them. +Representation of a project may be further defined and clarified by project maintainers. + +## Community Responsibilities +Everyone in the community is empowered to respond to people who are showing unacceptable behavior. +They can talk to them privately or publicly. +Anyone requested to stop unacceptable behavior is expected to comply immediately. +If the behavior continues concerns may be brought to the project administrators or to any other party listed in the +[Reporting](#reporting) section below. + +## Project Administrator Responsibilities +Project administrators are responsible for clarifying the standards of acceptable behavior and are encouraged to model appropriate +behavior and provide support when people in the community point out inappropriate behavior. +Project administrator(s) are normally the ones that would be tasked to carry out the actions in the [Consequences](#consequences) +section below. + +Project administrators are also expected to keep this Code of Conduct updated with the main one housed at UCAR, as listed below in +the [Attribution](#attribution) section. + +## Reporting +Instances of unacceptable behavior can be brought to the attention of the project administrator(s) who may take any action as +outlined in the [Consequences](#consequences) section below. +However, making a report to a project administrator is not considered an 'official report' to UCAR. + +Instances of unacceptable behavior may also be reported directly to UCAR pursuant to [UCAR's Harassment Reporting and Complaint +Procedure](https://www2.fin.ucar.edu/procedures/hr/harassment-reporting-and-complaint-procedure), or anonymously through [UCAR's +EthicsPoint Hotline](https://www2.fin.ucar.edu/ethics/anonymous-reporting). + +Complaints received by UCAR will be handled pursuant to the procedures outlined in UCAR's Harassment Reporting and Complaint +Procedure. +Complaints to UCAR will be held as confidential as practicable under the circumstances, and retaliation against a person who +initiates a complaint or an inquiry about inappropriate behavior will not be tolerated. + +Any Contributor can use these reporting methods even if they are not directly affiliated with UCAR. +The Frequently Asked Questions (FAQ) page for reporting is [here](https://www2.fin.ucar.edu/procedures/hr/reporting-faqs). + +## Consequences +Upon receipt of a complaint, the project administrator(s) may take any action deemed necessary and appropriate under the +circumstances. +Such action can include things such as: removing, editing, or rejecting comments, commits, code, wiki edits, email, issues, and +other contributions that are not aligned to this Code of Conduct, or banning temporarily or permanently any contributor for other +behaviors that are deemed inappropriate, threatening, offensive, or harmful. +Project administrators also have the right to report violations to UCAR HR and/or UCAR's Office of Diversity, Equity and Inclusion +(ODEI), as well as a participant's home institution and/or law enforcement. +In the event an incident is reported to UCAR, UCAR will follow its Harassment Reporting and Complaint Procedure. + +## Process for Changes +All UCAR managed projects are required to adopt this Contributor Code of Conduct. +Adoption is assumed even if not expressly stated in the repository. +Projects should fill in sections where prompted with project-specific information, including, project name and adoption date. + +Projects that adopt this Code of Conduct need to stay up to date with UCAR's Contributor Code of Conduct, linked with a DOI in the +[Attribution](#attribution) section below. +Projects can make limited substantive changes to the Code of Conduct, however, the changes must be limited in scope and may not +contradict the UCAR Contributor Code of Conduct. + +## Attribution +This Code of Conduct was originally adapted from the [Contributor Covenant](http://contributor-covenant.org/version/1/4), version +1.4. +We then aligned it with the UCAR Participant Code of Conduct, which also borrows from the American Geophysical Union (AGU) Code of +Conduct. +The UCAR Participant Code of Conduct applies to both UCAR employees as well as participants in activities run by UCAR. +The original version of this for all software projects that have strong management from UCAR or UCAR staff is available on the UCAR +website at https://doi.org/10.5065/6w2c-a132. +The date that it was adopted by this project was **Feb/13/2018**. +When responding to complaints, UCAR HR and ODEI will do so based on the latest published version. +Therefore, any project-specific changes should follow the [Process for Changes](#process-for-changes) section above. From 31ce71bf80dd69823ca501d6902eb080d747cebb Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Wed, 26 Jun 2024 13:53:28 -0600 Subject: [PATCH 143/159] use poetry update to update poetry.lock file --- poetry.lock | 110 ++++++++++++++++++++++++++-------------------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/poetry.lock b/poetry.lock index 4d36881b..3a74effc 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,13 +13,13 @@ files = [ [[package]] name = "babel" -version = "2.14.0" +version = "2.15.0" description = "Internationalization utilities" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"}, - {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"}, + {file = "Babel-2.15.0-py3-none-any.whl", hash = "sha256:08706bdad8d0a3413266ab61bd6c34d0c28d6e1e7badf40a2cebe67644e2e1fb"}, + {file = "babel-2.15.0.tar.gz", hash = "sha256:8daf0e265d05768bc6c7a314cf1321e9a123afc328cc635c18622a2f30a04413"}, ] [package.dependencies] @@ -30,13 +30,13 @@ dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] [[package]] name = "certifi" -version = "2024.2.2" +version = "2024.6.2" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, - {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, + {file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"}, + {file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"}, ] [[package]] @@ -162,13 +162,13 @@ files = [ [[package]] name = "exceptiongroup" -version = "1.2.0" +version = "1.2.1" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, + {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, + {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, ] [package.extras] @@ -225,20 +225,21 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.41" +version = "3.1.43" description = "GitPython is a Python library used to interact with Git repositories" optional = false python-versions = ">=3.7" files = [ - {file = "GitPython-3.1.41-py3-none-any.whl", hash = "sha256:c36b6634d069b3f719610175020a9aed919421c87552185b085e04fbbdb10b7c"}, - {file = "GitPython-3.1.41.tar.gz", hash = "sha256:ed66e624884f76df22c8e16066d567aaa5a37d5b5fa19db2c6df6f7156db9048"}, + {file = "GitPython-3.1.43-py3-none-any.whl", hash = "sha256:eec7ec56b92aad751f9912a73404bc02ba212a23adb2c7098ee668417051a1ff"}, + {file = "GitPython-3.1.43.tar.gz", hash = "sha256:35f314a9f878467f5453cc1fee295c3e18e52f1b99f10f6cf5b1682e968a9e7c"}, ] [package.dependencies] gitdb = ">=4.0.1,<5" [package.extras] -test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "sumtypes"] +doc = ["sphinx (==4.3.2)", "sphinx-autodoc-typehints", "sphinx-rtd-theme", "sphinxcontrib-applehelp (>=1.0.2,<=1.0.4)", "sphinxcontrib-devhelp (==1.0.2)", "sphinxcontrib-htmlhelp (>=2.0.0,<=2.0.1)", "sphinxcontrib-qthelp (==1.0.3)", "sphinxcontrib-serializinghtml (==1.1.5)"] +test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions"] [[package]] name = "idna" @@ -247,8 +248,8 @@ description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.5" files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, ] [[package]] @@ -264,22 +265,22 @@ files = [ [[package]] name = "importlib-metadata" -version = "7.0.1" +version = "8.0.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"}, - {file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"}, + {file = "importlib_metadata-8.0.0-py3-none-any.whl", hash = "sha256:15584cf2b1bf449d98ff8a6ff1abef57bf20f3ac6454f431736cd3e660921b2f"}, + {file = "importlib_metadata-8.0.0.tar.gz", hash = "sha256:188bd24e4c346d3f0a933f275c2fec67050326a856b9a359881d7c2a697e8812"}, ] [package.dependencies] zipp = ">=0.5" [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] [[package]] name = "iniconfig" @@ -299,8 +300,8 @@ description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" files = [ - {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, - {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, ] [package.dependencies] @@ -380,24 +381,24 @@ files = [ [[package]] name = "packaging" -version = "23.2" +version = "24.1" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] name = "pluggy" -version = "1.4.0" +version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] @@ -406,39 +407,38 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pyfakefs" -version = "5.3.5" +version = "5.5.0" description = "pyfakefs implements a fake file system that mocks the Python file system modules." optional = false python-versions = ">=3.7" files = [ - {file = "pyfakefs-5.3.5-py3-none-any.whl", hash = "sha256:751015c1de94e1390128c82b48cdedc3f088bbdbe4bc713c79d02a27f0f61e69"}, - {file = "pyfakefs-5.3.5.tar.gz", hash = "sha256:7cdc500b35a214cb7a614e1940543acc6650e69a94ac76e30f33c9373bd9cf90"}, + {file = "pyfakefs-5.5.0-py3-none-any.whl", hash = "sha256:8dbf203ab7bef1529f11f7d41b9478b898e95bf9f3b71262163aac07a518cd76"}, + {file = "pyfakefs-5.5.0.tar.gz", hash = "sha256:7448aaa07142f892d0a4eb52a5ed3206a9f02c6599e686cd97d624c18979c154"}, ] [[package]] name = "pygments" -version = "2.17.2" +version = "2.18.0" description = "Pygments is a syntax highlighting package written in Python." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, - {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, ] [package.extras] -plugins = ["importlib-metadata"] windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pytest" -version = "8.0.0" +version = "8.2.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.0.0-py3-none-any.whl", hash = "sha256:50fb9cbe836c3f20f0dfa99c565201fb75dc54c8d76373cd1bde06b06657bdb6"}, - {file = "pytest-8.0.0.tar.gz", hash = "sha256:249b1b0864530ba251b7438274c4d251c58d868edaaec8762893ad4a0d71c36c"}, + {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"}, + {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, ] [package.dependencies] @@ -446,11 +446,11 @@ colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" -pluggy = ">=1.3.0,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} +pluggy = ">=1.5,<2.0" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytz" @@ -465,13 +465,13 @@ files = [ [[package]] name = "requests" -version = "2.32.0" +version = "2.32.3" description = "Python HTTP for Humans." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [package.dependencies] @@ -648,8 +648,8 @@ description = "HTTP library with thread-safe connection pooling, file post, and optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.0-py3-none-any.whl", hash = "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224"}, - {file = "urllib3-2.2.0.tar.gz", hash = "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20"}, + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, ] [package.extras] @@ -674,18 +674,18 @@ test = ["pytest (>=6.0.0)", "setuptools (>=65)"] [[package]] name = "zipp" -version = "3.17.0" +version = "3.19.2" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" files = [ - {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, - {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, + {file = "zipp-3.19.2-py3-none-any.whl", hash = "sha256:f091755f667055f2d02b32c53771a7a6c8b47e1fdbc4b72a8b9072b3eef8015c"}, + {file = "zipp-3.19.2.tar.gz", hash = "sha256:bf1dcf6450f873a13e952a29504887c89e6de7506209e5b1bcc3460135d4de19"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [metadata] lock-version = "2.0" From 53220198c5e53fd11026df8ef71b11ef4d5a3233 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Wed, 26 Jun 2024 14:07:18 -0600 Subject: [PATCH 144/159] Bump to 0.7.9 --- git_fleximod/cli.py | 2 +- pyproject.toml | 2 +- tbump.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/git_fleximod/cli.py b/git_fleximod/cli.py index a15a226d..25fce68f 100644 --- a/git_fleximod/cli.py +++ b/git_fleximod/cli.py @@ -2,7 +2,7 @@ import argparse from git_fleximod import utils -__version__ = "0.7.8" +__version__ = "0.7.9" def find_root_dir(filename=".gitmodules"): """ finds the highest directory in tree diff --git a/pyproject.toml b/pyproject.toml index 5b133254..52cc26e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "git-fleximod" -version = "0.7.8" +version = "0.7.9" description = "Extended support for git-submodule and git-sparse-checkout" authors = ["Jim Edwards "] maintainers = ["Jim Edwards "] diff --git a/tbump.toml b/tbump.toml index c4f7ac96..b1d08a56 100644 --- a/tbump.toml +++ b/tbump.toml @@ -2,7 +2,7 @@ github_url = "https://github.com/jedwards4b/git-fleximod/" [version] -current = "0.7.8" +current = "0.7.9" # Example of a semver regexp. # Make sure this matches current_version before From a7d8b4e3e7e0661b98ba03dfc3808b84c6ec6ff1 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Mon, 1 Jul 2024 17:06:50 -0600 Subject: [PATCH 145/159] all pytests passing --- git_fleximod/git_fleximod.py | 109 +++++++++++++++++++++------------ git_fleximod/gitmodules.py | 2 +- tests/conftest.py | 16 ++++- tests/test_e_complex_update.py | 68 ++++++++++++++++++++ 4 files changed, 152 insertions(+), 43 deletions(-) create mode 100644 tests/test_e_complex_update.py diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index ed24c4e7..69da054d 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -20,7 +20,7 @@ def fxrequired_allowed_values(): - return ["ToplevelRequired", "ToplevelOptional", "AlwaysRequired", "AlwaysOptional"] + return ["ToplevelRequired", "ToplevelOptional", "AlwaysRequired", "AlwaysOptional", "TopLevelRequired", "TopLevelOptional"] def commandline_arguments(args=None): @@ -33,14 +33,9 @@ def commandline_arguments(args=None): # explicitly listing a component overrides the optional flag if options.optional or options.components: - fxrequired = [ - "ToplevelRequired", - "ToplevelOptional", - "AlwaysRequired", - "AlwaysOptional", - ] + fxrequired = fxrequired_allowed_values() else: - fxrequired = ["ToplevelRequired", "AlwaysRequired"] + fxrequired = ["ToplevelRequired", "AlwaysRequired", "TopLevelRequired"] action = options.action if not action: @@ -154,6 +149,8 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master if os.path.isdir(os.path.join(root_dir, path, ".git")): with utils.pushd(sprep_repo): + if os.path.isdir(os.path.join(topgit,".git")): + shutil.rmtree(os.path.join(topgit,".git")) shutil.move(".git", topgit) with open(".git", "w") as f: f.write("gitdir: " + os.path.relpath(topgit)) @@ -166,7 +163,9 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master return with utils.pushd(sprep_repo): - shutil.copy(sparsefile, gitsparse) + if os.path.isfile(sparsefile): + shutil.copy(sparsefile, gitsparse) + # Finally checkout the repo sprepo_git.git_operation("fetch", "origin", "--tags") @@ -202,9 +201,9 @@ def single_submodule_checkout( # if url is provided update to the new url tmpurl = None repo_exists = False - if os.path.exists(os.path.join(repodir, ".git")): - logger.info("Submodule {} already checked out".format(name)) - repo_exists = True +# if os.path.exists(os.path.join(repodir, ".git")): +# logger.info("Submodule {} already checked out".format(name)) +# repo_exists = True # Look for a .gitmodules file in the newly checkedout repo if not repo_exists and url: # ssh urls cause problems for those who dont have git accounts with ssh keys defined @@ -244,10 +243,13 @@ def single_submodule_checkout( if not repo_exists or not tmpurl: git.git_operation("submodule", "update", "--init", "--", path) - + if tag and not optional: + smgit = GitInterface(repodir, logger) + smgit.git_operation("checkout", tag) + if os.path.exists(os.path.join(repodir, ".gitmodules")): # recursively handle this checkout - print(f"Recursively checking out submodules of {name}") + print(f"Recursively checking out submodules of {name} {optional}") gitmodules = GitModules(logger, confpath=repodir) requiredlist = ["AlwaysRequired"] if optional: @@ -288,7 +290,7 @@ def submodules_status(gitmodules, root_dir, toplevel=False): tag = gitmodules.get(name, "fxtag") url = gitmodules.get(name, "url") required = gitmodules.get(name, "fxrequired") - level = required and "Toplevel" in required + level = required and required.startswith("Top") if not path: utils.fatal_error("No path found in .gitmodules for {}".format(name)) newpath = os.path.join(root_dir, path) @@ -299,6 +301,7 @@ def submodules_status(gitmodules, root_dir, toplevel=False): url = url.replace("git@github.com:", "https://github.com/") tags = rootgit.git_operation("ls-remote", "--tags", url) result = rootgit.git_operation("submodule","status",newpath).split() +# print(f"newpath {newpath} result {result}") ahash = None if result: ahash = result[0][1:] @@ -318,8 +321,12 @@ def submodules_status(gitmodules, root_dir, toplevel=False): if hhash and atag: break optional = " (optional)" if required and "Optional" in required else "" +# print(f"tag is {tag} ahash is {ahash} {hhash} {atag} {newpath} {required} {level}") + if required and not required.startswith("Always") and not level: + continue + if tag and (ahash == hhash or atag == tag): - print(f"e {name:>20} not checked out, aligned at tag {tag}{optional}") + print(f"e {name:>20} not checked out, aligned at tag {tag}{optional} {level} {required}") elif tag: ahash = rootgit.git_operation( "submodule", "status", "{}".format(path) @@ -341,15 +348,19 @@ def submodules_status(gitmodules, root_dir, toplevel=False): atag = git.git_operation("describe", "--tags", "--always").rstrip() ahash = git.git_operation("rev-list", "HEAD").partition("\n")[0] rurl = git.git_operation("ls-remote","--get-url").rstrip() + recurse = False if rurl != url: remote = add_remote(git, url) git.git_operation("fetch", remote) if tag and atag == tag: print(f" {name:>20} at tag {tag}") + recurse = True elif tag and ahash[: len(tag)] == tag: print(f" {name:>20} at hash {ahash}") + recurse = True elif atag == ahash: print(f" {name:>20} at hash {ahash}") + recurse = True elif tag: print( f"s {name:>20} {atag} {ahash} is out of sync with .gitmodules {tag}" @@ -361,18 +372,33 @@ def submodules_status(gitmodules, root_dir, toplevel=False): f"e {name:>20} has no fxtag defined in .gitmodules, module at {atag}" ) testfails += 1 - + status = git.git_operation("status", "--ignore-submodules", "-uno") if "nothing to commit" not in status: localmods = localmods + 1 print("M" + textwrap.indent(status, " ")) - + if recurse and os.path.exists(os.path.join(newpath, ".gitmodules")): + submodules = GitModules( + logger, + confpath=newpath, + ) + nf, lm, nu = submodules_status(submodules, newpath) + testfails += nf + localmods += lm + needsupdate += nu + + return testfails, localmods, needsupdate +def git_toplevelroot(root_dir, logger): + rgit = GitInterface(root_dir, logger) + superroot = rgit.git_operation("rev-parse", "--show-superproject-working-tree") + return superroot + + def submodules_update(gitmodules, root_dir, requiredlist, force): _, localmods, needsupdate = submodules_status(gitmodules, root_dir) - if localmods and not force: local_mods_output() return @@ -383,27 +409,26 @@ def submodules_update(gitmodules, root_dir, requiredlist, force): fxtag = gitmodules.get(name, "fxtag") path = gitmodules.get(name, "path") url = gitmodules.get(name, "url") - logger.info( - "name={} path={} url={} fxtag={} requiredlist={} ".format( - name, os.path.join(root_dir, path), url, fxtag, requiredlist - ) - ) fxrequired = gitmodules.get(name, "fxrequired") - assert fxrequired in fxrequired_allowed_values() - rgit = GitInterface(root_dir, logger) - superroot = rgit.git_operation("rev-parse", "--show-superproject-working-tree") + if fxrequired: + allowedvalues = fxrequired_allowed_values() + assert fxrequired in allowedvalues - fxsparse = gitmodules.get(name, "fxsparse") + fxsparse = gitmodules.get(name, "fxsparse") + superroot = git_toplevelroot(root_dir, logger) + if ( fxrequired - and (superroot and "Toplevel" in fxrequired) - or fxrequired not in requiredlist + and ((superroot and "Toplevel" in fxrequired) + or fxrequired not in requiredlist) ): - if "ToplevelOptional" == fxrequired: - print("Skipping optional component {}".format(name)) - continue + if "Optional" in fxrequired and "Optional" not in requiredlist: + if fxrequired.startswith("Always"): + print(f"Skipping optional component {name:>20}") + continue + if fxsparse: logger.debug( "Callng submodule_sparse_checkout({}, {}, {}, {}, {}, {}".format( @@ -417,7 +442,7 @@ def submodules_update(gitmodules, root_dir, requiredlist, force): root_dir, name, path, url ) ) - + print(f"fxrequired {fxrequired} requiredlist {requiredlist}") single_submodule_checkout( root_dir, name, @@ -425,7 +450,7 @@ def submodules_update(gitmodules, root_dir, requiredlist, force): url=url, tag=fxtag, force=force, - optional=("AlwaysOptional" in requiredlist), + optional="AlwaysOptional" in requiredlist ) if os.path.exists(os.path.join(path, ".git")): @@ -448,6 +473,8 @@ def submodules_update(gitmodules, root_dir, requiredlist, force): print(f"{name:>20} updated to {fxtag}") except Exception as error: print(error) + + elif not fxtag: print(f"No fxtag found for submodule {name:>20}") else: @@ -455,7 +482,6 @@ def submodules_update(gitmodules, root_dir, requiredlist, force): - def local_mods_output(): text = """\ The submodules labeled with 'M' above are not in a clean state. @@ -469,7 +495,6 @@ def local_mods_output(): """ print(text) - # checkout is done by update if required so this function may be depricated def submodules_checkout(gitmodules, root_dir, requiredlist, force=False): """ @@ -498,10 +523,12 @@ def submodules_checkout(gitmodules, root_dir, requiredlist, force=False): fxtag = gitmodules.get(name, "fxtag") path = gitmodules.get(name, "path") url = gitmodules.get(name, "url") + superroot = git_toplevelroot(root_dir, logger) if fxrequired and fxrequired not in requiredlist: - if "Optional" in fxrequired: - print("Skipping optional component {}".format(name)) + if fxrequired == "AlwaysOptional" or (superroot == root_dir): + print(f"Skipping optional component {name:>20} {requiredlist}") continue + if fxsparse: logger.debug( @@ -521,7 +548,9 @@ def submodules_checkout(gitmodules, root_dir, requiredlist, force=False): url=url, tag=fxtag, force=force, - optional="AlwaysOptional" in requiredlist, + optional="AlwaysOptional" == fxrequired \ + or "ToplevelOptional" == fxrequired \ + or "TopLevelOptional" == fxrequired ) diff --git a/git_fleximod/gitmodules.py b/git_fleximod/gitmodules.py index 7e4e0539..cf8b350d 100644 --- a/git_fleximod/gitmodules.py +++ b/git_fleximod/gitmodules.py @@ -1,4 +1,4 @@ -import shutil +import shutil, os from pathlib import Path from configparser import RawConfigParser, ConfigParser from .lstripreader import LstripReader diff --git a/tests/conftest.py b/tests/conftest.py index 65ee85d2..81edbe71 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -119,8 +119,20 @@ def complex_repo(tmp_path, logger): str_path = str(test_dir) gitp = GitInterface(str_path, logger) gitp.git_operation("remote", "add", "origin", "https://github.com/jedwards4b/fleximod-test2") - gitp.git_operation("fetch", "origin", "main") - gitp.git_operation("checkout", "main") + gitp.git_operation("fetch", "origin") + gitp.git_operation("checkout", "v0.0.1") + return test_dir + +@pytest.fixture +def complex_update(tmp_path, logger): + test_dir = tmp_path / "testcomplex" + test_dir.mkdir() + str_path = str(test_dir) + gitp = GitInterface(str_path, logger) + gitp.git_operation("remote", "add", "origin", "https://github.com/jedwards4b/fleximod-test2") + gitp.git_operation("fetch", "origin") + gitp.git_operation("checkout", "v0.0.2") + return test_dir @pytest.fixture diff --git a/tests/test_e_complex_update.py b/tests/test_e_complex_update.py new file mode 100644 index 00000000..44998f18 --- /dev/null +++ b/tests/test_e_complex_update.py @@ -0,0 +1,68 @@ +import pytest +from pathlib import Path +from git_fleximod.gitinterface import GitInterface + +def test_complex_update(git_fleximod, complex_update, logger): + status = git_fleximod(complex_update, "status") + assert("ToplevelOptional not checked out, aligned at tag v5.3.2" in status.stdout) + assert("ToplevelRequired not checked out, aligned at tag MPIserial_2.5.0" in status.stdout) + assert("AlwaysRequired not checked out, aligned at tag MPIserial_2.4.0" in status.stdout) + assert("Complex not checked out, out of sync at tag testtag02, expected tag is testtag3" in status.stdout) + assert("AlwaysOptional not checked out, out of sync at tag None, expected tag is MPIserial_2.3.0" in status.stdout) + print(f"status before is {status.stdout}") + + # This should checkout and update test_submodule and complex_sub + result = git_fleximod(complex_update, "update") + assert result.returncode == 0 + + status = git_fleximod(complex_update, "status") + assert("ToplevelOptional not checked out, aligned at tag v5.3.2" in status.stdout) + assert("ToplevelRequired at tag MPIserial_2.5.0" in status.stdout) + assert("AlwaysRequired at tag MPIserial_2.4.0" in status.stdout) + assert("Complex at tag testtag3" in status.stdout) + print(f"status after is {status.stdout}") + # now check the complex_sub + root = (complex_update / "modules" / "complex") + assert(not (root / "libraries" / "gptl" / ".git").exists()) + assert(not (root / "libraries" / "mpi-serial" / ".git").exists()) + assert((root / "modules" / "mpi-serialAR" / ".git").exists()) + assert((root / "modules" / "mpi-serialSAR" / ".git").exists()) + assert(not (root / "modules" / "mpi-serial2" / ".git").exists()) + assert((root / "modules" / "mpi-sparse" / ".git").exists()) + assert((root / "modules" / "mpi-sparse" / "m4").exists()) + assert(not (root / "modules" / "mpi-sparse" / "README").exists()) + + # update a single optional submodule + + result = git_fleximod(complex_update, "update ToplevelOptional") + assert result.returncode == 0 + + status = git_fleximod(complex_update, "status") + assert("ToplevelOptional at tag v5.3.2" in status.stdout) + assert("ToplevelRequired at tag MPIserial_2.5.0" in status.stdout) + assert("AlwaysRequired at tag MPIserial_2.4.0" in status.stdout) + assert("Complex at tag testtag3" in status.stdout) + assert("AlwaysOptional not checked out, out of sync at tag None, expected tag is MPIserial_2.3.0" in status.stdout) + + # Finally update optional + result = git_fleximod(complex_update, "update --optional") + assert result.returncode == 0 + + status = git_fleximod(complex_update, "status") + assert("ToplevelOptional at tag v5.3.2" in status.stdout) + assert("ToplevelRequired at tag MPIserial_2.5.0" in status.stdout) + assert("AlwaysRequired at tag MPIserial_2.4.0" in status.stdout) + assert("Complex at tag testtag3" in status.stdout) + assert("AlwaysOptional at tag MPIserial_2.3.0" in status.stdout) + + # now check the complex_sub + root = (complex_update / "modules" / "complex" ) + assert(not (root / "libraries" / "gptl" / ".git").exists()) + assert(not (root / "libraries" / "mpi-serial" / ".git").exists()) + assert((root / "modules" / "mpi-serial" / ".git").exists()) + assert((root / "modules" / "mpi-serial2" / ".git").exists()) + assert((root / "modules" / "mpi-sparse" / ".git").exists()) + assert((root / "modules" / "mpi-sparse" / "m4").exists()) + assert(not (root / "modules" / "mpi-sparse" / "README").exists()) + + From 82dd7a41d1b01092a968e2126f6f300f872d2812 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Thu, 4 Jul 2024 07:59:52 -0600 Subject: [PATCH 146/159] test_b_update now working --- git_fleximod/git_fleximod.py | 250 +++++++++-------------------------- git_fleximod/gitinterface.py | 6 +- 2 files changed, 70 insertions(+), 186 deletions(-) diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index 69da054d..505d7222 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -13,7 +13,7 @@ from git_fleximod import cli from git_fleximod.gitinterface import GitInterface from git_fleximod.gitmodules import GitModules -from configparser import NoOptionError +from git_fleximod.submodule import Submodule # logger variable is global logger = None @@ -265,132 +265,42 @@ def single_submodule_checkout( return -def add_remote(git, url): - remotes = git.git_operation("remote", "-v") - newremote = "newremote.00" - if url in remotes: - for line in remotes: - if url in line and "fetch" in line: - newremote = line.split()[0] - break - else: - i = 0 - while "newremote" in remotes: - i = i + 1 - newremote = f"newremote.{i:02d}" - git.git_operation("remote", "add", newremote, url) - return newremote - -def submodules_status(gitmodules, root_dir, toplevel=False): + +def init_submodule_from_gitmodules(gitmodules, name, root_dir, logger): + path = gitmodules.get(name, "path") + url = gitmodules.get(name, "url") + tag = gitmodules.get(name, "fxtag") + fxurl = gitmodules.get(name, "fxDONOTUSEurl") + fxsparse = gitmodules.get(name, "fxsparse") + 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): testfails = 0 localmods = 0 needsupdate = 0 + submodules = {} + wrapper = textwrap.TextWrapper(initial_indent=' '*depth, width=120,subsequent_indent=' '*(depth+20)) for name in gitmodules.sections(): - path = gitmodules.get(name, "path") - tag = gitmodules.get(name, "fxtag") - url = gitmodules.get(name, "url") - required = gitmodules.get(name, "fxrequired") - level = required and required.startswith("Top") - if not path: - utils.fatal_error("No path found in .gitmodules for {}".format(name)) - newpath = os.path.join(root_dir, path) - logger.debug("newpath is {}".format(newpath)) - if not os.path.exists(os.path.join(newpath, ".git")): - rootgit = GitInterface(root_dir, logger) - # submodule commands use path, not name - url = url.replace("git@github.com:", "https://github.com/") - tags = rootgit.git_operation("ls-remote", "--tags", url) - result = rootgit.git_operation("submodule","status",newpath).split() -# print(f"newpath {newpath} result {result}") - ahash = None - if result: - ahash = result[0][1:] - hhash = None - atag = None + if not submodules or name not in submodules: + submodules[name] = init_submodule_from_gitmodules(gitmodules, name, root_dir, logger) + + result,n,l,t = submodules[name].status() + testfails += t + localmods += l + needsupdate += n + if toplevel or not submodules[name].toplevel(): + print(wrapper.fill(result)) + subdir = os.path.join(root_dir, submodules[name].path) + if os.path.exists(os.path.join(subdir, ".gitmodules")): + submod = GitModules(logger, confpath=subdir) + t,l,n = submodules_status(submod, subdir, depth=depth+1) + testfails += t + localmods += l + needsupdate += n - needsupdate += 1 - if not toplevel and level: - continue - for htag in tags.split("\n"): - if htag.endswith('^{}'): - htag = htag[:-3] - if ahash and not atag and ahash in htag: - atag = (htag.split()[1])[10:] - if tag and not hhash and htag.endswith(tag): - hhash = htag.split()[0] - if hhash and atag: - break - optional = " (optional)" if required and "Optional" in required else "" -# print(f"tag is {tag} ahash is {ahash} {hhash} {atag} {newpath} {required} {level}") - if required and not required.startswith("Always") and not level: - continue - - if tag and (ahash == hhash or atag == tag): - print(f"e {name:>20} not checked out, aligned at tag {tag}{optional} {level} {required}") - elif tag: - ahash = rootgit.git_operation( - "submodule", "status", "{}".format(path) - ).rstrip() - ahash = ahash[1 : len(tag) + 1] - if tag == ahash: - print(f"e {name:>20} not checked out, aligned at hash {ahash}{optional}") - else: - print( - f"e {name:>20} not checked out, out of sync at tag {atag}, expected tag is {tag}{optional}" - ) - testfails += 1 - else: - print(f"e {name:>20} has no fxtag defined in .gitmodules{optional}") - testfails += 1 - else: - with utils.pushd(newpath): - git = GitInterface(newpath, logger) - atag = git.git_operation("describe", "--tags", "--always").rstrip() - ahash = git.git_operation("rev-list", "HEAD").partition("\n")[0] - rurl = git.git_operation("ls-remote","--get-url").rstrip() - recurse = False - if rurl != url: - remote = add_remote(git, url) - git.git_operation("fetch", remote) - if tag and atag == tag: - print(f" {name:>20} at tag {tag}") - recurse = True - elif tag and ahash[: len(tag)] == tag: - print(f" {name:>20} at hash {ahash}") - recurse = True - elif atag == ahash: - print(f" {name:>20} at hash {ahash}") - recurse = True - elif tag: - print( - f"s {name:>20} {atag} {ahash} is out of sync with .gitmodules {tag}" - ) - testfails += 1 - needsupdate += 1 - else: - print( - f"e {name:>20} has no fxtag defined in .gitmodules, module at {atag}" - ) - testfails += 1 - - status = git.git_operation("status", "--ignore-submodules", "-uno") - if "nothing to commit" not in status: - localmods = localmods + 1 - print("M" + textwrap.indent(status, " ")) - if recurse and os.path.exists(os.path.join(newpath, ".gitmodules")): - submodules = GitModules( - logger, - confpath=newpath, - ) - nf, lm, nu = submodules_status(submodules, newpath) - testfails += nf - localmods += lm - needsupdate += nu - - return testfails, localmods, needsupdate - def git_toplevelroot(root_dir, logger): rgit = GitInterface(root_dir, logger) superroot = rgit.git_operation("rev-parse", "--show-superproject-working-tree") @@ -398,25 +308,24 @@ def git_toplevelroot(root_dir, logger): def submodules_update(gitmodules, root_dir, requiredlist, force): - _, localmods, needsupdate = submodules_status(gitmodules, root_dir) - if localmods and not force: - local_mods_output() - return - if needsupdate == 0: - return - +# _, localmods, needsupdate = submodules_status(gitmodules, root_dir) +# if localmods and not force: +# local_mods_output() +# return +# if needsupdate == 0: +# return + submodules = {} for name in gitmodules.sections(): - fxtag = gitmodules.get(name, "fxtag") - path = gitmodules.get(name, "path") - url = gitmodules.get(name, "url") - - fxrequired = gitmodules.get(name, "fxrequired") - if fxrequired: - allowedvalues = fxrequired_allowed_values() - assert fxrequired in allowedvalues + if not submodules or name not in submodules: + submodules[name] = init_submodule_from_gitmodules(gitmodules, name, root_dir, logger) + _, needsupdate, localmods, testfails = submodules[name].status() + fxrequired = submodules[name].fxrequired + if not fxrequired: + fxrequired = "AlwaysRequired" + allowedvalues = fxrequired_allowed_values() + assert fxrequired in allowedvalues - fxsparse = gitmodules.get(name, "fxsparse") superroot = git_toplevelroot(root_dir, logger) if ( @@ -429,56 +338,27 @@ def submodules_update(gitmodules, root_dir, requiredlist, force): print(f"Skipping optional component {name:>20}") continue - if fxsparse: - logger.debug( - "Callng submodule_sparse_checkout({}, {}, {}, {}, {}, {}".format( - root_dir, name, url, path, fxsparse, fxtag - ) - ) - submodule_sparse_checkout(root_dir, name, url, path, fxsparse, tag=fxtag) - else: - logger.info( - "Calling submodule_checkout({},{},{},{})".format( - root_dir, name, path, url - ) - ) - print(f"fxrequired {fxrequired} requiredlist {requiredlist}") - single_submodule_checkout( - root_dir, - name, - path, - url=url, - tag=fxtag, - force=force, - optional="AlwaysOptional" in requiredlist - ) +# if fxsparse: +# logger.debug( +# "Callng submodule_sparse_checkout({}, {}, {}, {}, {}, {}".format( +# root_dir, name, url, path, fxsparse, fxtag +# ) +# ) +# submodule_sparse_checkout(root_dir, name, url, path, fxsparse, tag=fxtag) +# else: + print(f"fxrequired {fxrequired} requiredlist {requiredlist}") + submodules[name].update(optional="AlwaysOptional") + +# single_submodule_checkout( +# root_dir, +# name, +# path, +# url=url, +# tag=fxtag, +# force=force, +# optional="AlwaysOptional" in requiredlist +# ) - if os.path.exists(os.path.join(path, ".git")): - submoddir = os.path.join(root_dir, path) - with utils.pushd(submoddir): - git = GitInterface(submoddir, logger) - # first make sure the url is correct - upstream = git.git_operation("ls-remote", "--get-url").rstrip() - newremote = "origin" - if upstream != url: - add_remote(git, url) - - tags = git.git_operation("tag", "-l") - if fxtag and fxtag not in tags: - git.git_operation("fetch", newremote, "--tags") - atag = git.git_operation("describe", "--tags", "--always").rstrip() - if fxtag and fxtag != atag: - try: - git.git_operation("checkout", fxtag) - print(f"{name:>20} updated to {fxtag}") - except Exception as error: - print(error) - - - elif not fxtag: - print(f"No fxtag found for submodule {name:>20}") - else: - print(f"{name:>20} up to date.") diff --git a/git_fleximod/gitinterface.py b/git_fleximod/gitinterface.py index 93ae38ec..c7462b3a 100644 --- a/git_fleximod/gitinterface.py +++ b/git_fleximod/gitinterface.py @@ -62,7 +62,11 @@ def git_operation(self, operation, *args, **kwargs): def config_get_value(self, section, name): if self._use_module: config = self.repo.config_reader() - return config.get_value(section, name) + try: + val = config.get_value(section, name) + except: + val = None + return val else: cmd = ("git", "-C", str(self.repo_path), "config", "--get", f"{section}.{name}") output = utils.execute_subprocess(cmd, output_to_caller=True) From 72211d20b74babf0f9996391a1f7f7f4091f17c2 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Thu, 4 Jul 2024 08:44:48 -0600 Subject: [PATCH 147/159] add the new submodule class --- git_fleximod/submodule.py | 329 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 329 insertions(+) create mode 100644 git_fleximod/submodule.py diff --git a/git_fleximod/submodule.py b/git_fleximod/submodule.py new file mode 100644 index 00000000..f1c1b629 --- /dev/null +++ b/git_fleximod/submodule.py @@ -0,0 +1,329 @@ +import os +import textwrap +import shutil +from configparser import NoOptionError +from git_fleximod import utils +from git_fleximod.gitinterface import GitInterface + +class Submodule(): + def __init__(self, root_dir, name, path, url, fxtag=None, fxurl=None, fxsparse=None, fxrequired=None, logger=None): + self.name = name + self.root_dir = root_dir + self.path = path + url = url.replace("git@github.com:", "https://github.com/") + self.url = url + self.fxurl = fxurl + self.fxtag = fxtag + self.fxsparse = fxsparse + self.fxrequired = fxrequired + self.logger = logger + + def status(self): + smpath = os.path.join(self.root_dir, self.path) + testfails = 0 + localmods = 0 + needsupdate = 0 + ahash = None + optional = " (optional)" if "Optional" in self.fxrequired else None + required = None + level = None + if not os.path.exists(os.path.join(smpath, ".git")): + rootgit = GitInterface(self.root_dir, self.logger) + # submodule commands use path, not name + tags = rootgit.git_operation("ls-remote", "--tags", self.url) + result = rootgit.git_operation("submodule","status",smpath).split() + + if result: + ahash = result[0][1:] + hhash = None + atag = None + for htag in tags.split("\n"): + if htag.endswith('^{}'): + htag = htag[:-3] + if ahash and not atag and ahash in htag: + atag = (htag.split()[1])[10:] + if self.fxtag and not hhash and htag.endswith(self.fxtag): + hhash = htag.split()[0] + 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} {level} {required}" + elif self.fxtag: + ahash = rootgit.git_operation( + "submodule", "status", "{}".format(self.path) + ).rstrip() + 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}" + else: + result = f"e {self.name:>20} not checked out, out of sync at tag {atag}, expected tag is {self.fxtag}{optional}" + testfails += 1 + else: + result = f"e {self.name:>20} has no fxtag defined in .gitmodules{optional}" + testfails += 1 + else: + with utils.pushd(smpath): + git = GitInterface(smpath, self.logger) + remote = git.git_operation("remote").rstrip() + if remote == '': + result = f"e {self.name:>20} has no associated remote" + testfails += 1 + needsupdate += 1 + return result, needsupdate, localmods, testfails + rurl = git.git_operation("ls-remote","--get-url").rstrip() + atag = git.git_operation("describe", "--tags", "--always").rstrip() + ahash = git.git_operation("rev-list", "HEAD").partition("\n")[0] + + recurse = False + if rurl != self.url: + remote = self._add_remote(git) + git.git_operation("fetch", remote) + if self.fxtag and atag == self.fxtag: + result = f" {self.name:>20} at tag {self.fxtag}" + recurse = True + elif self.fxtag and ahash[: len(self.fxtag)] == self.fxtag: + result = f" {self.name:>20} at hash {ahash}" + recurse = True + elif atag == ahash: + result = f" {self.name:>20} at hash {ahash}" + recurse = True + elif self.fxtag: + result = f"s {self.name:>20} {atag} {ahash} is out of sync with .gitmodules {self.fxtag}" + testfails += 1 + needsupdate += 1 + else: + result = f"e {self.name:>20} has no fxtag defined in .gitmodules, module at {atag}" + testfails += 1 + + status = git.git_operation("status", "--ignore-submodules", "-uno") + if "nothing to commit" not in status: + localmods = localmods + 1 + result = "M" + textwrap.indent(status, " ") + + return result, needsupdate, localmods, testfails + + + def _add_remote(self, git): + remotes = git.git_operation("remote", "-v").splitlines() + upstream = None + if remotes: + upstream = git.git_operation("ls-remote", "--get-url").rstrip() + newremote = "newremote.00" + + line = next((s for s in remotes if self.url in s), None) + if line: + newremote = line.split()[0] + return newremote + else: + i = 0 + while "newremote" in remotes: + i = i + 1 + newremote = f"newremote.{i:02d}" + else: + newremote = "origin" + git.git_operation("remote", "add", newremote, self.url) + return newremote + + def toplevel(self): + if self.fxrequired: + return True if self.fxrequired.startswith("Top") else False + + def sparse_checkout(self): + """ + This function performs a sparse checkout of a git submodule. It does so by first creating the .git/info/sparse-checkout fileq + in the submodule and then checking out the desired tag. If the submodule is already checked out, it will not be checked out again. + Creating the sparse-checkout file first prevents the entire submodule from being checked out and then removed. This is important + because the submodule may have a large number of files and checking out the entire submodule and then removing it would be time + and disk space consuming. + + Returns: + None + """ + self.logger.info("Called sparse_checkout for {}".format(self.name)) + rgit = GitInterface(self.root_dir, self.logger) + superroot = rgit.git_operation("rev-parse", "--show-superproject-working-tree") + if superroot: + gitroot = superroot.strip() + else: + gitroot = self.root_dir.strip() + assert os.path.isdir(os.path.join(gitroot, ".git")) + # first create the module directory + if not os.path.isdir(os.path.join(self.root_dir, self.path)): + os.makedirs(os.path.join(self.root_dir, self.path)) + + # initialize a new git repo and set the sparse checkout flag + sprep_repo = os.path.join(self.root_dir, self.path) + sprepo_git = GitInterface(sprep_repo, self.logger) + if os.path.exists(os.path.join(sprep_repo, ".git")): + try: + self.logger.info("Submodule {} found".format(self.name)) + chk = sprepo_git.config_get_value("core", "sparseCheckout") + if chk == "true": + self.logger.info("Sparse submodule {} already checked out".format(self.name)) + return + except (NoOptionError): + self.logger.debug("Sparse submodule {} not present".format(self.name)) + except Exception as e: + utils.fatal_error("Unexpected error {} occured.".format(e)) + + sprepo_git.config_set_value("core", "sparseCheckout", "true") + + # set the repository remote + + self.logger.info("Setting remote origin in {}/{}".format(self.root_dir, self.path)) + status = sprepo_git.git_operation("remote", "-v") + if self.url not in status: + sprepo_git.git_operation("remote", "add", "origin", self.url) + + topgit = os.path.join(gitroot, ".git") + + if gitroot != self.root_dir and os.path.isfile(os.path.join(self.root_dir, ".git")): + with open(os.path.join(self.root_dir, ".git")) as f: + gitpath = os.path.relpath( + os.path.join(self.root_dir, f.read().split()[1]), + start=os.path.join(self.root_dir, self.path), + ) + topgit = os.path.join(gitpath, "modules") + else: + topgit = os.path.relpath( + os.path.join(self.root_dir, ".git", "modules"), + start=os.path.join(self.root_dir, self.path), + ) + + with utils.pushd(sprep_repo): + if not os.path.isdir(topgit): + os.makedirs(topgit) + topgit += os.sep + self.name + + if os.path.isdir(os.path.join(self.root_dir, self.path, ".git")): + with utils.pushd(sprep_repo): + if os.path.isdir(os.path.join(topgit,".git")): + shutil.rmtree(os.path.join(topgit,".git")) + shutil.move(".git", topgit) + with open(".git", "w") as f: + f.write("gitdir: " + os.path.relpath(topgit)) + # assert(os.path.isdir(os.path.relpath(topgit, start=sprep_repo))) + gitsparse = os.path.abspath(os.path.join(topgit, "info", "sparse-checkout")) + if os.path.isfile(gitsparse): + self.logger.warning( + "submodule {} is already initialized {}".format(self.name, topgit) + ) + return + + with utils.pushd(sprep_repo): + if os.path.isfile(self.fxsparse): + shutil.copy(self.fxsparse, gitsparse) + + + # Finally checkout the repo + sprepo_git.git_operation("fetch", "origin", "--tags") + sprepo_git.git_operation("checkout", self.fxtag) + + print(f"Successfully checked out {self.name:>20} at {self.fxtag}") + rgit.config_set_value(f'submodule "{self.name}"', "active", "true") + rgit.config_set_value(f'submodule "{self.name}"', "url", self.url) + + def update(self, optional=None): + # function implementation... + git = GitInterface(self.root_dir, self.logger) + repodir = os.path.join(self.root_dir, self.path) + self.logger.info("Checkout {} into {}/{}".format(self.name, self.root_dir, self.path)) + # if url is provided update to the new url + tmpurl = None + repo_exists = False + # if os.path.exists(os.path.join(repodir, ".git")): + # self.logger.info("Submodule {} already checked out".format(self.name)) + # repo_exists = True + # Look for a .gitmodules file in the newly checkedout repo + if self.fxsparse: + print(f"Sparse checkout {self.name} fxsparse {self.fxsparse}") + self.sparse_checkout() + else: + if not repo_exists and self.url: + # ssh urls cause problems for those who dont have git accounts with ssh keys defined + # but cime has one since e3sm prefers ssh to https, because the .gitmodules file was + # opened with a GitModules object we don't need to worry about restoring the file here + # it will be done by the GitModules class + if self.url.startswith("git@"): + tmpurl = self.url + url = self.url.replace("git@github.com:", "https://github.com/") + git.git_operation("clone", url, self.path) + smgit = GitInterface(repodir, self.logger) + if not tag: + tag = smgit.git_operation("describe", "--tags", "--always").rstrip() + smgit.git_operation("checkout", tag) + # Now need to move the .git dir to the submodule location + rootdotgit = os.path.join(self.root_dir, ".git") + if os.path.isfile(rootdotgit): + with open(rootdotgit) as f: + line = f.readline() + if line.startswith("gitdir: "): + rootdotgit = line[8:].rstrip() + + newpath = os.path.abspath(os.path.join(self.root_dir, rootdotgit, "modules", self.name)) + if os.path.exists(newpath): + shutil.rmtree(os.path.join(repodir, ".git")) + else: + shutil.move(os.path.join(repodir, ".git"), newpath) + + with open(os.path.join(repodir, ".git"), "w") as f: + f.write("gitdir: " + os.path.relpath(newpath, start=repodir)) + + if not os.path.exists(repodir): + parent = os.path.dirname(repodir) + if not os.path.isdir(parent): + os.makedirs(parent) + git.git_operation("submodule", "add", "--name", self.name, "--", url, self.path) + + if not repo_exists or not tmpurl: + git.git_operation("submodule", "update", "--init", "--", self.path) + + if self.fxtag and not optional: + smgit = GitInterface(repodir, self.logger) + smgit.git_operation("checkout", self.fxtag) + + if os.path.exists(os.path.join(repodir, ".gitmodules")): + # recursively handle this checkout + print(f"Recursively checking out submodules of {self.name} {optional}") + gitmodules = GitModules(self.logger, confpath=repodir) + requiredlist = ["AlwaysRequired"] + if optional: + requiredlist.append("AlwaysOptional") + submodules_checkout(gitmodules, repodir, requiredlist, force=force) + if not os.path.exists(os.path.join(repodir, ".git")): + utils.fatal_error( + f"Failed to checkout {self.name} {repo_exists} {tmpurl} {repodir} {self.path}" + ) + + if tmpurl: + print(git.git_operation("restore", ".gitmodules")) + if os.path.exists(os.path.join(self.path, ".git")): + submoddir = os.path.join(self.root_dir, self.path) + with utils.pushd(submoddir): + git = GitInterface(submoddir, self.logger) + # first make sure the url is correct + print("3calling ls-remote") + + newremote = self._add_remote(git) + + tags = git.git_operation("tag", "-l") + fxtag = self.fxtag + if fxtag and fxtag not in tags: + git.git_operation("fetch", newremote, "--tags") + atag = git.git_operation("describe", "--tags", "--always").rstrip() + if fxtag and fxtag != atag: + try: + git.git_operation("checkout", fxtag) + print(f"{self.name:>20} updated to {fxtag}") + except Exception as error: + print(error) + + + elif not fxtag: + print(f"No fxtag found for submodule {self.name:>20}") + else: + print(f"{self.name:>20} up to date.") + + + + return From 2b021fdd76e31b4c7addaae97db0241d557183a2 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Thu, 4 Jul 2024 09:08:45 -0600 Subject: [PATCH 148/159] add documentation to submodule.py --- git_fleximod/submodule.py | 82 +++++++++++++++++++++++++++++++++++---- 1 file changed, 74 insertions(+), 8 deletions(-) diff --git a/git_fleximod/submodule.py b/git_fleximod/submodule.py index f1c1b629..dc27c3d0 100644 --- a/git_fleximod/submodule.py +++ b/git_fleximod/submodule.py @@ -6,7 +6,24 @@ from git_fleximod.gitinterface import GitInterface class Submodule(): + """ + Represents a Git submodule with enhanced features for flexible management. + + Attributes: + name (str): The name of the submodule. + root_dir (str): The root directory of the main project. + path (str): The relative path from the root directory to the submodule. + url (str): The URL of the submodule repository. + fxurl (str): The URL for flexible submodule management (optional). + fxtag (str): The tag for flexible submodule management (optional). + fxsparse (str): Path to the sparse checkout file relative to the submodule path, see git-sparse-checkout for details (optional). + fxrequired (str): Indicates if the submodule is optional or required (optional). + logger (logging.Logger): Logger instance for logging (optional). + """ def __init__(self, root_dir, name, path, url, fxtag=None, fxurl=None, fxsparse=None, fxrequired=None, logger=None): + """ + Initializes a new Submodule instance with the provided attributes. + """ self.name = name self.root_dir = root_dir self.path = path @@ -19,6 +36,13 @@ def __init__(self, root_dir, name, path, url, fxtag=None, fxurl=None, fxsparse=N self.logger = logger def status(self): + """ + Checks the status of the submodule and returns 4 parameters: + - result (str): The status of the submodule. + - needsupdate (int): An indicator if the submodule needs to be updated. + - localmods (int): An indicator if the submodule has local modifications. + - testfails (int): An indicator if the submodule has failed a test, this is used for testing purposes. + """ smpath = os.path.join(self.root_dir, self.path) testfails = 0 localmods = 0 @@ -104,6 +128,19 @@ def status(self): def _add_remote(self, git): + """ + Adds a new remote to the submodule if it does not already exist. + + This method checks the existing remotes of the submodule. If the submodule's URL is not already listed as a remote, + it attempts to add a new remote. The name for the new remote is generated dynamically to avoid conflicts. If no + remotes exist, it defaults to naming the new remote 'origin'. + + Args: + git (GitInterface): An instance of GitInterface to perform git operations. + + Returns: + str: The name of the new remote if added, or the name of the existing remote that matches the submodule's URL. + """ remotes = git.git_operation("remote", "-v").splitlines() upstream = None if remotes: @@ -125,20 +162,27 @@ def _add_remote(self, git): return newremote def toplevel(self): + """ + Checks if the submodule is a top-level submodule (ie not a submodule of a submodule). + """ if self.fxrequired: return True if self.fxrequired.startswith("Top") else False def sparse_checkout(self): """ - This function performs a sparse checkout of a git submodule. It does so by first creating the .git/info/sparse-checkout fileq - in the submodule and then checking out the desired tag. If the submodule is already checked out, it will not be checked out again. - Creating the sparse-checkout file first prevents the entire submodule from being checked out and then removed. This is important - because the submodule may have a large number of files and checking out the entire submodule and then removing it would be time - and disk space consuming. + Performs a sparse checkout of the submodule. - Returns: + This method optimizes the checkout process by only checking out files specified in the submodule's sparse-checkout configuration, + rather than the entire submodule content. It achieves this by first ensuring the `.git/info/sparse-checkout` file is created and + configured in the submodule's directory. Then, it proceeds to checkout the desired tag. If the submodule has already been checked out, + this method will not perform the checkout again. + + This approach is particularly beneficial for submodules with a large number of files, as it significantly reduces the time and disk space + required for the checkout process by avoiding the unnecessary checkout and subsequent removal of unneeded files. + + Returns: None - """ + """ self.logger.info("Called sparse_checkout for {}".format(self.name)) rgit = GitInterface(self.root_dir, self.logger) superroot = rgit.git_operation("rev-parse", "--show-superproject-working-tree") @@ -224,7 +268,29 @@ def sparse_checkout(self): rgit.config_set_value(f'submodule "{self.name}"', "url", self.url) def update(self, optional=None): - # function implementation... + """ + Updates the submodule to the latest or specified version. + + This method handles the update process of the submodule, including checking out the submodule into the specified path, + handling sparse checkouts if configured, and updating the submodule's URL if necessary. It supports both SSH and HTTPS URLs, + automatically converting SSH URLs to HTTPS to avoid issues for users without SSH keys. + + The update process involves the following steps: + 1. If the submodule is configured for sparse checkout, it performs a sparse checkout. + 2. If the submodule is not already checked out, it clones the submodule using the provided URL. + 3. If a specific tag or hash is provided, it checks out that tag; otherwise, it checks out the latest version. + 4. If the root `.git` is a file (indicating a submodule or a worktree), additional steps are taken to integrate the submodule properly. + + Args: + optional (bool): Indicates if the submodule is optional. This parameter is currently unused in the function but can be implemented for conditional updates based on the submodule's importance. + + Note: + - The method currently does not use the `optional` parameter, but it is designed for future use where updates can be conditional based on the submodule's importance. + - SSH URLs are automatically converted to HTTPS to accommodate users without SSH keys. + + Returns: + None + """ git = GitInterface(self.root_dir, self.logger) repodir = os.path.join(self.root_dir, self.path) self.logger.info("Checkout {} into {}/{}".format(self.name, self.root_dir, self.path)) From 143abac699cd344b978b3fcd4fc76b9e21c409a5 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Thu, 4 Jul 2024 10:19:56 -0600 Subject: [PATCH 149/159] tests a-d all pass --- git_fleximod/git_fleximod.py | 204 ++++------------------------------- git_fleximod/submodule.py | 69 +++++------- 2 files changed, 47 insertions(+), 226 deletions(-) diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index 505d7222..e3adddb8 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -175,97 +175,6 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master rgit.config_set_value(f'submodule "{name}"', "active", "true") rgit.config_set_value(f'submodule "{name}"', "url", url) - -def single_submodule_checkout( - root, name, path, url=None, tag=None, force=False, optional=False -): - """ - This function checks out a single git submodule. - - Parameters: - root (str): The root directory for the git operation. - name (str): The name of the submodule. - path (str): The path to the submodule. - url (str, optional): The URL of the submodule. Defaults to None. - tag (str, optional): The tag to checkout. Defaults to None. - force (bool, optional): If set to True, forces the checkout operation. Defaults to False. - optional (bool, optional): If set to True, the submodule is considered optional. Defaults to False. - - Returns: - None - """ - # function implementation... - git = GitInterface(root, logger) - repodir = os.path.join(root, path) - logger.info("Checkout {} into {}/{}".format(name, root, path)) - # if url is provided update to the new url - tmpurl = None - repo_exists = False -# if os.path.exists(os.path.join(repodir, ".git")): -# logger.info("Submodule {} already checked out".format(name)) -# repo_exists = True - # Look for a .gitmodules file in the newly checkedout repo - if not repo_exists and url: - # ssh urls cause problems for those who dont have git accounts with ssh keys defined - # but cime has one since e3sm prefers ssh to https, because the .gitmodules file was - # opened with a GitModules object we don't need to worry about restoring the file here - # it will be done by the GitModules class - if url.startswith("git@"): - tmpurl = url - url = url.replace("git@github.com:", "https://github.com/") - git.git_operation("clone", url, path) - smgit = GitInterface(repodir, logger) - if not tag: - tag = smgit.git_operation("describe", "--tags", "--always").rstrip() - smgit.git_operation("checkout", tag) - # Now need to move the .git dir to the submodule location - rootdotgit = os.path.join(root, ".git") - if os.path.isfile(rootdotgit): - with open(rootdotgit) as f: - line = f.readline() - if line.startswith("gitdir: "): - rootdotgit = line[8:].rstrip() - - newpath = os.path.abspath(os.path.join(root, rootdotgit, "modules", name)) - if os.path.exists(newpath): - shutil.rmtree(os.path.join(repodir, ".git")) - else: - shutil.move(os.path.join(repodir, ".git"), newpath) - - with open(os.path.join(repodir, ".git"), "w") as f: - f.write("gitdir: " + os.path.relpath(newpath, start=repodir)) - - if not os.path.exists(repodir): - parent = os.path.dirname(repodir) - if not os.path.isdir(parent): - os.makedirs(parent) - git.git_operation("submodule", "add", "--name", name, "--", url, path) - - if not repo_exists or not tmpurl: - git.git_operation("submodule", "update", "--init", "--", path) - if tag and not optional: - smgit = GitInterface(repodir, logger) - smgit.git_operation("checkout", tag) - - if os.path.exists(os.path.join(repodir, ".gitmodules")): - # recursively handle this checkout - print(f"Recursively checking out submodules of {name} {optional}") - gitmodules = GitModules(logger, confpath=repodir) - requiredlist = ["AlwaysRequired"] - if optional: - requiredlist.append("AlwaysOptional") - submodules_checkout(gitmodules, repodir, requiredlist, force=force) - if not os.path.exists(os.path.join(repodir, ".git")): - utils.fatal_error( - f"Failed to checkout {name} {repo_exists} {tmpurl} {repodir} {path}" - ) - - if tmpurl: - print(git.git_operation("restore", ".gitmodules")) - - return - - def init_submodule_from_gitmodules(gitmodules, name, root_dir, logger): path = gitmodules.get(name, "path") url = gitmodules.get(name, "url") @@ -280,7 +189,7 @@ def submodules_status(gitmodules, root_dir, toplevel=False, depth=0): localmods = 0 needsupdate = 0 submodules = {} - wrapper = textwrap.TextWrapper(initial_indent=' '*depth, width=120,subsequent_indent=' '*(depth+20)) + wrapper = textwrap.TextWrapper(initial_indent=' '*(depth*10), width=120,subsequent_indent=' '*(depth*20)) for name in gitmodules.sections(): if not submodules or name not in submodules: submodules[name] = init_submodule_from_gitmodules(gitmodules, name, root_dir, logger) @@ -306,23 +215,15 @@ def git_toplevelroot(root_dir, logger): superroot = rgit.git_operation("rev-parse", "--show-superproject-working-tree") return superroot - def submodules_update(gitmodules, root_dir, requiredlist, force): -# _, localmods, needsupdate = submodules_status(gitmodules, root_dir) -# if localmods and not force: -# local_mods_output() -# return -# if needsupdate == 0: -# return submodules = {} for name in gitmodules.sections(): if not submodules or name not in submodules: submodules[name] = init_submodule_from_gitmodules(gitmodules, name, root_dir, logger) _, needsupdate, localmods, testfails = submodules[name].status() - fxrequired = submodules[name].fxrequired - if not fxrequired: - fxrequired = "AlwaysRequired" - + if not submodules[name].fxrequired: + submodules[name].fxrequired = "AlwaysRequired" + fxrequired = submodules[name].fxrequired allowedvalues = fxrequired_allowed_values() assert fxrequired in allowedvalues @@ -334,32 +235,24 @@ def submodules_update(gitmodules, root_dir, requiredlist, force): or fxrequired not in requiredlist) ): if "Optional" in fxrequired and "Optional" not in requiredlist: + print(f"Skipping optional component {name:>20}") if fxrequired.startswith("Always"): print(f"Skipping optional component {name:>20}") continue - -# if fxsparse: -# logger.debug( -# "Callng submodule_sparse_checkout({}, {}, {}, {}, {}, {}".format( -# root_dir, name, url, path, fxsparse, fxtag -# ) -# ) -# submodule_sparse_checkout(root_dir, name, url, path, fxsparse, tag=fxtag) -# else: - print(f"fxrequired {fxrequired} requiredlist {requiredlist}") - submodules[name].update(optional="AlwaysOptional") - -# single_submodule_checkout( -# root_dir, -# name, -# path, -# url=url, -# tag=fxtag, -# force=force, -# optional="AlwaysOptional" in requiredlist -# ) - - + optional = "AlwaysOptional" in requiredlist + print(f"1 Required list is {requiredlist} optional is {optional}") + if fxrequired in requiredlist: + submodules[name].update() + repodir = os.path.join(root_dir, submodules[name].path) + if os.path.exists(os.path.join(repodir, ".gitmodules")): + # recursively handle this checkout + print(f"Recursively checking out submodules of {name}") + gitmodules = GitModules(submodules[name].logger, confpath=repodir) + requiredlist = ["AlwaysRequired"] + if optional: + requiredlist.append("AlwaysOptional") + print(f"2 Required list is {requiredlist}") + submodules_update(gitmodules, repodir, requiredlist, force=force) def local_mods_output(): @@ -375,65 +268,6 @@ def local_mods_output(): """ print(text) -# checkout is done by update if required so this function may be depricated -def submodules_checkout(gitmodules, root_dir, requiredlist, force=False): - """ - This function checks out all git submodules based on the provided parameters. - - Parameters: - gitmodules (ConfigParser): The gitmodules configuration. - root_dir (str): The root directory for the git operation. - requiredlist (list): The list of required modules. - force (bool, optional): If set to True, forces the checkout operation. Defaults to False. - - Returns: - None - """ - # function implementation... - print("") - _, localmods, needsupdate = submodules_status(gitmodules, root_dir) - if localmods and not force: - local_mods_output() - return - if not needsupdate: - return - for name in gitmodules.sections(): - fxrequired = gitmodules.get(name, "fxrequired") - fxsparse = gitmodules.get(name, "fxsparse") - fxtag = gitmodules.get(name, "fxtag") - path = gitmodules.get(name, "path") - url = gitmodules.get(name, "url") - superroot = git_toplevelroot(root_dir, logger) - if fxrequired and fxrequired not in requiredlist: - if fxrequired == "AlwaysOptional" or (superroot == root_dir): - print(f"Skipping optional component {name:>20} {requiredlist}") - continue - - - if fxsparse: - logger.debug( - "Callng submodule_sparse_checkout({}, {}, {}, {}, {}, {}".format( - root_dir, name, url, path, fxsparse, fxtag - ) - ) - submodule_sparse_checkout(root_dir, name, url, path, fxsparse, tag=fxtag) - else: - logger.debug( - "Calling submodule_checkout({},{},{})".format(root_dir, name, path) - ) - single_submodule_checkout( - root_dir, - name, - path, - url=url, - tag=fxtag, - force=force, - optional="AlwaysOptional" == fxrequired \ - or "ToplevelOptional" == fxrequired \ - or "TopLevelOptional" == fxrequired - ) - - def submodules_test(gitmodules, root_dir): """ This function tests the git submodules based on the provided parameters. diff --git a/git_fleximod/submodule.py b/git_fleximod/submodule.py index dc27c3d0..a3e5624e 100644 --- a/git_fleximod/submodule.py +++ b/git_fleximod/submodule.py @@ -32,23 +32,28 @@ def __init__(self, root_dir, name, path, url, fxtag=None, fxurl=None, fxsparse=N self.fxurl = fxurl self.fxtag = fxtag self.fxsparse = fxsparse - self.fxrequired = fxrequired + if fxrequired: + self.fxrequired = fxrequired + else: + self.fxrequired = "AlwaysRequired" self.logger = logger def status(self): """ Checks the status of the submodule and returns 4 parameters: - result (str): The status of the submodule. - - needsupdate (int): An indicator if the submodule needs to be updated. - - localmods (int): An indicator if the submodule has local modifications. - - testfails (int): An indicator if the submodule has failed a test, this is used for testing purposes. + - 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. """ smpath = os.path.join(self.root_dir, self.path) - testfails = 0 - localmods = 0 - needsupdate = 0 + testfails = False + localmods = False + needsupdate = False ahash = None - optional = " (optional)" if "Optional" in self.fxrequired else None + optional = "" + if "Optional" in self.fxrequired: + optional = " (optional)" required = None level = None if not os.path.exists(os.path.join(smpath, ".git")): @@ -71,7 +76,8 @@ 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} {level} {required}" + result = f"e {self.name:>20} not checked out, aligned at tag {self.fxtag}{optional}" + needsupdate = True elif self.fxtag: ahash = rootgit.git_operation( "submodule", "status", "{}".format(self.path) @@ -81,18 +87,19 @@ def status(self): result = f"e {self.name:>20} 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}" - testfails += 1 + testfails = True + needsupdate = True else: result = f"e {self.name:>20} has no fxtag defined in .gitmodules{optional}" - testfails += 1 + testfails = True else: with utils.pushd(smpath): git = GitInterface(smpath, self.logger) remote = git.git_operation("remote").rstrip() if remote == '': result = f"e {self.name:>20} has no associated remote" - testfails += 1 - needsupdate += 1 + testfails = True + needsupdate = True return result, needsupdate, localmods, testfails rurl = git.git_operation("ls-remote","--get-url").rstrip() atag = git.git_operation("describe", "--tags", "--always").rstrip() @@ -113,15 +120,15 @@ def status(self): recurse = True elif self.fxtag: result = f"s {self.name:>20} {atag} {ahash} is out of sync with .gitmodules {self.fxtag}" - testfails += 1 - needsupdate += 1 + testfails = True + needsupdate = True else: result = f"e {self.name:>20} has no fxtag defined in .gitmodules, module at {atag}" - testfails += 1 + testfails = True status = git.git_operation("status", "--ignore-submodules", "-uno") if "nothing to commit" not in status: - localmods = localmods + 1 + localmods = True result = "M" + textwrap.indent(status, " ") return result, needsupdate, localmods, testfails @@ -161,13 +168,6 @@ def _add_remote(self, git): git.git_operation("remote", "add", newremote, self.url) return newremote - def toplevel(self): - """ - Checks if the submodule is a top-level submodule (ie not a submodule of a submodule). - """ - if self.fxrequired: - return True if self.fxrequired.startswith("Top") else False - def sparse_checkout(self): """ Performs a sparse checkout of the submodule. @@ -267,7 +267,7 @@ def sparse_checkout(self): rgit.config_set_value(f'submodule "{self.name}"', "active", "true") rgit.config_set_value(f'submodule "{self.name}"', "url", self.url) - def update(self, optional=None): + def update(self): """ Updates the submodule to the latest or specified version. @@ -282,10 +282,8 @@ def update(self, optional=None): 4. If the root `.git` is a file (indicating a submodule or a worktree), additional steps are taken to integrate the submodule properly. Args: - optional (bool): Indicates if the submodule is optional. This parameter is currently unused in the function but can be implemented for conditional updates based on the submodule's importance. - + None Note: - - The method currently does not use the `optional` parameter, but it is designed for future use where updates can be conditional based on the submodule's importance. - SSH URLs are automatically converted to HTTPS to accommodate users without SSH keys. Returns: @@ -339,23 +337,15 @@ def update(self, optional=None): parent = os.path.dirname(repodir) if not os.path.isdir(parent): os.makedirs(parent) - git.git_operation("submodule", "add", "--name", self.name, "--", url, self.path) + git.git_operation("submodule", "add", "--name", self.name, "--", self.url, self.path) if not repo_exists or not tmpurl: git.git_operation("submodule", "update", "--init", "--", self.path) - if self.fxtag and not optional: + if self.fxtag: smgit = GitInterface(repodir, self.logger) smgit.git_operation("checkout", self.fxtag) - if os.path.exists(os.path.join(repodir, ".gitmodules")): - # recursively handle this checkout - print(f"Recursively checking out submodules of {self.name} {optional}") - gitmodules = GitModules(self.logger, confpath=repodir) - requiredlist = ["AlwaysRequired"] - if optional: - requiredlist.append("AlwaysOptional") - submodules_checkout(gitmodules, repodir, requiredlist, force=force) if not os.path.exists(os.path.join(repodir, ".git")): utils.fatal_error( f"Failed to checkout {self.name} {repo_exists} {tmpurl} {repodir} {self.path}" @@ -368,10 +358,7 @@ def update(self, optional=None): with utils.pushd(submoddir): git = GitInterface(submoddir, self.logger) # first make sure the url is correct - print("3calling ls-remote") - newremote = self._add_remote(git) - tags = git.git_operation("tag", "-l") fxtag = self.fxtag if fxtag and fxtag not in tags: From 62a338b531bbf858b5d941d4f8f7af5166328dcf Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Thu, 4 Jul 2024 10:33:31 -0600 Subject: [PATCH 150/159] all pytests now passing --- git_fleximod/submodule.py | 6 ++++++ tests/test_e_complex_update.py | 9 +++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/git_fleximod/submodule.py b/git_fleximod/submodule.py index a3e5624e..68fb08d6 100644 --- a/git_fleximod/submodule.py +++ b/git_fleximod/submodule.py @@ -168,6 +168,12 @@ def _add_remote(self, git): git.git_operation("remote", "add", newremote, self.url) return newremote + def toplevel(self): + """ + Returns True if the submodule is Toplevel (either Required or Optional) + """ + return True if "Top" in self.fxrequired else False + def sparse_checkout(self): """ Performs a sparse checkout of the submodule. diff --git a/tests/test_e_complex_update.py b/tests/test_e_complex_update.py index 44998f18..0c3ab4c6 100644 --- a/tests/test_e_complex_update.py +++ b/tests/test_e_complex_update.py @@ -9,7 +9,6 @@ def test_complex_update(git_fleximod, complex_update, logger): assert("AlwaysRequired not checked out, aligned at tag MPIserial_2.4.0" in status.stdout) assert("Complex not checked out, out of sync at tag testtag02, expected tag is testtag3" in status.stdout) assert("AlwaysOptional not checked out, out of sync at tag None, expected tag is MPIserial_2.3.0" in status.stdout) - print(f"status before is {status.stdout}") # This should checkout and update test_submodule and complex_sub result = git_fleximod(complex_update, "update") @@ -20,7 +19,7 @@ def test_complex_update(git_fleximod, complex_update, logger): assert("ToplevelRequired at tag MPIserial_2.5.0" in status.stdout) assert("AlwaysRequired at tag MPIserial_2.4.0" in status.stdout) assert("Complex at tag testtag3" in status.stdout) - print(f"status after is {status.stdout}") + # now check the complex_sub root = (complex_update / "modules" / "complex") assert(not (root / "libraries" / "gptl" / ".git").exists()) @@ -59,9 +58,11 @@ def test_complex_update(git_fleximod, complex_update, logger): root = (complex_update / "modules" / "complex" ) assert(not (root / "libraries" / "gptl" / ".git").exists()) assert(not (root / "libraries" / "mpi-serial" / ".git").exists()) - assert((root / "modules" / "mpi-serial" / ".git").exists()) - assert((root / "modules" / "mpi-serial2" / ".git").exists()) + assert(not (root / "modules" / "mpi-serial" / ".git").exists()) + assert((root / "modules" / "mpi-serialAR" / ".git").exists()) + assert((root / "modules" / "mpi-serialSAR" / ".git").exists()) assert((root / "modules" / "mpi-sparse" / ".git").exists()) + assert((root / "modules" / "mpi-serial2" / ".git").exists()) assert((root / "modules" / "mpi-sparse" / "m4").exists()) assert(not (root / "modules" / "mpi-sparse" / "README").exists()) From ded91fda0c896e5967b6df8dd2d18895f9dae096 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Thu, 4 Jul 2024 12:15:29 -0600 Subject: [PATCH 151/159] all tests passing, issue #50 fixed --- git_fleximod/git_fleximod.py | 15 ++++++++------- git_fleximod/submodule.py | 13 +++++++++++-- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index e3adddb8..c4e595d8 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -215,11 +215,13 @@ def git_toplevelroot(root_dir, logger): superroot = rgit.git_operation("rev-parse", "--show-superproject-working-tree") return superroot -def submodules_update(gitmodules, root_dir, requiredlist, force): - submodules = {} +def submodules_update(gitmodules, root_dir, requiredlist, force, submodules=None): + if not submodules: + submodules = {} for name in gitmodules.sections(): if not submodules or name not in submodules: submodules[name] = init_submodule_from_gitmodules(gitmodules, name, root_dir, logger) + _, needsupdate, localmods, testfails = submodules[name].status() if not submodules[name].fxrequired: submodules[name].fxrequired = "AlwaysRequired" @@ -235,24 +237,23 @@ def submodules_update(gitmodules, root_dir, requiredlist, force): or fxrequired not in requiredlist) ): if "Optional" in fxrequired and "Optional" not in requiredlist: - print(f"Skipping optional component {name:>20}") if fxrequired.startswith("Always"): print(f"Skipping optional component {name:>20}") continue optional = "AlwaysOptional" in requiredlist - print(f"1 Required list is {requiredlist} optional is {optional}") + if fxrequired in requiredlist: submodules[name].update() repodir = os.path.join(root_dir, submodules[name].path) if os.path.exists(os.path.join(repodir, ".gitmodules")): # recursively handle this checkout print(f"Recursively checking out submodules of {name}") - gitmodules = GitModules(submodules[name].logger, confpath=repodir) + gitsubmodules = GitModules(submodules[name].logger, confpath=repodir) requiredlist = ["AlwaysRequired"] if optional: requiredlist.append("AlwaysOptional") - print(f"2 Required list is {requiredlist}") - submodules_update(gitmodules, repodir, requiredlist, force=force) + + submodules_update(gitsubmodules, repodir, requiredlist, force=force, submodules=submodules) def local_mods_output(): diff --git a/git_fleximod/submodule.py b/git_fleximod/submodule.py index 68fb08d6..daa0fef0 100644 --- a/git_fleximod/submodule.py +++ b/git_fleximod/submodule.py @@ -102,8 +102,17 @@ def status(self): needsupdate = True return result, needsupdate, localmods, testfails rurl = git.git_operation("ls-remote","--get-url").rstrip() - atag = git.git_operation("describe", "--tags", "--always").rstrip() - ahash = git.git_operation("rev-list", "HEAD").partition("\n")[0] + line = git.git_operation("log", "--pretty=format:\"%h %d").partition('\n')[0] + parts = line.split() + ahash = parts[0][1:] + if len(parts) > 3: + atag = parts[3][:-1] + else: + atag = None + + #print(f"line is {line} ahash is {ahash} atag is {atag}") + # atag = git.git_operation("describe", "--tags", "--always").rstrip() + # ahash = git.git_operation("rev-list", "HEAD").partition("\n")[0] recurse = False if rurl != self.url: From dae1c82884feb69249dd0616c2a18131252ef4d2 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Thu, 4 Jul 2024 12:45:32 -0600 Subject: [PATCH 152/159] now working with issue #50 --- git_fleximod/git_fleximod.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index c4e595d8..2e282af4 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -215,9 +215,8 @@ def git_toplevelroot(root_dir, logger): superroot = rgit.git_operation("rev-parse", "--show-superproject-working-tree") return superroot -def submodules_update(gitmodules, root_dir, requiredlist, force, submodules=None): - if not submodules: - submodules = {} +def submodules_update(gitmodules, root_dir, requiredlist, force): + submodules = {} for name in gitmodules.sections(): if not submodules or name not in submodules: submodules[name] = init_submodule_from_gitmodules(gitmodules, name, root_dir, logger) @@ -249,12 +248,11 @@ def submodules_update(gitmodules, root_dir, requiredlist, force, submodules=None # recursively handle this checkout print(f"Recursively checking out submodules of {name}") gitsubmodules = GitModules(submodules[name].logger, confpath=repodir) - requiredlist = ["AlwaysRequired"] + newrequiredlist = ["AlwaysRequired"] if optional: - requiredlist.append("AlwaysOptional") - - submodules_update(gitsubmodules, repodir, requiredlist, force=force, submodules=submodules) + newrequiredlist.append("AlwaysOptional") + submodules_update(gitsubmodules, repodir, newrequiredlist, force=force) def local_mods_output(): text = """\ From 53fce573bcad0ca1ae5c78013df8a42ad3414e5b Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Thu, 4 Jul 2024 16:10:22 -0600 Subject: [PATCH 153/159] no need to make submodule objects persist --- git_fleximod/git_fleximod.py | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index 2e282af4..e28499de 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -188,19 +188,17 @@ def submodules_status(gitmodules, root_dir, toplevel=False, depth=0): testfails = 0 localmods = 0 needsupdate = 0 - submodules = {} wrapper = textwrap.TextWrapper(initial_indent=' '*(depth*10), width=120,subsequent_indent=' '*(depth*20)) for name in gitmodules.sections(): - if not submodules or name not in submodules: - submodules[name] = init_submodule_from_gitmodules(gitmodules, name, root_dir, logger) + submod = init_submodule_from_gitmodules(gitmodules, name, root_dir, logger) - result,n,l,t = submodules[name].status() + result,n,l,t = submod.status() testfails += t localmods += l needsupdate += n - if toplevel or not submodules[name].toplevel(): + if toplevel or not submod.toplevel(): print(wrapper.fill(result)) - subdir = os.path.join(root_dir, submodules[name].path) + subdir = os.path.join(root_dir, submod.path) if os.path.exists(os.path.join(subdir, ".gitmodules")): submod = GitModules(logger, confpath=subdir) t,l,n = submodules_status(submod, subdir, depth=depth+1) @@ -216,15 +214,13 @@ def git_toplevelroot(root_dir, logger): return superroot def submodules_update(gitmodules, root_dir, requiredlist, force): - submodules = {} for name in gitmodules.sections(): - if not submodules or name not in submodules: - submodules[name] = init_submodule_from_gitmodules(gitmodules, name, root_dir, logger) + submod = init_submodule_from_gitmodules(gitmodules, name, root_dir, logger) - _, needsupdate, localmods, testfails = submodules[name].status() - if not submodules[name].fxrequired: - submodules[name].fxrequired = "AlwaysRequired" - fxrequired = submodules[name].fxrequired + _, needsupdate, localmods, testfails = submod.status() + if not submod.fxrequired: + submod.fxrequired = "AlwaysRequired" + fxrequired = submod.fxrequired allowedvalues = fxrequired_allowed_values() assert fxrequired in allowedvalues @@ -242,12 +238,12 @@ def submodules_update(gitmodules, root_dir, requiredlist, force): optional = "AlwaysOptional" in requiredlist if fxrequired in requiredlist: - submodules[name].update() - repodir = os.path.join(root_dir, submodules[name].path) + submod.update() + repodir = os.path.join(root_dir, submod.path) if os.path.exists(os.path.join(repodir, ".gitmodules")): # recursively handle this checkout print(f"Recursively checking out submodules of {name}") - gitsubmodules = GitModules(submodules[name].logger, confpath=repodir) + gitsubmodules = GitModules(submod.logger, confpath=repodir) newrequiredlist = ["AlwaysRequired"] if optional: newrequiredlist.append("AlwaysOptional") From ed5d152dec0b629df584843edc7f22b4e6d8a4ce Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Fri, 5 Jul 2024 08:23:14 -0600 Subject: [PATCH 154/159] remove old git-fleximod --- .../git-fleximod/.github/workflows/pre-commit | 13 - .../.github/workflows/pytest.yaml | 77 -- .lib/git-fleximod/.pre-commit-config.yaml | 18 - .lib/git-fleximod/License | 20 - .lib/git-fleximod/README.md | 108 --- .lib/git-fleximod/doc/Makefile | 20 - .lib/git-fleximod/doc/conf.py | 26 - .lib/git-fleximod/doc/index.rst | 24 - .lib/git-fleximod/doc/make.bat | 35 - .lib/git-fleximod/escomp_install | 25 - .lib/git-fleximod/git_fleximod/__init__.py | 0 .lib/git-fleximod/git_fleximod/cli.py | 119 --- .../git-fleximod/git_fleximod/git_fleximod.py | 601 --------------- .../git-fleximod/git_fleximod/gitinterface.py | 79 -- .lib/git-fleximod/git_fleximod/gitmodules.py | 97 --- .../git-fleximod/git_fleximod/lstripreader.py | 43 -- .lib/git-fleximod/git_fleximod/metoflexi.py | 236 ------ .lib/git-fleximod/git_fleximod/utils.py | 365 --------- .lib/git-fleximod/poetry.lock | 693 ------------------ .lib/git-fleximod/pyproject.toml | 41 -- .lib/git-fleximod/tbump.toml | 43 -- .lib/git-fleximod/tests/__init__.py | 3 - .lib/git-fleximod/tests/conftest.py | 138 ---- .lib/git-fleximod/tests/test_a_import.py | 8 - .lib/git-fleximod/tests/test_b_update.py | 26 - .lib/git-fleximod/tests/test_c_required.py | 30 - .lib/git-fleximod/tests/test_d_complex.py | 67 -- 27 files changed, 2955 deletions(-) delete mode 100644 .lib/git-fleximod/.github/workflows/pre-commit delete mode 100644 .lib/git-fleximod/.github/workflows/pytest.yaml delete mode 100644 .lib/git-fleximod/.pre-commit-config.yaml delete mode 100644 .lib/git-fleximod/License delete mode 100644 .lib/git-fleximod/README.md delete mode 100644 .lib/git-fleximod/doc/Makefile delete mode 100644 .lib/git-fleximod/doc/conf.py delete mode 100644 .lib/git-fleximod/doc/index.rst delete mode 100644 .lib/git-fleximod/doc/make.bat delete mode 100644 .lib/git-fleximod/escomp_install delete mode 100644 .lib/git-fleximod/git_fleximod/__init__.py delete mode 100644 .lib/git-fleximod/git_fleximod/cli.py delete mode 100755 .lib/git-fleximod/git_fleximod/git_fleximod.py delete mode 100644 .lib/git-fleximod/git_fleximod/gitinterface.py delete mode 100644 .lib/git-fleximod/git_fleximod/gitmodules.py delete mode 100644 .lib/git-fleximod/git_fleximod/lstripreader.py delete mode 100755 .lib/git-fleximod/git_fleximod/metoflexi.py delete mode 100644 .lib/git-fleximod/git_fleximod/utils.py delete mode 100644 .lib/git-fleximod/poetry.lock delete mode 100644 .lib/git-fleximod/pyproject.toml delete mode 100644 .lib/git-fleximod/tbump.toml delete mode 100644 .lib/git-fleximod/tests/__init__.py delete mode 100644 .lib/git-fleximod/tests/conftest.py delete mode 100644 .lib/git-fleximod/tests/test_a_import.py delete mode 100644 .lib/git-fleximod/tests/test_b_update.py delete mode 100644 .lib/git-fleximod/tests/test_c_required.py delete mode 100644 .lib/git-fleximod/tests/test_d_complex.py diff --git a/.lib/git-fleximod/.github/workflows/pre-commit b/.lib/git-fleximod/.github/workflows/pre-commit deleted file mode 100644 index 1a6ad008..00000000 --- a/.lib/git-fleximod/.github/workflows/pre-commit +++ /dev/null @@ -1,13 +0,0 @@ -name: pre-commit -on: - pull_request: - push: - branches: [main] - -jobs: - pre-commit: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v3 - - uses: pre-commit/action@v3.0.0 diff --git a/.lib/git-fleximod/.github/workflows/pytest.yaml b/.lib/git-fleximod/.github/workflows/pytest.yaml deleted file mode 100644 index 0868dd9a..00000000 --- a/.lib/git-fleximod/.github/workflows/pytest.yaml +++ /dev/null @@ -1,77 +0,0 @@ -# Run this job on pushes to `main`, and for pull requests. If you don't specify -# `branches: [main], then this actions runs _twice_ on pull requests, which is -# annoying. - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - # If you wanted to use multiple Python versions, you'd have specify a matrix in the job and - # reference the matrixe python version here. - - uses: actions/setup-python@v5 - with: - python-version: '3.9' - - # Cache the installation of Poetry itself, e.g. the next step. This prevents the workflow - # from installing Poetry every time, which can be slow. Note the use of the Poetry version - # number in the cache key, and the "-0" suffix: this allows you to invalidate the cache - # manually if/when you want to upgrade Poetry, or if something goes wrong. This could be - # mildly cleaner by using an environment variable, but I don't really care. - - name: cache poetry install - uses: actions/cache@v4 - with: - path: ~/.local - key: poetry-1.7.1 - - # Install Poetry. You could do this manually, or there are several actions that do this. - # `snok/install-poetry` seems to be minimal yet complete, and really just calls out to - # Poetry's default install script, which feels correct. I pin the Poetry version here - # because Poetry does occasionally change APIs between versions and I don't want my - # actions to break if it does. - # - # The key configuration value here is `virtualenvs-in-project: true`: this creates the - # venv as a `.venv` in your testing directory, which allows the next step to easily - # cache it. - - uses: snok/install-poetry@v1 - with: - version: 1.7.1 - virtualenvs-create: true - virtualenvs-in-project: true - - # Cache your dependencies (i.e. all the stuff in your `pyproject.toml`). Note the cache - # key: if you're using multiple Python versions, or multiple OSes, you'd need to include - # them in the cache key. I'm not, so it can be simple and just depend on the poetry.lock. - - name: cache deps - id: cache-deps - uses: actions/cache@v4 - with: - path: .venv - key: pydeps-${{ hashFiles('**/poetry.lock') }} - - # Install dependencies. `--no-root` means "install all dependencies but not the project - # itself", which is what you want to avoid caching _your_ code. The `if` statement - # ensures this only runs on a cache miss. - - run: poetry install --no-interaction --no-root - if: steps.cache-deps.outputs.cache-hit != 'true' - - # Now install _your_ project. This isn't necessary for many types of projects -- particularly - # things like Django apps don't need this. But it's a good idea since it fully-exercises the - # pyproject.toml and makes that if you add things like console-scripts at some point that - # they'll be installed and working. - - run: poetry install --no-interaction - - # And finally run tests. I'm using pytest and all my pytest config is in my `pyproject.toml` - # so this line is super-simple. But it could be as complex as you need. - - run: | - git config --global user.name "${GITHUB_ACTOR}" - git config --global user.email "${GITHUB_ACTOR_ID}+${GITHUB_ACTOR}@users.noreply.github.com" - poetry run pytest - diff --git a/.lib/git-fleximod/.pre-commit-config.yaml b/.lib/git-fleximod/.pre-commit-config.yaml deleted file mode 100644 index 2f6089da..00000000 --- a/.lib/git-fleximod/.pre-commit-config.yaml +++ /dev/null @@ -1,18 +0,0 @@ -exclude: ^utils/.*$ - -repos: - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 - hooks: - - id: end-of-file-fixer - - id: trailing-whitespace - - repo: https://github.com/psf/black - rev: 22.3.0 - hooks: - - id: black - - repo: https://github.com/PyCQA/pylint - rev: v2.11.1 - hooks: - - id: pylint - args: - - --disable=I,C,R,logging-not-lazy,wildcard-import,unused-wildcard-import,fixme,broad-except,bare-except,eval-used,exec-used,global-statement,logging-format-interpolation,no-name-in-module,arguments-renamed,unspecified-encoding,protected-access,import-error,no-member diff --git a/.lib/git-fleximod/License b/.lib/git-fleximod/License deleted file mode 100644 index 88bc2251..00000000 --- a/.lib/git-fleximod/License +++ /dev/null @@ -1,20 +0,0 @@ -Copyright 2024 NSF National Center for Atmospheric Sciences (NCAR) - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -“Software”), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/.lib/git-fleximod/README.md b/.lib/git-fleximod/README.md deleted file mode 100644 index 53917da4..00000000 --- a/.lib/git-fleximod/README.md +++ /dev/null @@ -1,108 +0,0 @@ -# git-fleximod - -Flexible, Enhanced Submodule Management for Git - -## Overview - -Git-fleximod is a Python-based tool that extends Git's submodule and sparse checkout capabilities, offering additional features for managing submodules in a more flexible and efficient way. - -## Installation - - If you choose to locate git-fleximod in your path you can access it via command: git fleximod - -## Usage - - Basic Usage: - git fleximod [options] - Available Commands: - status: Display the status of submodules. - update: Update submodules to the tag indicated in .gitmodules variable fxtag. - test: Make sure that fxtags and submodule hashes are consistant, - make sure that official urls (as defined by fxDONOTUSEurl) are set - make sure that fxtags are defined for all submodules - Additional Options: - See git fleximod --help for more details. - -## Supported .gitmodules Variables - - fxtag: Specify a specific tag or branch to checkout for a submodule. - fxrequired: Mark a submodule's checkout behavior, with allowed values: - - ToplevelRequired: Top-level and required (checked out only when this is the Toplevel module). - - ToplevelOptional: Top-level and optional (checked out with --optional flag if this is the Toplevel module). - - AlwaysRequired: Always required (always checked out). - - AlwaysOptional: Always optional (checked out with --optional flag). - fxsparse: Enable sparse checkout for a submodule, pointing to a file containing sparse checkout paths. - fxDONOTUSEurl: This is the url used in the test subcommand to assure that protected branches do not point to forks - **NOTE** the fxDONOTUSEurl variable is only used to identify the official project repository and should not be - changed by users. Use the url variable to change to a fork if desired. - -## Sparse Checkouts - - To enable sparse checkout for a submodule, set the fxsparse variable - in the .gitmodules file to the path of a file containing the desired - sparse checkout paths. Git-fleximod will automatically configure - sparse checkout based on this file when applicable commands are run. - See [git-sparse-checkout](https://git-scm.com/docs/git-sparse-checkout#_internalsfull_pattern_set) - for details on the format of this file. - -## Tests - - The git fleximod test action is designed to be used by, for example, github workflows - to assure that protected branches are consistant with respect to submodule hashes and fleximod fxtags - -## Examples - -Here are some common usage examples: - -Update all submodules, including optional ones: -```bash - git fleximod update --optional -``` - -Updating a specific submodule to the fxtag indicated in .gitmodules: - -```bash - git fleximod update submodule-name -``` -Example .gitmodules entry: -```ini, toml - [submodule "cosp2"] - path = src/physics/cosp2/src - url = https://github.com/CFMIP/COSPv2.0 - fxsparse = ../.cosp_sparse_checkout - fxrequired = AlwaysRequired - fxtag = v2.1.4cesm -``` -Explanation: - -This entry indicates that the submodule named cosp2 at tag v2.1.4cesm -should be checked out into the directory src/physics/cosp2/src -relative to the .gitmodules directory. It should be checked out from -the URL https://github.com/CFMIP/COSPv2.0 and use sparse checkout as -described in the file ../.cosp_sparse_checkout relative to the path -directory. It should be checked out anytime this .gitmodules entry is -read. - -Additional example: -```ini, toml - [submodule "cime"] - path = cime - url = https://github.com/jedwards4b/cime - fxrequired = ToplevelRequired - fxtag = cime6.0.198_rme01 -``` - -Explanation: - -This entry indicates that the submodule cime should be checked out -into a directory named cime at tag cime6.0.198_rme01 from the URL -https://github.com/jedwards4b/cime. This should only be done if -the .gitmodules file is at the top level of the repository clone. - -## Contributing - -We welcome contributions! Please see the CONTRIBUTING.md file for guidelines. - -## License - -Git-fleximod is released under the MIT License. diff --git a/.lib/git-fleximod/doc/Makefile b/.lib/git-fleximod/doc/Makefile deleted file mode 100644 index d4bb2cbb..00000000 --- a/.lib/git-fleximod/doc/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = . -BUILDDIR = _build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/.lib/git-fleximod/doc/conf.py b/.lib/git-fleximod/doc/conf.py deleted file mode 100644 index 423099ee..00000000 --- a/.lib/git-fleximod/doc/conf.py +++ /dev/null @@ -1,26 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# For the full list of built-in configuration values, see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Project information ----------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information - -project = "git-fleximod" -author = "Jim Edwards " -release = "0.4.0" - -# -- General configuration --------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration - -extensions = ["sphinx_argparse_cli"] - -templates_path = ["_templates"] -exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] - - -# -- Options for HTML output ------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output - -html_theme = "alabaster" -html_static_path = ["_static"] diff --git a/.lib/git-fleximod/doc/index.rst b/.lib/git-fleximod/doc/index.rst deleted file mode 100644 index 0f9c1a7f..00000000 --- a/.lib/git-fleximod/doc/index.rst +++ /dev/null @@ -1,24 +0,0 @@ -.. git-fleximod documentation master file, created by - sphinx-quickstart on Sat Feb 3 12:02:22 2024. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to git-fleximod's documentation! -======================================== - -.. toctree:: - :maxdepth: 2 - :caption: Contents: -.. module:: sphinxcontrib.autoprogram -.. sphinx_argparse_cli:: - :module: git_fleximod.cli - :func: get_parser - :prog: git-fleximod - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/.lib/git-fleximod/doc/make.bat b/.lib/git-fleximod/doc/make.bat deleted file mode 100644 index 32bb2452..00000000 --- a/.lib/git-fleximod/doc/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=. -set BUILDDIR=_build - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.https://www.sphinx-doc.org/ - exit /b 1 -) - -if "%1" == "" goto help - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/.lib/git-fleximod/escomp_install b/.lib/git-fleximod/escomp_install deleted file mode 100644 index ae782e72..00000000 --- a/.lib/git-fleximod/escomp_install +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python -# updates git-fleximod in an ESCOMP model -# this script should be run from the model root directory, it expects -# git-fleximod to already be installed with the script in bin -# and the classes in lib/python/site-packages -import sys -import shutil -import os - -from glob import iglob - -fleximod_root = sys.argv[1] -fleximod_path = os.path.join(fleximod_root,"src","git-fleximod") -if os.path.isfile(fleximod_path): - with open(fleximod_path,"r") as f: - fleximod = f.readlines() - with open(os.path.join(".","bin","git-fleximod"),"w") as f: - for line in fleximod: - f.write(line) - if "import argparse" in line: - f.write('\nsys.path.append(os.path.join(os.path.dirname(__file__),"..","lib","python","site-packages"))\n\n') - - for file in iglob(os.path.join(fleximod_root, "src", "fleximod", "*.py")): - shutil.copy(file, - os.path.join("lib","python","site-packages","fleximod",os.path.basename(file))) diff --git a/.lib/git-fleximod/git_fleximod/__init__.py b/.lib/git-fleximod/git_fleximod/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/.lib/git-fleximod/git_fleximod/cli.py b/.lib/git-fleximod/git_fleximod/cli.py deleted file mode 100644 index 1fb959da..00000000 --- a/.lib/git-fleximod/git_fleximod/cli.py +++ /dev/null @@ -1,119 +0,0 @@ -from pathlib import Path -import argparse - -__version__ = "0.7.4" - -def find_root_dir(filename=".git"): - d = Path.cwd() - root = Path(d.root) - while d != root: - attempt = d / filename - if attempt.is_dir(): - return attempt - d = d.parent - return None - - -def get_parser(): - description = """ - %(prog)s manages checking out groups of gitsubmodules with addtional support for Earth System Models - """ - parser = argparse.ArgumentParser( - description=description, formatter_class=argparse.RawDescriptionHelpFormatter - ) - - # - # user options - # - choices = ["update", "status", "test"] - parser.add_argument( - "action", - choices=choices, - default="update", - help=f"Subcommand of git-fleximod, choices are {choices[:-1]}", - ) - - parser.add_argument( - "components", - nargs="*", - help="Specific component(s) to checkout. By default, " - "all required submodules are checked out.", - ) - - parser.add_argument( - "-C", - "--path", - default=find_root_dir(), - help="Toplevel repository directory. Defaults to top git directory relative to current.", - ) - - parser.add_argument( - "-g", - "--gitmodules", - nargs="?", - default=".gitmodules", - help="The submodule description filename. " "Default: %(default)s.", - ) - - parser.add_argument( - "-x", - "--exclude", - nargs="*", - help="Component(s) listed in the gitmodules file which should be ignored.", - ) - parser.add_argument( - "-f", - "--force", - action="store_true", - default=False, - help="Override cautions and update or checkout over locally modified repository.", - ) - - parser.add_argument( - "-o", - "--optional", - action="store_true", - default=False, - help="By default only the required submodules " - "are checked out. This flag will also checkout the " - "optional submodules relative to the toplevel directory.", - ) - - parser.add_argument( - "-v", - "--verbose", - action="count", - default=0, - help="Output additional information to " - "the screen and log file. This flag can be " - "used up to two times, increasing the " - "verbosity level each time.", - ) - - parser.add_argument( - "-V", - "--version", - action="version", - version=f"%(prog)s {__version__}", - help="Print version and exit.", - ) - - # - # developer options - # - parser.add_argument( - "--backtrace", - action="store_true", - help="DEVELOPER: show exception backtraces as extra " "debugging output", - ) - - parser.add_argument( - "-d", - "--debug", - action="store_true", - default=False, - help="DEVELOPER: output additional debugging " - "information to the screen and log file.", - ) - - return parser diff --git a/.lib/git-fleximod/git_fleximod/git_fleximod.py b/.lib/git-fleximod/git_fleximod/git_fleximod.py deleted file mode 100755 index 103cc82a..00000000 --- a/.lib/git-fleximod/git_fleximod/git_fleximod.py +++ /dev/null @@ -1,601 +0,0 @@ -#!/usr/bin/env python -import sys - -MIN_PYTHON = (3, 7) -if sys.version_info < MIN_PYTHON: - sys.exit("Python %s.%s or later is required." % MIN_PYTHON) - -import os -import shutil -import logging -import textwrap -from git_fleximod import utils -from git_fleximod import cli -from git_fleximod.gitinterface import GitInterface -from git_fleximod.gitmodules import GitModules -from configparser import NoOptionError - -# logger variable is global -logger = None - - -def fxrequired_allowed_values(): - return ["ToplevelRequired", "ToplevelOptional", "AlwaysRequired", "AlwaysOptional"] - - -def commandline_arguments(args=None): - parser = cli.get_parser() - - if args: - options = parser.parse_args(args) - else: - options = parser.parse_args() - - # explicitly listing a component overrides the optional flag - if options.optional or options.components: - fxrequired = [ - "ToplevelRequired", - "ToplevelOptional", - "AlwaysRequired", - "AlwaysOptional", - ] - else: - fxrequired = ["ToplevelRequired", "AlwaysRequired"] - - action = options.action - if not action: - action = "update" - handlers = [logging.StreamHandler()] - - if options.debug: - try: - open("fleximod.log", "w") - except PermissionError: - sys.exit("ABORT: Could not write file fleximod.log") - level = logging.DEBUG - handlers.append(logging.FileHandler("fleximod.log")) - elif options.verbose: - level = logging.INFO - else: - level = logging.WARNING - # Configure the root logger - logging.basicConfig( - level=level, format="%(name)s - %(levelname)s - %(message)s", handlers=handlers - ) - - if hasattr(options, "version"): - exit() - - return ( - options.path, - options.gitmodules, - fxrequired, - options.components, - options.exclude, - options.force, - action, - ) - - -def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master"): - """ - This function performs a sparse checkout of a git submodule. It does so by first creating the .git/info/sparse-checkout fileq - in the submodule and then checking out the desired tag. If the submodule is already checked out, it will not be checked out again. - Creating the sparse-checkout file first prevents the entire submodule from being checked out and then removed. This is important - because the submodule may have a large number of files and checking out the entire submodule and then removing it would be time - and disk space consuming. - - Parameters: - root_dir (str): The root directory for the git operation. - name (str): The name of the submodule. - url (str): The URL of the submodule. - path (str): The path to the submodule. - sparsefile (str): The sparse file for the submodule. - tag (str, optional): The tag to checkout. Defaults to "master". - - Returns: - None - """ - logger.info("Called sparse_checkout for {}".format(name)) - rgit = GitInterface(root_dir, logger) - superroot = rgit.git_operation("rev-parse", "--show-superproject-working-tree") - if superroot: - gitroot = superroot.strip() - else: - gitroot = root_dir.strip() - assert os.path.isdir(os.path.join(gitroot, ".git")) - # first create the module directory - if not os.path.isdir(os.path.join(root_dir, path)): - os.makedirs(os.path.join(root_dir, path)) - - # initialize a new git repo and set the sparse checkout flag - sprep_repo = os.path.join(root_dir, path) - sprepo_git = GitInterface(sprep_repo, logger) - if os.path.exists(os.path.join(sprep_repo, ".git")): - try: - logger.info("Submodule {} found".format(name)) - chk = sprepo_git.config_get_value("core", "sparseCheckout") - if chk == "true": - logger.info("Sparse submodule {} already checked out".format(name)) - return - except NoOptionError: - logger.debug("Sparse submodule {} not present".format(name)) - except Exception as e: - utils.fatal_error("Unexpected error {} occured.".format(e)) - - sprepo_git.config_set_value("core", "sparseCheckout", "true") - - # set the repository remote - - logger.info("Setting remote origin in {}/{}".format(root_dir, path)) - status = sprepo_git.git_operation("remote", "-v") - if url not in status: - sprepo_git.git_operation("remote", "add", "origin", url) - - topgit = os.path.join(gitroot, ".git") - - if gitroot != root_dir and os.path.isfile(os.path.join(root_dir, ".git")): - with open(os.path.join(root_dir, ".git")) as f: - gitpath = os.path.relpath( - os.path.join(root_dir, f.read().split()[1]), - start=os.path.join(root_dir, path), - ) - topgit = os.path.join(gitpath, "modules") - else: - topgit = os.path.relpath( - os.path.join(root_dir, ".git", "modules"), - start=os.path.join(root_dir, path), - ) - - with utils.pushd(sprep_repo): - if not os.path.isdir(topgit): - os.makedirs(topgit) - topgit += os.sep + name - - if os.path.isdir(os.path.join(root_dir, path, ".git")): - with utils.pushd(sprep_repo): - shutil.move(".git", topgit) - with open(".git", "w") as f: - f.write("gitdir: " + os.path.relpath(topgit)) - # assert(os.path.isdir(os.path.relpath(topgit, start=sprep_repo))) - gitsparse = os.path.abspath(os.path.join(topgit, "info", "sparse-checkout")) - if os.path.isfile(gitsparse): - logger.warning( - "submodule {} is already initialized {}".format(name, topgit) - ) - return - - with utils.pushd(sprep_repo): - shutil.copy(sparsefile, gitsparse) - - # Finally checkout the repo - sprepo_git.git_operation("fetch", "origin", "--tags") - sprepo_git.git_operation("checkout", tag) - - print(f"Successfully checked out {name:>20} at {tag}") - rgit.config_set_value(f'submodule "{name}"', "active", "true") - rgit.config_set_value(f'submodule "{name}"', "url", url) - - -def single_submodule_checkout( - root, name, path, url=None, tag=None, force=False, optional=False -): - """ - This function checks out a single git submodule. - - Parameters: - root (str): The root directory for the git operation. - name (str): The name of the submodule. - path (str): The path to the submodule. - url (str, optional): The URL of the submodule. Defaults to None. - tag (str, optional): The tag to checkout. Defaults to None. - force (bool, optional): If set to True, forces the checkout operation. Defaults to False. - optional (bool, optional): If set to True, the submodule is considered optional. Defaults to False. - - Returns: - None - """ - # function implementation... - git = GitInterface(root, logger) - repodir = os.path.join(root, path) - logger.info("Checkout {} into {}/{}".format(name, root, path)) - # if url is provided update to the new url - tmpurl = None - repo_exists = False - if os.path.exists(os.path.join(repodir, ".git")): - logger.info("Submodule {} already checked out".format(name)) - repo_exists = True - # Look for a .gitmodules file in the newly checkedout repo - if not repo_exists and url: - # ssh urls cause problems for those who dont have git accounts with ssh keys defined - # but cime has one since e3sm prefers ssh to https, because the .gitmodules file was - # opened with a GitModules object we don't need to worry about restoring the file here - # it will be done by the GitModules class - if url.startswith("git@"): - tmpurl = url - url = url.replace("git@github.com:", "https://github.com/") - git.git_operation("clone", url, path) - smgit = GitInterface(repodir, logger) - if not tag: - tag = smgit.git_operation("describe", "--tags", "--always").rstrip() - smgit.git_operation("checkout", tag) - # Now need to move the .git dir to the submodule location - rootdotgit = os.path.join(root, ".git") - if os.path.isfile(rootdotgit): - with open(rootdotgit) as f: - line = f.readline() - if line.startswith("gitdir: "): - rootdotgit = line[8:].rstrip() - - newpath = os.path.abspath(os.path.join(root, rootdotgit, "modules", name)) - if os.path.exists(newpath): - shutil.rmtree(os.path.join(repodir, ".git")) - else: - shutil.move(os.path.join(repodir, ".git"), newpath) - - with open(os.path.join(repodir, ".git"), "w") as f: - f.write("gitdir: " + os.path.relpath(newpath, start=repodir)) - - if not os.path.exists(repodir): - parent = os.path.dirname(repodir) - if not os.path.isdir(parent): - os.makedirs(parent) - git.git_operation("submodule", "add", "--name", name, "--", url, path) - - if not repo_exists or not tmpurl: - git.git_operation("submodule", "update", "--init", "--", path) - - if os.path.exists(os.path.join(repodir, ".gitmodules")): - # recursively handle this checkout - print(f"Recursively checking out submodules of {name}") - gitmodules = GitModules(logger, confpath=repodir) - requiredlist = ["AlwaysRequired"] - if optional: - requiredlist.append("AlwaysOptional") - submodules_checkout(gitmodules, repodir, requiredlist, force=force) - if not os.path.exists(os.path.join(repodir, ".git")): - utils.fatal_error( - f"Failed to checkout {name} {repo_exists} {tmpurl} {repodir} {path}" - ) - - if tmpurl: - print(git.git_operation("restore", ".gitmodules")) - - return - - -def submodules_status(gitmodules, root_dir, toplevel=False): - testfails = 0 - localmods = 0 - needsupdate = 0 - for name in gitmodules.sections(): - path = gitmodules.get(name, "path") - tag = gitmodules.get(name, "fxtag") - required = gitmodules.get(name, "fxrequired") - level = required and "Toplevel" in required - if not path: - utils.fatal_error("No path found in .gitmodules for {}".format(name)) - newpath = os.path.join(root_dir, path) - logger.debug("newpath is {}".format(newpath)) - if not os.path.exists(os.path.join(newpath, ".git")): - rootgit = GitInterface(root_dir, logger) - # submodule commands use path, not name - url = gitmodules.get(name, "url") - url = url.replace("git@github.com:", "https://github.com/") - tags = rootgit.git_operation("ls-remote", "--tags", url) - atag = None - needsupdate += 1 - if not toplevel and level: - continue - for htag in tags.split("\n"): - if tag and tag in htag: - atag = (htag.split()[1])[10:] - break - if tag and tag == atag: - print(f"e {name:>20} not checked out, aligned at tag {tag}") - elif tag: - ahash = rootgit.git_operation( - "submodule", "status", "{}".format(path) - ).rstrip() - ahash = ahash[1 : len(tag) + 1] - if tag == ahash: - print(f"e {name:>20} not checked out, aligned at hash {ahash}") - else: - print( - f"e {name:>20} not checked out, out of sync at tag {atag}, expected tag is {tag}" - ) - testfails += 1 - else: - print(f"e {name:>20} has no fxtag defined in .gitmodules") - testfails += 1 - else: - with utils.pushd(newpath): - git = GitInterface(newpath, logger) - atag = git.git_operation("describe", "--tags", "--always").rstrip() - ahash = git.git_operation("status").partition("\n")[0].split()[-1] - if tag and atag == tag: - print(f" {name:>20} at tag {tag}") - elif tag and ahash[: len(tag)] == tag: - print(f" {name:>20} at hash {ahash}") - elif atag == ahash: - print(f" {name:>20} at hash {ahash}") - elif tag: - print( - f"s {name:>20} {atag} {ahash} is out of sync with .gitmodules {tag}" - ) - testfails += 1 - needsupdate += 1 - else: - print( - f"e {name:>20} has no fxtag defined in .gitmodules, module at {atag}" - ) - testfails += 1 - - status = git.git_operation("status", "--ignore-submodules") - if "nothing to commit" not in status: - localmods = localmods + 1 - print("M" + textwrap.indent(status, " ")) - - return testfails, localmods, needsupdate - - -def submodules_update(gitmodules, root_dir, requiredlist, force): - _, localmods, needsupdate = submodules_status(gitmodules, root_dir) - - if localmods and not force: - local_mods_output() - return - if needsupdate == 0: - return - - for name in gitmodules.sections(): - fxtag = gitmodules.get(name, "fxtag") - path = gitmodules.get(name, "path") - url = gitmodules.get(name, "url") - logger.info( - "name={} path={} url={} fxtag={} requiredlist={}".format( - name, os.path.join(root_dir, path), url, fxtag, requiredlist - ) - ) - # if not os.path.exists(os.path.join(root_dir,path, ".git")): - fxrequired = gitmodules.get(name, "fxrequired") - assert fxrequired in fxrequired_allowed_values() - rgit = GitInterface(root_dir, logger) - superroot = rgit.git_operation("rev-parse", "--show-superproject-working-tree") - - fxsparse = gitmodules.get(name, "fxsparse") - - if ( - fxrequired - and (superroot and "Toplevel" in fxrequired) - or fxrequired not in requiredlist - ): - if "ToplevelOptional" == fxrequired: - print("Skipping optional component {}".format(name)) - continue - if fxsparse: - logger.debug( - "Callng submodule_sparse_checkout({}, {}, {}, {}, {}, {}".format( - root_dir, name, url, path, fxsparse, fxtag - ) - ) - submodule_sparse_checkout(root_dir, name, url, path, fxsparse, tag=fxtag) - else: - logger.info( - "Calling submodule_checkout({},{},{},{})".format( - root_dir, name, path, url - ) - ) - - single_submodule_checkout( - root_dir, - name, - path, - url=url, - tag=fxtag, - force=force, - optional=("AlwaysOptional" in requiredlist), - ) - - if os.path.exists(os.path.join(path, ".git")): - submoddir = os.path.join(root_dir, path) - with utils.pushd(submoddir): - git = GitInterface(submoddir, logger) - # first make sure the url is correct - upstream = git.git_operation("ls-remote", "--get-url").rstrip() - newremote = "origin" - if upstream != url: - # TODO - this needs to be a unique name - remotes = git.git_operation("remote", "-v") - if url in remotes: - for line in remotes: - if url in line and "fetch" in line: - newremote = line.split()[0] - break - else: - i = 0 - while newremote in remotes: - i = i + 1 - newremote = f"newremote.{i:02d}" - git.git_operation("remote", "add", newremote, url) - - tags = git.git_operation("tag", "-l") - if fxtag and fxtag not in tags: - git.git_operation("fetch", newremote, "--tags") - atag = git.git_operation("describe", "--tags", "--always").rstrip() - if fxtag and fxtag != atag: - try: - git.git_operation("checkout", fxtag) - print(f"{name:>20} updated to {fxtag}") - except Exception as error: - print(error) - elif not fxtag: - print(f"No fxtag found for submodule {name:>20}") - else: - print(f"{name:>20} up to date.") - - -def local_mods_output(): - text = """\ - The submodules labeled with 'M' above are not in a clean state. - The following are options for how to proceed: - (1) Go into each submodule which is not in a clean state and issue a 'git status' - Either revert or commit your changes so that the submodule is in a clean state. - (2) use the --force option to git-fleximod - (3) you can name the particular submodules to update using the git-fleximod command line - (4) As a last resort you can remove the submodule (via 'rm -fr [directory]') - then rerun git-fleximod update. -""" - print(text) - - -# checkout is done by update if required so this function may be depricated -def submodules_checkout(gitmodules, root_dir, requiredlist, force=False): - """ - This function checks out all git submodules based on the provided parameters. - - Parameters: - gitmodules (ConfigParser): The gitmodules configuration. - root_dir (str): The root directory for the git operation. - requiredlist (list): The list of required modules. - force (bool, optional): If set to True, forces the checkout operation. Defaults to False. - - Returns: - None - """ - # function implementation... - print("") - _, localmods, needsupdate = submodules_status(gitmodules, root_dir) - if localmods and not force: - local_mods_output() - return - if not needsupdate: - return - for name in gitmodules.sections(): - fxrequired = gitmodules.get(name, "fxrequired") - fxsparse = gitmodules.get(name, "fxsparse") - fxtag = gitmodules.get(name, "fxtag") - path = gitmodules.get(name, "path") - url = gitmodules.get(name, "url") - if fxrequired and fxrequired not in requiredlist: - if "Optional" in fxrequired: - print("Skipping optional component {}".format(name)) - continue - - if fxsparse: - logger.debug( - "Callng submodule_sparse_checkout({}, {}, {}, {}, {}, {}".format( - root_dir, name, url, path, fxsparse, fxtag - ) - ) - submodule_sparse_checkout(root_dir, name, url, path, fxsparse, tag=fxtag) - else: - logger.debug( - "Calling submodule_checkout({},{},{})".format(root_dir, name, path) - ) - single_submodule_checkout( - root_dir, - name, - path, - url=url, - tag=fxtag, - force=force, - optional="AlwaysOptional" in requiredlist, - ) - - -def submodules_test(gitmodules, root_dir): - """ - This function tests the git submodules based on the provided parameters. - - It first checks that fxtags are present and in sync with submodule hashes. - Then it ensures that urls are consistent with fxurls (not forks and not ssh) - and that sparse checkout files exist. - - Parameters: - gitmodules (ConfigParser): The gitmodules configuration. - root_dir (str): The root directory for the git operation. - - 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) - print("") - # Then make sure that urls are consistant with fxurls (not forks and not ssh) - # and that sparse checkout files exist - for name in gitmodules.sections(): - url = gitmodules.get(name, "url") - fxurl = gitmodules.get(name, "fxDONOTMODIFYurl") - fxsparse = gitmodules.get(name, "fxsparse") - path = gitmodules.get(name, "path") - fxurl = fxurl[:-4] if fxurl.endswith(".git") else fxurl - url = url[:-4] if url.endswith(".git") else url - if not fxurl or url.lower() != fxurl.lower(): - print(f"{name:>20} url {url} not in sync with required {fxurl}") - testfails += 1 - if fxsparse and not os.path.isfile(os.path.join(root_dir, path, fxsparse)): - print(f"{name:>20} sparse checkout file {fxsparse} not found") - testfails += 1 - return testfails + localmods + needsupdate - - -def main(): - ( - root_dir, - file_name, - fxrequired, - includelist, - excludelist, - force, - action, - ) = commandline_arguments() - # Get a logger for the package - global logger - logger = logging.getLogger(__name__) - - logger.info("action is {}".format(action)) - - if not os.path.isfile(os.path.join(root_dir, file_name)): - file_path = utils.find_upwards(root_dir, file_name) - - if file_path is None: - utils.fatal_error( - "No {} found in {} or any of it's parents".format(file_name, root_dir) - ) - - root_dir = os.path.dirname(file_path) - logger.info( - "root_dir is {} includelist={} excludelist={}".format( - root_dir, includelist, excludelist - ) - ) - gitmodules = GitModules( - logger, - confpath=root_dir, - conffile=file_name, - includelist=includelist, - excludelist=excludelist, - ) - if not gitmodules.sections(): - sys.exit("No submodule components found") - retval = 0 - if action == "update": - submodules_update(gitmodules, root_dir, fxrequired, force) - elif action == "status": - tfails, lmods, updates = submodules_status(gitmodules, root_dir, toplevel=True) - 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) - else: - utils.fatal_error(f"unrecognized action request {action}") - return retval - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/.lib/git-fleximod/git_fleximod/gitinterface.py b/.lib/git-fleximod/git_fleximod/gitinterface.py deleted file mode 100644 index 93ae38ec..00000000 --- a/.lib/git-fleximod/git_fleximod/gitinterface.py +++ /dev/null @@ -1,79 +0,0 @@ -import os -import sys -from . import utils -from pathlib import Path - -class GitInterface: - def __init__(self, repo_path, logger): - logger.debug("Initialize GitInterface for {}".format(repo_path)) - if isinstance(repo_path, str): - self.repo_path = Path(repo_path).resolve() - elif isinstance(repo_path, Path): - self.repo_path = repo_path.resolve() - else: - raise TypeError("repo_path must be a str or Path object") - self.logger = logger - try: - import git - - self._use_module = True - try: - self.repo = git.Repo(str(self.repo_path)) # Initialize GitPython repo - except git.exc.InvalidGitRepositoryError: - self.git = git - self._init_git_repo() - msg = "Using GitPython interface to git" - except ImportError: - self._use_module = False - if not (self.repo_path / ".git").exists(): - self._init_git_repo() - msg = "Using shell interface to git" - self.logger.info(msg) - - def _git_command(self, operation, *args): - self.logger.info(operation) - if self._use_module and operation != "submodule": - try: - return getattr(self.repo.git, operation)(*args) - except Exception as e: - sys.exit(e) - else: - return ["git", "-C", str(self.repo_path), operation] + list(args) - - def _init_git_repo(self): - if self._use_module: - self.repo = self.git.Repo.init(str(self.repo_path)) - else: - command = ("git", "-C", str(self.repo_path), "init") - utils.execute_subprocess(command) - - # pylint: disable=unused-argument - def git_operation(self, operation, *args, **kwargs): - command = self._git_command(operation, *args) - self.logger.info(command) - if isinstance(command, list): - try: - return utils.execute_subprocess(command, output_to_caller=True) - except Exception as e: - sys.exit(e) - else: - return command - - def config_get_value(self, section, name): - if self._use_module: - config = self.repo.config_reader() - return config.get_value(section, name) - else: - cmd = ("git", "-C", str(self.repo_path), "config", "--get", f"{section}.{name}") - output = utils.execute_subprocess(cmd, output_to_caller=True) - return output.strip() - - def config_set_value(self, section, name, value): - if self._use_module: - with self.repo.config_writer() as writer: - writer.set_value(section, name, value) - writer.release() # Ensure changes are saved - else: - cmd = ("git", "-C", str(self.repo_path), "config", f"{section}.{name}", value) - self.logger.info(cmd) - utils.execute_subprocess(cmd, output_to_caller=True) diff --git a/.lib/git-fleximod/git_fleximod/gitmodules.py b/.lib/git-fleximod/git_fleximod/gitmodules.py deleted file mode 100644 index 68c82d06..00000000 --- a/.lib/git-fleximod/git_fleximod/gitmodules.py +++ /dev/null @@ -1,97 +0,0 @@ -import shutil -from pathlib import Path -from configparser import RawConfigParser, ConfigParser -from .lstripreader import LstripReader - - -class GitModules(RawConfigParser): - def __init__( - self, - logger, - confpath=Path.cwd(), - conffile=".gitmodules", - includelist=None, - excludelist=None, - ): - """ - confpath: Path to the directory containing the .gitmodules file (defaults to the current working directory). - conffile: Name of the configuration file (defaults to .gitmodules). - includelist: Optional list of submodules to include. - excludelist: Optional list of submodules to exclude. - """ - self.logger = logger - self.logger.debug( - "Creating a GitModules object {} {} {} {}".format( - confpath, conffile, includelist, excludelist - ) - ) - super().__init__() - self.conf_file = (Path(confpath) / Path(conffile)) - if self.conf_file.exists(): - self.read_file(LstripReader(str(self.conf_file)), source=conffile) - self.includelist = includelist - self.excludelist = excludelist - self.isdirty = False - - def reload(self): - self.clear() - if self.conf_file.exists(): - self.read_file(LstripReader(str(self.conf_file)), source=self.conf_file) - - - def set(self, name, option, value): - """ - Sets a configuration value for a specific submodule: - Ensures the appropriate section exists for the submodule. - Calls the parent class's set method to store the value. - """ - self.isdirty = True - self.logger.debug("set called {} {} {}".format(name, option, value)) - section = f'submodule "{name}"' - if not self.has_section(section): - self.add_section(section) - super().set(section, option, str(value)) - - # pylint: disable=redefined-builtin, arguments-differ - def get(self, name, option, raw=False, vars=None, fallback=None): - """ - Retrieves a configuration value for a specific submodule: - Uses the parent class's get method to access the value. - Handles potential errors if the section or option doesn't exist. - """ - self.logger.debug("get called {} {}".format(name, option)) - section = f'submodule "{name}"' - try: - return ConfigParser.get( - self, section, option, raw=raw, vars=vars, fallback=fallback - ) - except ConfigParser.NoOptionError: - return None - - def save(self): - if self.isdirty: - self.logger.info("Writing {}".format(self.conf_file)) - with open(self.conf_file, "w") as fd: - self.write(fd) - self.isdirty = False - - def __del__(self): - self.save() - - def sections(self): - """Strip the submodule part out of section and just use the name""" - self.logger.debug("calling GitModules sections iterator") - names = [] - for section in ConfigParser.sections(self): - name = section[11:-1] - if self.includelist and name not in self.includelist: - continue - if self.excludelist and name in self.excludelist: - continue - names.append(name) - return names - - def items(self, name, raw=False, vars=None): - self.logger.debug("calling GitModules items for {}".format(name)) - section = f'submodule "{name}"' - return ConfigParser.items(section, raw=raw, vars=vars) diff --git a/.lib/git-fleximod/git_fleximod/lstripreader.py b/.lib/git-fleximod/git_fleximod/lstripreader.py deleted file mode 100644 index 01d5580e..00000000 --- a/.lib/git-fleximod/git_fleximod/lstripreader.py +++ /dev/null @@ -1,43 +0,0 @@ -class LstripReader(object): - "LstripReader formats .gitmodules files to be acceptable for configparser" - - def __init__(self, filename): - with open(filename, "r") as infile: - lines = infile.readlines() - self._lines = list() - self._num_lines = len(lines) - self._index = 0 - for line in lines: - self._lines.append(line.lstrip()) - - def readlines(self): - """Return all the lines from this object's file""" - return self._lines - - def readline(self, size=-1): - """Format and return the next line or raise StopIteration""" - try: - line = self.next() - except StopIteration: - line = "" - - if (size > 0) and (len(line) < size): - return line[0:size] - - return line - - def __iter__(self): - """Begin an iteration""" - self._index = 0 - return self - - def next(self): - """Return the next line or raise StopIteration""" - if self._index >= self._num_lines: - raise StopIteration - - self._index = self._index + 1 - return self._lines[self._index - 1] - - def __next__(self): - return self.next() diff --git a/.lib/git-fleximod/git_fleximod/metoflexi.py b/.lib/git-fleximod/git_fleximod/metoflexi.py deleted file mode 100755 index cc347db2..00000000 --- a/.lib/git-fleximod/git_fleximod/metoflexi.py +++ /dev/null @@ -1,236 +0,0 @@ -#!/usr/bin/env python -from configparser import ConfigParser -import sys -import shutil -from pathlib import Path -import argparse -import logging -from git_fleximod.gitinterface import GitInterface -from git_fleximod.gitmodules import GitModules -from git_fleximod import utils - -logger = None - -def find_root_dir(filename=".git"): - d = Path.cwd() - root = Path(d.root) - while d != root: - attempt = d / filename - if attempt.is_dir(): - return d - d = d.parent - return None - - -def get_parser(): - description = """ - %(prog)s manages checking out groups of gitsubmodules with addtional support for Earth System Models - """ - parser = argparse.ArgumentParser( - description=description, formatter_class=argparse.RawDescriptionHelpFormatter - ) - - parser.add_argument('-e', '--externals', nargs='?', - default='Externals.cfg', - help='The externals description filename. ' - 'Default: %(default)s.') - - parser.add_argument( - "-C", - "--path", - default=find_root_dir(), - help="Toplevel repository directory. Defaults to top git directory relative to current.", - ) - - parser.add_argument( - "-g", - "--gitmodules", - nargs="?", - default=".gitmodules", - help="The submodule description filename. " "Default: %(default)s.", - ) - parser.add_argument( - "-v", - "--verbose", - action="count", - default=0, - help="Output additional information to " - "the screen and log file. This flag can be " - "used up to two times, increasing the " - "verbosity level each time.", - ) - parser.add_argument( - "-d", - "--debug", - action="store_true", - default=False, - help="DEVELOPER: output additional debugging " - "information to the screen and log file.", - ) - - return parser - -def commandline_arguments(args=None): - parser = get_parser() - - options = parser.parse_args(args) - handlers = [logging.StreamHandler()] - - if options.debug: - try: - open("fleximod.log", "w") - except PermissionError: - sys.exit("ABORT: Could not write file fleximod.log") - level = logging.DEBUG - handlers.append(logging.FileHandler("fleximod.log")) - elif options.verbose: - level = logging.INFO - else: - level = logging.WARNING - # Configure the root logger - logging.basicConfig( - level=level, format="%(name)s - %(levelname)s - %(message)s", handlers=handlers - ) - - return( - options.path, - options.gitmodules, - options.externals - ) - -class ExternalRepoTranslator: - """ - Translates external repositories configured in an INI-style externals file. - """ - - def __init__(self, rootpath, gitmodules, externals): - self.rootpath = rootpath - if gitmodules: - self.gitmodules = GitModules(logger, confpath=rootpath) - self.externals = (rootpath / Path(externals)).resolve() - print(f"Translating {self.externals}") - self.git = GitInterface(rootpath, logger) - -# def __del__(self): -# if (self.rootpath / "save.gitignore"): - - - def translate_single_repo(self, section, tag, url, path, efile, hash_, sparse, protocol): - """ - Translates a single repository based on configuration details. - - Args: - rootpath (str): Root path of the main repository. - gitmodules (str): Path to the .gitmodules file. - tag (str): The tag to use for the external repository. - url (str): The URL of the external repository. - path (str): The relative path within the main repository for the external repository. - efile (str): The external file or file containing submodules. - hash_ (str): The commit hash to checkout (if applicable). - sparse (str): Boolean indicating whether to use sparse checkout (if applicable). - protocol (str): The protocol to use (e.g., 'git', 'http'). - """ - assert protocol != "svn", "SVN protocol is not currently supported" - print(f"Translating repository {section}") - if efile: - file_path = Path(path) / Path(efile) - newroot = (self.rootpath / file_path).parent.resolve() - if not newroot.exists(): - newroot.mkdir(parents=True) - logger.info("Newroot is {}".format(newroot)) - newt = ExternalRepoTranslator(newroot, ".gitmodules", efile) - newt.translate_repo() - if protocol == "externals_only": - if tag: - self.gitmodules.set(section, "fxtag", tag) - if hash_: - self.gitmodules.set(section, "fxtag", hash_) - - self.gitmodules.set(section, "fxDONOTUSEurl", url) - if sparse: - self.gitmodules.set(section, "fxsparse", sparse) - self.gitmodules.set(section, "fxrequired", "ToplevelRequired") - else: - newpath = (self.rootpath / Path(path)) - if newpath.exists(): - shutil.rmtree(newpath) - logger.info("Creating directory {}".format(newpath)) - newpath.mkdir(parents=True) - if tag: - logger.info("cloning {}".format(section)) - try: - self.git.git_operation("clone", "-b", tag, "--depth", "1", url, path) - except: - self.git.git_operation("clone", url, path) - with utils.pushd(newpath): - ngit = GitInterface(newpath, logger) - ngit.git_operation("checkout", tag) - if hash_: - self.git.git_operation("clone", url, path) - git = GitInterface(newpath, logger) - git.git_operation("fetch", "origin") - git.git_operation("checkout", hash_) - if sparse: - print("setting as sparse submodule {}".format(section)) - sparsefile = (newpath / Path(sparse)) - newfile = (newpath / ".git" / "info" / "sparse-checkout") - print(f"sparsefile {sparsefile} newfile {newfile}") - shutil.copy(sparsefile, newfile) - - logger.info("adding submodule {}".format(section)) - self.gitmodules.save() - self.git.git_operation("submodule", "add", "-f", "--name", section, url, path) - self.git.git_operation("submodule","absorbgitdirs") - self.gitmodules.reload() - if tag: - self.gitmodules.set(section, "fxtag", tag) - if hash_: - self.gitmodules.set(section, "fxtag", hash_) - - self.gitmodules.set(section, "fxDONOTUSEurl", url) - if sparse: - self.gitmodules.set(section, "fxsparse", sparse) - self.gitmodules.set(section, "fxrequired", "ToplevelRequired") - - - def translate_repo(self): - """ - Translates external repositories defined within an external file. - - Args: - rootpath (str): Root path of the main repository. - gitmodules (str): Path to the .gitmodules file. - external_file (str): The path to the external file containing repository definitions. - """ - econfig = ConfigParser() - econfig.read((self.rootpath / Path(self.externals))) - - for section in econfig.sections(): - if section == "externals_description": - logger.info("skipping section {}".format(section)) - return - logger.info("Translating section {}".format(section)) - tag = econfig.get(section, "tag", raw=False, fallback=None) - url = econfig.get(section, "repo_url", raw=False, fallback=None) - path = econfig.get(section, "local_path", raw=False, fallback=None) - efile = econfig.get(section, "externals", raw=False, fallback=None) - hash_ = econfig.get(section, "hash", raw=False, fallback=None) - sparse = econfig.get(section, "sparse", raw=False, fallback=None) - protocol = econfig.get(section, "protocol", raw=False, fallback=None) - - self.translate_single_repo(section, tag, url, path, efile, hash_, sparse, protocol) - - - -def _main(): - rootpath, gitmodules, externals = commandline_arguments() - global logger - logger = logging.getLogger(__name__) - with utils.pushd(rootpath): - t = ExternalRepoTranslator(Path(rootpath), gitmodules, externals) - logger.info("Translating {}".format(rootpath)) - t.translate_repo() - - -if __name__ == "__main__": - sys.exit(_main()) diff --git a/.lib/git-fleximod/git_fleximod/utils.py b/.lib/git-fleximod/git_fleximod/utils.py deleted file mode 100644 index 7cc1de38..00000000 --- a/.lib/git-fleximod/git_fleximod/utils.py +++ /dev/null @@ -1,365 +0,0 @@ -#!/usr/bin/env python3 -""" -Common public utilities for manic package - -""" - -import logging -import os -import subprocess -import sys -from threading import Timer -from pathlib import Path - -LOCAL_PATH_INDICATOR = "." -# --------------------------------------------------------------------- -# -# functions to massage text for output and other useful utilities -# -# --------------------------------------------------------------------- -from contextlib import contextmanager - - -@contextmanager -def pushd(new_dir): - """context for chdir. usage: with pushd(new_dir)""" - previous_dir = os.getcwd() - os.chdir(new_dir) - try: - yield - finally: - os.chdir(previous_dir) - - -def log_process_output(output): - """Log each line of process output at debug level so it can be - filtered if necessary. By default, output is a single string, and - logging.debug(output) will only put log info heading on the first - line. This makes it hard to filter with grep. - - """ - output = output.split("\n") - for line in output: - logging.debug(line) - - -def printlog(msg, **kwargs): - """Wrapper script around print to ensure that everything printed to - the screen also gets logged. - - """ - logging.info(msg) - if kwargs: - print(msg, **kwargs) - else: - print(msg) - sys.stdout.flush() - - -def find_upwards(root_dir, filename): - """Find a file in root dir or any of it's parents""" - d = Path(root_dir) - root = Path(d.root) - while d != root: - attempt = d / filename - if attempt.exists(): - return attempt - d = d.parent - return None - - -def last_n_lines(the_string, n_lines, truncation_message=None): - """Returns the last n lines of the given string - - Args: - the_string: str - n_lines: int - truncation_message: str, optional - - Returns a string containing the last n lines of the_string - - If truncation_message is provided, the returned string begins with - the given message if and only if the string is greater than n lines - to begin with. - """ - - lines = the_string.splitlines(True) - if len(lines) <= n_lines: - return_val = the_string - else: - lines_subset = lines[-n_lines:] - str_truncated = "".join(lines_subset) - if truncation_message: - str_truncated = truncation_message + "\n" + str_truncated - return_val = str_truncated - - return return_val - - -def indent_string(the_string, indent_level): - """Indents the given string by a given number of spaces - - Args: - the_string: str - indent_level: int - - Returns a new string that is the same as the_string, except that - each line is indented by 'indent_level' spaces. - - In python3, this can be done with textwrap.indent. - """ - - lines = the_string.splitlines(True) - padding = " " * indent_level - lines_indented = [padding + line for line in lines] - return "".join(lines_indented) - - -# --------------------------------------------------------------------- -# -# error handling -# -# --------------------------------------------------------------------- - - -def fatal_error(message): - """ - Error output function - """ - logging.error(message) - raise RuntimeError("{0}ERROR: {1}".format(os.linesep, message)) - - -# --------------------------------------------------------------------- -# -# Data conversion / manipulation -# -# --------------------------------------------------------------------- -def str_to_bool(bool_str): - """Convert a sting representation of as boolean into a true boolean. - - Conversion should be case insensitive. - """ - value = None - str_lower = bool_str.lower() - if str_lower in ("true", "t"): - value = True - elif str_lower in ("false", "f"): - value = False - if value is None: - msg = ( - 'ERROR: invalid boolean string value "{0}". ' - 'Must be "true" or "false"'.format(bool_str) - ) - fatal_error(msg) - return value - - -REMOTE_PREFIXES = ["http://", "https://", "ssh://", "git@"] - - -def is_remote_url(url): - """check if the user provided a local file path instead of a - remote. If so, it must be expanded to an absolute - path. - - """ - remote_url = False - for prefix in REMOTE_PREFIXES: - if url.startswith(prefix): - remote_url = True - return remote_url - - -def split_remote_url(url): - """check if the user provided a local file path or a - remote. If remote, try to strip off protocol info. - - """ - remote_url = is_remote_url(url) - if not remote_url: - return url - - for prefix in REMOTE_PREFIXES: - url = url.replace(prefix, "") - - if "@" in url: - url = url.split("@")[1] - - if ":" in url: - url = url.split(":")[1] - - return url - - -def expand_local_url(url, field): - """check if the user provided a local file path instead of a - remote. If so, it must be expanded to an absolute - path. - - Note: local paths of LOCAL_PATH_INDICATOR have special meaning and - represent local copy only, don't work with the remotes. - - """ - remote_url = is_remote_url(url) - if not remote_url: - if url.strip() == LOCAL_PATH_INDICATOR: - pass - else: - url = os.path.expandvars(url) - url = os.path.expanduser(url) - if not os.path.isabs(url): - msg = ( - 'WARNING: Externals description for "{0}" contains a ' - "url that is not remote and does not expand to an " - "absolute path. Version control operations may " - "fail.\n\nurl={1}".format(field, url) - ) - printlog(msg) - else: - url = os.path.normpath(url) - return url - - -# --------------------------------------------------------------------- -# -# subprocess -# -# --------------------------------------------------------------------- - -# Give the user a helpful message if we detect that a command seems to -# be hanging. -_HANGING_SEC = 300 - - -def _hanging_msg(working_directory, command): - print( - """ - -Command '{command}' -from directory {working_directory} -has taken {hanging_sec} seconds. It may be hanging. - -The command will continue to run, but you may want to abort -manage_externals with ^C and investigate. A possible cause of hangs is -when svn or git require authentication to access a private -repository. On some systems, svn and git requests for authentication -information will not be displayed to the user. In this case, the program -will appear to hang. Ensure you can run svn and git manually and access -all repositories without entering your authentication information. - -""".format( - command=command, - working_directory=working_directory, - hanging_sec=_HANGING_SEC, - ) - ) - - -def execute_subprocess(commands, status_to_caller=False, output_to_caller=False): - """Wrapper around subprocess.check_output to handle common - exceptions. - - check_output runs a command with arguments and waits - for it to complete. - - check_output raises an exception on a nonzero return code. if - status_to_caller is true, execute_subprocess returns the subprocess - return code, otherwise execute_subprocess treats non-zero return - status as an error and raises an exception. - - """ - cwd = os.getcwd() - msg = "In directory: {0}\nexecute_subprocess running command:".format(cwd) - logging.info(msg) - commands_str = " ".join(str(element) for element in commands) - logging.info(commands_str) - return_to_caller = status_to_caller or output_to_caller - status = -1 - output = "" - hanging_timer = Timer( - _HANGING_SEC, - _hanging_msg, - kwargs={"working_directory": cwd, "command": commands_str}, - ) - hanging_timer.start() - try: - output = subprocess.check_output( - commands, stderr=subprocess.STDOUT, universal_newlines=True - ) - log_process_output(output) - status = 0 - except OSError as error: - msg = failed_command_msg( - "Command execution failed. Does the executable exist?", commands - ) - logging.error(error) - fatal_error(msg) - except ValueError as error: - msg = failed_command_msg( - "DEV_ERROR: Invalid arguments trying to run subprocess", commands - ) - logging.error(error) - fatal_error(msg) - except subprocess.CalledProcessError as error: - # Only report the error if we are NOT returning to the - # caller. If we are returning to the caller, then it may be a - # simple status check. If returning, it is the callers - # responsibility determine if an error occurred and handle it - # appropriately. - if not return_to_caller: - msg_context = ( - "Process did not run successfully; " - "returned status {0}".format(error.returncode) - ) - msg = failed_command_msg(msg_context, commands, output=error.output) - logging.error(error) - logging.error(msg) - log_process_output(error.output) - fatal_error(msg) - status = error.returncode - finally: - hanging_timer.cancel() - - if status_to_caller and output_to_caller: - ret_value = (status, output) - elif status_to_caller: - ret_value = status - elif output_to_caller: - ret_value = output - else: - ret_value = None - - return ret_value - - -def failed_command_msg(msg_context, command, output=None): - """Template for consistent error messages from subprocess calls. - - If 'output' is given, it should provide the output from the failed - command - """ - - if output: - output_truncated = last_n_lines( - output, 20, truncation_message="[... Output truncated for brevity ...]" - ) - errmsg = ( - "Failed with output:\n" + indent_string(output_truncated, 4) + "\nERROR: " - ) - else: - errmsg = "" - - command_str = " ".join(command) - errmsg += """In directory - {cwd} -{context}: - {command} -""".format( - cwd=os.getcwd(), context=msg_context, command=command_str - ) - - if output: - errmsg += "See above for output from failed command.\n" - - return errmsg diff --git a/.lib/git-fleximod/poetry.lock b/.lib/git-fleximod/poetry.lock deleted file mode 100644 index b59ed394..00000000 --- a/.lib/git-fleximod/poetry.lock +++ /dev/null @@ -1,693 +0,0 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. - -[[package]] -name = "alabaster" -version = "0.7.13" -description = "A configurable sidebar-enabled Sphinx theme" -optional = false -python-versions = ">=3.6" -files = [ - {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, - {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, -] - -[[package]] -name = "babel" -version = "2.14.0" -description = "Internationalization utilities" -optional = false -python-versions = ">=3.7" -files = [ - {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"}, - {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"}, -] - -[package.dependencies] -pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} - -[package.extras] -dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] - -[[package]] -name = "certifi" -version = "2024.2.2" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -files = [ - {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, - {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.3.2" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, -] - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "docutils" -version = "0.19" -description = "Docutils -- Python Documentation Utilities" -optional = false -python-versions = ">=3.7" -files = [ - {file = "docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc"}, - {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"}, -] - -[[package]] -name = "exceptiongroup" -version = "1.2.0" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -files = [ - {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, - {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, -] - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "fsspec" -version = "2023.12.2" -description = "File-system specification" -optional = false -python-versions = ">=3.8" -files = [ - {file = "fsspec-2023.12.2-py3-none-any.whl", hash = "sha256:d800d87f72189a745fa3d6b033b9dc4a34ad069f60ca60b943a63599f5501960"}, - {file = "fsspec-2023.12.2.tar.gz", hash = "sha256:8548d39e8810b59c38014934f6b31e57f40c1b20f911f4cc2b85389c7e9bf0cb"}, -] - -[package.extras] -abfs = ["adlfs"] -adl = ["adlfs"] -arrow = ["pyarrow (>=1)"] -dask = ["dask", "distributed"] -devel = ["pytest", "pytest-cov"] -dropbox = ["dropbox", "dropboxdrivefs", "requests"] -full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs", "smbprotocol", "tqdm"] -fuse = ["fusepy"] -gcs = ["gcsfs"] -git = ["pygit2"] -github = ["requests"] -gs = ["gcsfs"] -gui = ["panel"] -hdfs = ["pyarrow (>=1)"] -http = ["aiohttp (!=4.0.0a0,!=4.0.0a1)", "requests"] -libarchive = ["libarchive-c"] -oci = ["ocifs"] -s3 = ["s3fs"] -sftp = ["paramiko"] -smb = ["smbprotocol"] -ssh = ["paramiko"] -tqdm = ["tqdm"] - -[[package]] -name = "gitdb" -version = "4.0.11" -description = "Git Object Database" -optional = false -python-versions = ">=3.7" -files = [ - {file = "gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4"}, - {file = "gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b"}, -] - -[package.dependencies] -smmap = ">=3.0.1,<6" - -[[package]] -name = "gitpython" -version = "3.1.41" -description = "GitPython is a Python library used to interact with Git repositories" -optional = false -python-versions = ">=3.7" -files = [ - {file = "GitPython-3.1.41-py3-none-any.whl", hash = "sha256:c36b6634d069b3f719610175020a9aed919421c87552185b085e04fbbdb10b7c"}, - {file = "GitPython-3.1.41.tar.gz", hash = "sha256:ed66e624884f76df22c8e16066d567aaa5a37d5b5fa19db2c6df6f7156db9048"}, -] - -[package.dependencies] -gitdb = ">=4.0.1,<5" - -[package.extras] -test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "sumtypes"] - -[[package]] -name = "idna" -version = "3.6" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.5" -files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, -] - -[[package]] -name = "imagesize" -version = "1.4.1" -description = "Getting image size from png/jpeg/jpeg2000/gif file" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, - {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, -] - -[[package]] -name = "importlib-metadata" -version = "7.0.1" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "importlib_metadata-7.0.1-py3-none-any.whl", hash = "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e"}, - {file = "importlib_metadata-7.0.1.tar.gz", hash = "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc"}, -] - -[package.dependencies] -zipp = ">=0.5" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "jinja2" -version = "3.1.3" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -files = [ - {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, - {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "markupsafe" -version = "2.1.5" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.7" -files = [ - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, -] - -[[package]] -name = "packaging" -version = "23.2" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.7" -files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, -] - -[[package]] -name = "pluggy" -version = "1.4.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, - {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "pyfakefs" -version = "5.3.5" -description = "pyfakefs implements a fake file system that mocks the Python file system modules." -optional = false -python-versions = ">=3.7" -files = [ - {file = "pyfakefs-5.3.5-py3-none-any.whl", hash = "sha256:751015c1de94e1390128c82b48cdedc3f088bbdbe4bc713c79d02a27f0f61e69"}, - {file = "pyfakefs-5.3.5.tar.gz", hash = "sha256:7cdc500b35a214cb7a614e1940543acc6650e69a94ac76e30f33c9373bd9cf90"}, -] - -[[package]] -name = "pygments" -version = "2.17.2" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.7" -files = [ - {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, - {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, -] - -[package.extras] -plugins = ["importlib-metadata"] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pytest" -version = "8.0.0" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest-8.0.0-py3-none-any.whl", hash = "sha256:50fb9cbe836c3f20f0dfa99c565201fb75dc54c8d76373cd1bde06b06657bdb6"}, - {file = "pytest-8.0.0.tar.gz", hash = "sha256:249b1b0864530ba251b7438274c4d251c58d868edaaec8762893ad4a0d71c36c"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=1.3.0,<2.0" -tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} - -[package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytz" -version = "2024.1" -description = "World timezone definitions, modern and historical" -optional = false -python-versions = "*" -files = [ - {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, - {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, -] - -[[package]] -name = "requests" -version = "2.31.0" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.7" -files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "smmap" -version = "5.0.1" -description = "A pure Python implementation of a sliding window memory map manager" -optional = false -python-versions = ">=3.7" -files = [ - {file = "smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da"}, - {file = "smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62"}, -] - -[[package]] -name = "snowballstemmer" -version = "2.2.0" -description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -optional = false -python-versions = "*" -files = [ - {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, - {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, -] - -[[package]] -name = "sphinx" -version = "5.3.0" -description = "Python documentation generator" -optional = false -python-versions = ">=3.6" -files = [ - {file = "Sphinx-5.3.0.tar.gz", hash = "sha256:51026de0a9ff9fc13c05d74913ad66047e104f56a129ff73e174eb5c3ee794b5"}, - {file = "sphinx-5.3.0-py3-none-any.whl", hash = "sha256:060ca5c9f7ba57a08a1219e547b269fadf125ae25b06b9fa7f66768efb652d6d"}, -] - -[package.dependencies] -alabaster = ">=0.7,<0.8" -babel = ">=2.9" -colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} -docutils = ">=0.14,<0.20" -imagesize = ">=1.3" -importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} -Jinja2 = ">=3.0" -packaging = ">=21.0" -Pygments = ">=2.12" -requests = ">=2.5.0" -snowballstemmer = ">=2.0" -sphinxcontrib-applehelp = "*" -sphinxcontrib-devhelp = "*" -sphinxcontrib-htmlhelp = ">=2.0.0" -sphinxcontrib-jsmath = "*" -sphinxcontrib-qthelp = "*" -sphinxcontrib-serializinghtml = ">=1.1.5" - -[package.extras] -docs = ["sphinxcontrib-websupport"] -lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-bugbear", "flake8-comprehensions", "flake8-simplify", "isort", "mypy (>=0.981)", "sphinx-lint", "types-requests", "types-typed-ast"] -test = ["cython", "html5lib", "pytest (>=4.6)", "typed_ast"] - -[[package]] -name = "sphinxcontrib-applehelp" -version = "1.0.4" -description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" -optional = false -python-versions = ">=3.8" -files = [ - {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, - {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-devhelp" -version = "1.0.2" -description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." -optional = false -python-versions = ">=3.5" -files = [ - {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, - {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-htmlhelp" -version = "2.0.1" -description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" -optional = false -python-versions = ">=3.8" -files = [ - {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, - {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["html5lib", "pytest"] - -[[package]] -name = "sphinxcontrib-jsmath" -version = "1.0.1" -description = "A sphinx extension which renders display math in HTML via JavaScript" -optional = false -python-versions = ">=3.5" -files = [ - {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, - {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, -] - -[package.extras] -test = ["flake8", "mypy", "pytest"] - -[[package]] -name = "sphinxcontrib-qthelp" -version = "1.0.3" -description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." -optional = false -python-versions = ">=3.5" -files = [ - {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, - {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "sphinxcontrib-serializinghtml" -version = "1.1.5" -description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." -optional = false -python-versions = ">=3.5" -files = [ - {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, - {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, -] - -[package.extras] -lint = ["docutils-stubs", "flake8", "mypy"] -test = ["pytest"] - -[[package]] -name = "tomli" -version = "2.0.1" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, - {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, -] - -[[package]] -name = "urllib3" -version = "2.2.0" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.8" -files = [ - {file = "urllib3-2.2.0-py3-none-any.whl", hash = "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224"}, - {file = "urllib3-2.2.0.tar.gz", hash = "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20"}, -] - -[package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - -[[package]] -name = "wheel" -version = "0.42.0" -description = "A built-package format for Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "wheel-0.42.0-py3-none-any.whl", hash = "sha256:177f9c9b0d45c47873b619f5b650346d632cdc35fb5e4d25058e09c9e581433d"}, - {file = "wheel-0.42.0.tar.gz", hash = "sha256:c45be39f7882c9d34243236f2d63cbd58039e360f85d0913425fbd7ceea617a8"}, -] - -[package.extras] -test = ["pytest (>=6.0.0)", "setuptools (>=65)"] - -[[package]] -name = "zipp" -version = "3.17.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.8" -files = [ - {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, - {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] - -[metadata] -lock-version = "2.0" -python-versions = "^3.8" -content-hash = "25ee2ae1d74abedde3a6637a60d4a3095ea5cf9731960875741bbc2ba84a475d" diff --git a/.lib/git-fleximod/pyproject.toml b/.lib/git-fleximod/pyproject.toml deleted file mode 100644 index 2484552e..00000000 --- a/.lib/git-fleximod/pyproject.toml +++ /dev/null @@ -1,41 +0,0 @@ -[tool.poetry] -name = "git-fleximod" -version = "0.7.4" -description = "Extended support for git-submodule and git-sparse-checkout" -authors = ["Jim Edwards "] -maintainers = ["Jim Edwards "] -license = "MIT" -readme = "README.md" -homepage = "https://github.com/jedwards4b/git-fleximod" -keywords = ["git", "submodule", "sparse-checkout"] -packages = [ -{ include = "git_fleximod"}, -{ include = "doc"}, -] - -[tool.poetry.scripts] -git-fleximod = "git_fleximod.git_fleximod:main" -me2flexi = "git_fleximod.metoflexi:_main" -fsspec = "fsspec.fuse:main" - -[tool.poetry.dependencies] -python = "^3.8" -GitPython = "^3.1.0" -sphinx = "^5.0.0" -fsspec = "^2023.12.2" -wheel = "^0.42.0" -pytest = "^8.0.0" -pyfakefs = "^5.3.5" - -[tool.poetry.urls] -"Bug Tracker" = "https://github.com/jedwards4b/git-fleximod/issues" - -[tool.pytest.ini_options] -markers = [ - "skip_after_first: only run on first iteration" -] - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" - diff --git a/.lib/git-fleximod/tbump.toml b/.lib/git-fleximod/tbump.toml deleted file mode 100644 index d4b8eaee..00000000 --- a/.lib/git-fleximod/tbump.toml +++ /dev/null @@ -1,43 +0,0 @@ -# Uncomment this if your project is hosted on GitHub: -github_url = "https://github.com/jedwards4b/git-fleximod/" - -[version] -current = "0.7.4" - -# Example of a semver regexp. -# Make sure this matches current_version before -# using tbump -regex = ''' - (?P\d+) - \. - (?P\d+) - \. - (?P\d+) - ''' - -[git] -message_template = "Bump to {new_version}" -tag_template = "v{new_version}" - -# For each file to patch, add a [[file]] config -# section containing the path of the file, relative to the -# tbump.toml location. -[[file]] -src = "git_fleximod/cli.py" - -[[file]] -src = "pyproject.toml" - -# You can specify a list of commands to -# run after the files have been patched -# and before the git commit is made - -# [[before_commit]] -# name = "check changelog" -# cmd = "grep -q {new_version} Changelog.rst" - -# Or run some commands after the git tag and the branch -# have been pushed: -# [[after_push]] -# name = "publish" -# cmd = "./publish.sh" diff --git a/.lib/git-fleximod/tests/__init__.py b/.lib/git-fleximod/tests/__init__.py deleted file mode 100644 index 4d4c66c7..00000000 --- a/.lib/git-fleximod/tests/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -import sys, os - -sys.path.append(os.path.join(os.path.dirname(__file__), os.path.pardir, "src")) diff --git a/.lib/git-fleximod/tests/conftest.py b/.lib/git-fleximod/tests/conftest.py deleted file mode 100644 index 942a0efb..00000000 --- a/.lib/git-fleximod/tests/conftest.py +++ /dev/null @@ -1,138 +0,0 @@ -import pytest -from git_fleximod.gitinterface import GitInterface -import os -import subprocess -import logging -from pathlib import Path - -@pytest.fixture(scope='session') -def logger(): - logging.basicConfig( - level=logging.INFO, format="%(name)s - %(levelname)s - %(message)s", handlers=[logging.StreamHandler()] - ) - logger = logging.getLogger(__name__) - return logger - -all_repos=[ - {"subrepo_path": "modules/test", - "submodule_name": "test_submodule", - "status1" : "test_submodule MPIserial_2.5.0-3-gd82ce7c is out of sync with .gitmodules MPIserial_2.4.0", - "status2" : "test_submodule at tag MPIserial_2.4.0", - "status3" : "test_submodule at tag MPIserial_2.4.0", - "status4" : "test_submodule at tag MPIserial_2.4.0", - "gitmodules_content" : """ - [submodule "test_submodule"] - path = modules/test - url = https://github.com/ESMCI/mpi-serial.git - fxtag = MPIserial_2.4.0 - fxDONOTUSEurl = https://github.com/ESMCI/mpi-serial.git - fxrequired = ToplevelRequired -"""}, - {"subrepo_path": "modules/test_optional", - "submodule_name": "test_optional", - "status1" : "test_optional MPIserial_2.5.0-3-gd82ce7c is out of sync with .gitmodules MPIserial_2.4.0", - "status2" : "test_optional at tag MPIserial_2.4.0", - "status3" : "test_optional not checked out, aligned at tag MPIserial_2.4.0", - "status4" : "test_optional at tag MPIserial_2.4.0", - "gitmodules_content": """ - [submodule "test_optional"] - path = modules/test_optional - url = https://github.com/ESMCI/mpi-serial.git - fxtag = MPIserial_2.4.0 - fxDONOTUSEurl = https://github.com/ESMCI/mpi-serial.git - fxrequired = ToplevelOptional -"""}, - {"subrepo_path": "modules/test_alwaysoptional", - "submodule_name": "test_alwaysoptional", - "status1" : "test_alwaysoptional MPIserial_2.3.0 is out of sync with .gitmodules e5cf35c", - "status2" : "test_alwaysoptional at hash e5cf35c", - "status3" : "test_alwaysoptional not checked out, out of sync at tag MPIserial_2.3.0", - "status4" : "test_alwaysoptional at hash e5cf35c", - "gitmodules_content": """ - [submodule "test_alwaysoptional"] - path = modules/test_alwaysoptional - url = https://github.com/ESMCI/mpi-serial.git - fxtag = e5cf35c - fxDONOTUSEurl = https://github.com/ESMCI/mpi-serial.git - fxrequired = AlwaysOptional -"""}, - {"subrepo_path": "modules/test_sparse", - "submodule_name": "test_sparse", - "status1" : "test_sparse at tag MPIserial_2.5.0", - "status2" : "test_sparse at tag MPIserial_2.5.0", - "status3" : "test_sparse at tag MPIserial_2.5.0", - "status4" : "test_sparse at tag MPIserial_2.5.0", - "gitmodules_content": """ - [submodule "test_sparse"] - path = modules/test_sparse - url = https://github.com/ESMCI/mpi-serial.git - fxtag = MPIserial_2.5.0 - fxDONOTUSEurl = https://github.com/ESMCI/mpi-serial.git - fxrequired = AlwaysRequired - fxsparse = ../.sparse_file_list -"""}, -] -@pytest.fixture(params=all_repos) - -def shared_repos(request): - return request.param - -@pytest.fixture -def get_all_repos(): - return all_repos - -def write_sparse_checkout_file(fp): - sparse_content = """m4 -""" - fp.write_text(sparse_content) - -@pytest.fixture -def test_repo(shared_repos, tmp_path, logger): - subrepo_path = shared_repos["subrepo_path"] - submodule_name = shared_repos["submodule_name"] - test_dir = tmp_path / "testrepo" - test_dir.mkdir() - str_path = str(test_dir) - gitp = GitInterface(str_path, logger) - assert test_dir.joinpath(".git").is_dir() - (test_dir / "modules").mkdir() - if "sparse" in submodule_name: - (test_dir / subrepo_path).mkdir() - # Add the sparse checkout file - write_sparse_checkout_file(test_dir / "modules" / ".sparse_file_list") - gitp.git_operation("add","modules/.sparse_file_list") - else: - gitp = GitInterface(str(test_dir), logger) - gitp.git_operation("submodule", "add", "--depth","1","--name", submodule_name, "https://github.com/ESMCI/mpi-serial.git", subrepo_path) - assert test_dir.joinpath(".gitmodules").is_file() - gitp.git_operation("add",subrepo_path) - gitp.git_operation("commit","-a","-m","\"add submod\"") - test_dir2 = tmp_path / "testrepo2" - gitp.git_operation("clone",test_dir,test_dir2) - return test_dir2 - - -@pytest.fixture -def complex_repo(tmp_path, logger): - test_dir = tmp_path / "testcomplex" - test_dir.mkdir() - str_path = str(test_dir) - gitp = GitInterface(str_path, logger) - gitp.git_operation("remote", "add", "origin", "https://github.com/jedwards4b/fleximod-test2") - gitp.git_operation("fetch", "origin", "main") - gitp.git_operation("checkout", "main") - return test_dir - -@pytest.fixture -def git_fleximod(): - def _run_fleximod(path, args, input=None): - cmd = ["git", "fleximod"] + args.split() - result = subprocess.run(cmd, cwd=path, input=input, - stdout=subprocess.PIPE, stderr=subprocess.PIPE, - text=True) - if result.returncode: - print(result.stdout) - print(result.stderr) - return result - return _run_fleximod - diff --git a/.lib/git-fleximod/tests/test_a_import.py b/.lib/git-fleximod/tests/test_a_import.py deleted file mode 100644 index d5ca878d..00000000 --- a/.lib/git-fleximod/tests/test_a_import.py +++ /dev/null @@ -1,8 +0,0 @@ -# pylint: disable=unused-import -from git_fleximod import cli -from git_fleximod import utils -from git_fleximod.gitinterface import GitInterface -from git_fleximod.gitmodules import GitModules - -def test_import(): - print("here") diff --git a/.lib/git-fleximod/tests/test_b_update.py b/.lib/git-fleximod/tests/test_b_update.py deleted file mode 100644 index 159f1cfa..00000000 --- a/.lib/git-fleximod/tests/test_b_update.py +++ /dev/null @@ -1,26 +0,0 @@ -import pytest -from pathlib import Path - -def test_basic_checkout(git_fleximod, test_repo, shared_repos): - # Prepare a simple .gitmodules - gm = shared_repos['gitmodules_content'] - file_path = (test_repo / ".gitmodules") - repo_name = shared_repos["submodule_name"] - repo_path = shared_repos["subrepo_path"] - - file_path.write_text(gm) - - # Run the command - result = git_fleximod(test_repo, f"update {repo_name}") - - # Assertions - assert result.returncode == 0 - assert Path(test_repo / repo_path).exists() # Did the submodule directory get created? - 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 - diff --git a/.lib/git-fleximod/tests/test_c_required.py b/.lib/git-fleximod/tests/test_c_required.py deleted file mode 100644 index 89ab8d29..00000000 --- a/.lib/git-fleximod/tests/test_c_required.py +++ /dev/null @@ -1,30 +0,0 @@ -import pytest -from pathlib import Path - -def test_required(git_fleximod, test_repo, shared_repos): - file_path = (test_repo / ".gitmodules") - gm = shared_repos["gitmodules_content"] - repo_name = shared_repos["submodule_name"] - if file_path.exists(): - with file_path.open("r") as f: - gitmodules_content = f.read() - # add the entry if it does not exist - if repo_name not in gitmodules_content: - file_path.write_text(gitmodules_content+gm) - # or if it is incomplete - elif gm not in gitmodules_content: - file_path.write_text(gm) - else: - file_path.write_text(gm) - 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 - 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 - 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 diff --git a/.lib/git-fleximod/tests/test_d_complex.py b/.lib/git-fleximod/tests/test_d_complex.py deleted file mode 100644 index fdce5162..00000000 --- a/.lib/git-fleximod/tests/test_d_complex.py +++ /dev/null @@ -1,67 +0,0 @@ -import pytest -from pathlib import Path -from git_fleximod.gitinterface import GitInterface - -def test_complex_checkout(git_fleximod, complex_repo, logger): - status = git_fleximod(complex_repo, "status") - assert("ToplevelOptional not checked out, aligned at tag v5.3.2" in status.stdout) - assert("ToplevelRequired not checked out, aligned at tag MPIserial_2.5.0" in status.stdout) - assert("AlwaysRequired not checked out, aligned at tag MPIserial_2.4.0" in status.stdout) - assert("Complex not checked out, aligned at tag testtag01" in status.stdout) - assert("AlwaysOptional not checked out, aligned at tag MPIserial_2.3.0" in status.stdout) - - # This should checkout and update test_submodule and complex_sub - result = git_fleximod(complex_repo, "update") - assert result.returncode == 0 - - status = git_fleximod(complex_repo, "status") - assert("ToplevelOptional not checked out, aligned at tag v5.3.2" in status.stdout) - assert("ToplevelRequired at tag MPIserial_2.5.0" in status.stdout) - assert("AlwaysRequired at tag MPIserial_2.4.0" in status.stdout) - assert("Complex at tag testtag01" in status.stdout) - - # now check the complex_sub - root = (complex_repo / "modules" / "complex") - assert(not (root / "libraries" / "gptl" / ".git").exists()) - assert(not (root / "libraries" / "mpi-serial" / ".git").exists()) - assert((root / "modules" / "mpi-serial" / ".git").exists()) - assert(not (root / "modules" / "mpi-serial2" / ".git").exists()) - assert((root / "modules" / "mpi-sparse" / ".git").exists()) - assert((root / "modules" / "mpi-sparse" / "m4").exists()) - assert(not (root / "modules" / "mpi-sparse" / "README").exists()) - - # update a single optional submodule - - result = git_fleximod(complex_repo, "update ToplevelOptional") - assert result.returncode == 0 - - status = git_fleximod(complex_repo, "status") - assert("ToplevelOptional at tag v5.3.2" in status.stdout) - assert("ToplevelRequired at tag MPIserial_2.5.0" in status.stdout) - assert("AlwaysRequired at tag MPIserial_2.4.0" in status.stdout) - assert("Complex at tag testtag01" in status.stdout) - assert("AlwaysOptional not checked out, aligned at tag MPIserial_2.3.0" in status.stdout) - - - # Finally update optional - result = git_fleximod(complex_repo, "update --optional") - assert result.returncode == 0 - - status = git_fleximod(complex_repo, "status") - assert("ToplevelOptional at tag v5.3.2" in status.stdout) - assert("ToplevelRequired at tag MPIserial_2.5.0" in status.stdout) - assert("AlwaysRequired at tag MPIserial_2.4.0" in status.stdout) - assert("Complex at tag testtag01" in status.stdout) - assert("AlwaysOptional at tag MPIserial_2.3.0" in status.stdout) - - # now check the complex_sub - root = (complex_repo / "modules" / "complex" ) - assert(not (root / "libraries" / "gptl" / ".git").exists()) - assert(not (root / "libraries" / "mpi-serial" / ".git").exists()) - assert((root / "modules" / "mpi-serial" / ".git").exists()) - assert((root / "modules" / "mpi-serial2" / ".git").exists()) - assert((root / "modules" / "mpi-sparse" / ".git").exists()) - assert((root / "modules" / "mpi-sparse" / "m4").exists()) - assert(not (root / "modules" / "mpi-sparse" / "README").exists()) - - From c624b61124d911f52ca8eead618ed2dd3c1b2171 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Fri, 5 Jul 2024 08:30:57 -0600 Subject: [PATCH 155/159] Bump to 0.8.0 --- git_fleximod/cli.py | 2 +- pyproject.toml | 2 +- tbump.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/git_fleximod/cli.py b/git_fleximod/cli.py index 25fce68f..80cce85f 100644 --- a/git_fleximod/cli.py +++ b/git_fleximod/cli.py @@ -2,7 +2,7 @@ import argparse from git_fleximod import utils -__version__ = "0.7.9" +__version__ = "0.8.0" def find_root_dir(filename=".gitmodules"): """ finds the highest directory in tree diff --git a/pyproject.toml b/pyproject.toml index 52cc26e7..7f65fa1c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "git-fleximod" -version = "0.7.9" +version = "0.8.0" description = "Extended support for git-submodule and git-sparse-checkout" authors = ["Jim Edwards "] maintainers = ["Jim Edwards "] diff --git a/tbump.toml b/tbump.toml index b1d08a56..89621b64 100644 --- a/tbump.toml +++ b/tbump.toml @@ -2,7 +2,7 @@ github_url = "https://github.com/jedwards4b/git-fleximod/" [version] -current = "0.7.9" +current = "0.8.0" # Example of a semver regexp. # Make sure this matches current_version before From e9cd2bee6200f7bde68dec6e10d5491e3171cc4d Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Mon, 8 Jul 2024 09:49:35 -0600 Subject: [PATCH 156/159] better fix for issue with ssh access --- git_fleximod/git_fleximod.py | 4 +++- git_fleximod/gitinterface.py | 10 ++++++++-- git_fleximod/submodule.py | 29 +++++++++++++---------------- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/git_fleximod/git_fleximod.py b/git_fleximod/git_fleximod.py index e28499de..4595cd2a 100755 --- a/git_fleximod/git_fleximod.py +++ b/git_fleximod/git_fleximod.py @@ -93,7 +93,8 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master """ logger.info("Called sparse_checkout for {}".format(name)) rgit = GitInterface(root_dir, logger) - superroot = rgit.git_operation("rev-parse", "--show-superproject-working-tree") + superroot = git_toplevelroot(root_dir, logger) + if superroot: gitroot = superroot.strip() else: @@ -178,6 +179,7 @@ def submodule_sparse_checkout(root_dir, name, url, path, sparsefile, tag="master def init_submodule_from_gitmodules(gitmodules, name, root_dir, logger): path = gitmodules.get(name, "path") url = gitmodules.get(name, "url") + assert path and url, f"Malformed .gitmodules file {path} {url}" tag = gitmodules.get(name, "fxtag") fxurl = gitmodules.get(name, "fxDONOTUSEurl") fxsparse = gitmodules.get(name, "fxsparse") diff --git a/git_fleximod/gitinterface.py b/git_fleximod/gitinterface.py index c7462b3a..58312014 100644 --- a/git_fleximod/gitinterface.py +++ b/git_fleximod/gitinterface.py @@ -49,8 +49,14 @@ def _init_git_repo(self): # pylint: disable=unused-argument def git_operation(self, operation, *args, **kwargs): - command = self._git_command(operation, *args) - self.logger.info(command) + newargs = [] + for a in args: + # Do not use ssh interface + if isinstance(a, str): + a = a.replace("git@github.com:", "https://github.com/") + newargs.append(a) + + command = self._git_command(operation, *newargs) if isinstance(command, list): try: return utils.execute_subprocess(command, output_to_caller=True) diff --git a/git_fleximod/submodule.py b/git_fleximod/submodule.py index daa0fef0..713ea344 100644 --- a/git_fleximod/submodule.py +++ b/git_fleximod/submodule.py @@ -26,8 +26,7 @@ def __init__(self, root_dir, name, path, url, fxtag=None, fxurl=None, fxsparse=N """ self.name = name self.root_dir = root_dir - self.path = path - url = url.replace("git@github.com:", "https://github.com/") + self.path = path self.url = url self.fxurl = fxurl self.fxtag = fxtag @@ -46,6 +45,7 @@ def status(self): - 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. """ + smpath = os.path.join(self.root_dir, self.path) testfails = False localmods = False @@ -162,8 +162,7 @@ def _add_remote(self, git): if remotes: upstream = git.git_operation("ls-remote", "--get-url").rstrip() newremote = "newremote.00" - - line = next((s for s in remotes if self.url in s), None) + line = next((s for s in remotes if self.url or tmpurl in s), None) if line: newremote = line.split()[0] return newremote @@ -281,6 +280,7 @@ def sparse_checkout(self): print(f"Successfully checked out {self.name:>20} at {self.fxtag}") rgit.config_set_value(f'submodule "{self.name}"', "active", "true") rgit.config_set_value(f'submodule "{self.name}"', "url", self.url) + rgit.config_set_value(f'submodule "{self.name}"', "path", self.path) def update(self): """ @@ -308,11 +308,11 @@ def update(self): repodir = os.path.join(self.root_dir, self.path) self.logger.info("Checkout {} into {}/{}".format(self.name, self.root_dir, self.path)) # if url is provided update to the new url - tmpurl = None + tag = None repo_exists = False - # if os.path.exists(os.path.join(repodir, ".git")): - # self.logger.info("Submodule {} already checked out".format(self.name)) - # repo_exists = True + if os.path.exists(os.path.join(repodir, ".git")): + self.logger.info("Submodule {} already checked out".format(self.name)) + repo_exists = True # Look for a .gitmodules file in the newly checkedout repo if self.fxsparse: print(f"Sparse checkout {self.name} fxsparse {self.fxsparse}") @@ -324,9 +324,7 @@ def update(self): # opened with a GitModules object we don't need to worry about restoring the file here # it will be done by the GitModules class if self.url.startswith("git@"): - tmpurl = self.url - url = self.url.replace("git@github.com:", "https://github.com/") - git.git_operation("clone", url, self.path) + git.git_operation("clone", self.url, self.path) smgit = GitInterface(repodir, self.logger) if not tag: tag = smgit.git_operation("describe", "--tags", "--always").rstrip() @@ -347,14 +345,14 @@ def update(self): with open(os.path.join(repodir, ".git"), "w") as f: f.write("gitdir: " + os.path.relpath(newpath, start=repodir)) - + if not os.path.exists(repodir): parent = os.path.dirname(repodir) if not os.path.isdir(parent): os.makedirs(parent) git.git_operation("submodule", "add", "--name", self.name, "--", self.url, self.path) - if not repo_exists or not tmpurl: + if not repo_exists: git.git_operation("submodule", "update", "--init", "--", self.path) if self.fxtag: @@ -363,11 +361,10 @@ def update(self): if not os.path.exists(os.path.join(repodir, ".git")): utils.fatal_error( - f"Failed to checkout {self.name} {repo_exists} {tmpurl} {repodir} {self.path}" + f"Failed to checkout {self.name} {repo_exists} {repodir} {self.path}" ) - if tmpurl: - print(git.git_operation("restore", ".gitmodules")) + if os.path.exists(os.path.join(self.path, ".git")): submoddir = os.path.join(self.root_dir, self.path) with utils.pushd(submoddir): From a6e43fcfd70795763c0fed13d3b0d8ae9151dcc7 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Mon, 8 Jul 2024 10:08:29 -0600 Subject: [PATCH 157/159] Bump to 0.8.1 --- git_fleximod/cli.py | 2 +- pyproject.toml | 2 +- tbump.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/git_fleximod/cli.py b/git_fleximod/cli.py index 80cce85f..13908334 100644 --- a/git_fleximod/cli.py +++ b/git_fleximod/cli.py @@ -2,7 +2,7 @@ import argparse from git_fleximod import utils -__version__ = "0.8.0" +__version__ = "0.8.1" def find_root_dir(filename=".gitmodules"): """ finds the highest directory in tree diff --git a/pyproject.toml b/pyproject.toml index 7f65fa1c..ef8f541f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "git-fleximod" -version = "0.8.0" +version = "0.8.1" description = "Extended support for git-submodule and git-sparse-checkout" authors = ["Jim Edwards "] maintainers = ["Jim Edwards "] diff --git a/tbump.toml b/tbump.toml index 89621b64..0b0d5ac7 100644 --- a/tbump.toml +++ b/tbump.toml @@ -2,7 +2,7 @@ github_url = "https://github.com/jedwards4b/git-fleximod/" [version] -current = "0.8.0" +current = "0.8.1" # Example of a semver regexp. # Make sure this matches current_version before From a90de4f8481e9c28a682da3189f84948dffd1bd8 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Tue, 9 Jul 2024 11:01:49 -0600 Subject: [PATCH 158/159] update to fetch tag first --- git_fleximod/submodule.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/git_fleximod/submodule.py b/git_fleximod/submodule.py index 713ea344..48657272 100644 --- a/git_fleximod/submodule.py +++ b/git_fleximod/submodule.py @@ -1,6 +1,7 @@ import os import textwrap import shutil +import string from configparser import NoOptionError from git_fleximod import utils from git_fleximod.gitinterface import GitInterface @@ -162,7 +163,8 @@ def _add_remote(self, git): if remotes: upstream = git.git_operation("ls-remote", "--get-url").rstrip() newremote = "newremote.00" - line = next((s for s in remotes if self.url or tmpurl in s), None) + tmpurl = self.url.replace("git@github.com:", "https://github.com/") + line = next((s for s in remotes if self.url in s or tmpurl in s), None) if line: newremote = line.split()[0] return newremote @@ -357,6 +359,13 @@ def update(self): if self.fxtag: smgit = GitInterface(repodir, self.logger) + newremote = self._add_remote(smgit) + # Trying to distingush a tag from a hash + allowed = set(string.digits + 'abcdef') + if not set(self.fxtag) <= allowed: + # This is a tag + tag = f"refs/tags/{self.fxtag}:refs/tags/{self.fxtag}" + smgit.git_operation("fetch", newremote, tag) smgit.git_operation("checkout", self.fxtag) if not os.path.exists(os.path.join(repodir, ".git")): From 75a03373577f5ed19a00d2c387ca36bff1172d76 Mon Sep 17 00:00:00 2001 From: Jim Edwards Date: Tue, 9 Jul 2024 11:14:56 -0600 Subject: [PATCH 159/159] Bump to 0.8.2 --- git_fleximod/cli.py | 2 +- pyproject.toml | 2 +- tbump.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/git_fleximod/cli.py b/git_fleximod/cli.py index 13908334..fdcf102a 100644 --- a/git_fleximod/cli.py +++ b/git_fleximod/cli.py @@ -2,7 +2,7 @@ import argparse from git_fleximod import utils -__version__ = "0.8.1" +__version__ = "0.8.2" def find_root_dir(filename=".gitmodules"): """ finds the highest directory in tree diff --git a/pyproject.toml b/pyproject.toml index ef8f541f..9cff1423 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "git-fleximod" -version = "0.8.1" +version = "0.8.2" description = "Extended support for git-submodule and git-sparse-checkout" authors = ["Jim Edwards "] maintainers = ["Jim Edwards "] diff --git a/tbump.toml b/tbump.toml index 0b0d5ac7..b4eed7d4 100644 --- a/tbump.toml +++ b/tbump.toml @@ -2,7 +2,7 @@ github_url = "https://github.com/jedwards4b/git-fleximod/" [version] -current = "0.8.1" +current = "0.8.2" # Example of a semver regexp. # Make sure this matches current_version before