Created
          November 12, 2024 06:48 
        
      - 
      
- 
        Save ink-splatters/8d66ce165f29a27f3cabc3916db0eb3a to your computer and use it in GitHub Desktop. 
Revisions
- 
        ink-splatters created this gist Nov 12, 2024 .There are no files selected for viewingThis file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,167 @@ # SPDX-FileCopyrightText: © 2024 Peter A. (@ink-splatters) # # SPDX-License-Identifier: MIT # # ----------------------------------------------------------------------------- # gix_populate_submodules # ----------------------------------------------------------------------------- # # MIT License # # © 2024 Peter A. (@ink-splatters) # # 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. # # ----------------------------------------------------------------------------- # # gitoxide hasn't (yet?) implemented a drop-in replacement for # `git submodules update`. This may negate the whole purpose of using it on repos # containing a large number of heavy submodules. # # This script tricks git by manually cloning all the submodules using gitoxide. # NOTE: Recursive clones are not supported yet. # # ----------------------------------------------------------------------------- from __future__ import annotations import dataclasses as D import pathlib import typing as T import subprocess import shutil @D.dataclass class Option: name: str value: T.Optional[str] = None def __str__(self) -> str: prefix = "-" if len(self.name) == 1 else "--" return f"{prefix}{self.name}" + (f" {self.value}" if self.value else "") @D.dataclass class Command: cmd: str args: list[str] = D.field(default_factory=list) opts: list[Option] = D.field(default_factory=list) def append_args(self, *args: str) -> Command: self.args.extend(args) return self def append_options(self, *args: str, **kwargs: str) -> Command: self.opts.extend([Option(name=name) for name in args]) self.opts.extend([Option(name=k, value=v) for k, v in kwargs.items()]) return self def run(self) -> int: command_line = [str(self.cmd)] + self.args + [str(opt) for opt in self.opts] try: print(f"Running command: {' '.join(command_line)}") result = subprocess.run(command_line, check=True) return result.returncode except subprocess.CalledProcessError as e: print(f"Command failed with return code {e.returncode}") return e.returncode def __post_init__(self): if not (pathlib.Path(self.cmd).is_absolute() or shutil.which(self.cmd)): raise FileNotFoundError(f"Command {self.cmd} not found in PATH") def parse_modules(repo_dir: pathlib.Path) -> T.List[T.Tuple[str, str]]: modules_path = repo_dir / ".gitmodules" if not modules_path.exists(): raise FileNotFoundError(".gitmodules file not found") print("Parsing .gitmodules to obtain the list of submodules...") submodules = [] submodule_path = None submodule_url = None with open(modules_path, "r") as file: for line in file: line = line.strip() if line.startswith("[submodule"): # Start of a new submodule entry, save the previous one if complete if submodule_path and submodule_url: submodules.append((submodule_path, submodule_url)) # Reset for the new submodule entry submodule_path = None submodule_url = None elif line.startswith("path ="): submodule_path = line.split("=", 1)[1].strip() elif line.startswith("url ="): submodule_url = line.split("=", 1)[1].strip() # Add the last submodule if present if submodule_path and submodule_url: submodules.append((submodule_path, submodule_url)) return submodules def update(repo_dir: T.Optional[pathlib.Path] = None, shallow_clones: bool = False): repo_dir = repo_dir or pathlib.Path.cwd() print(f"Repository: {repo_dir}") try: submodules = parse_modules(repo_dir) except FileNotFoundError as e: print(e) return if len(submodules) == 0: raise AttributeError("The repository does not have any submodules.") for submodule_path, submodule_url in submodules: submodule_full_path = repo_dir / submodule_path if ( not submodule_full_path.exists() or not (submodule_full_path / ".git").exists() ): print( f"{'Shallow-c' if shallow_clones else 'C'}loning submodule: {submodule_url} to: {submodule_path}" ) cmd = Command(cmd="gix").append_args( "clone", submodule_url, str(submodule_full_path) ) if shallow_clones: cmd.append_options(depth="1") result = cmd.run() if result != 0: print(f"Failed to clone submodule {submodule_path}") else: print(f"Cloned submodule {submodule_path} successfully") if __name__ == "__main__": update() print( "To register the submodules, run:\n\n\tgit submodule update --init --recursive\n" ) print( "NOTE: the clones of recursive sobmodules, if any, won't be optimized as it's not implemented yet (by us)." )