Git 原生的sumodules
功能无法实现将子模块
存放在父级目录, 而放入子目录时候会存在多份代码,交叉依赖的问题。所以需要搭建一个脚本工具来解决多个模块之间的依赖关系。
通过配置文件指定git 仓库url、名称、分支、版本
支持解析原生的.gitmodules
文件 (优先解析)
[submodule "modules/my-module-base"] path = modules/my-module-base url = http://gitlab.xxx.com/my-project/my-module-base.git [submodule "modules/my-module-config"] path = modules/my-module-config url = http://gitlab.xxx.com/my-project/my-module-config.git
支持dependent.json
g格式配置, 第一次自动生成模板json文件,需要手动再修改成项目所依赖的子模块
{ "my-module-base": { "url": "http://gitlab.xxx.com/my-project/my-module-base.git", "path": "../base", "branch": "master", "version": "" }, "my-module-config": { "url": "http://gitlab.xxx.com/my-project/my-module-config.git", "path": "../config", "branch": "master", "version": "" } }
.gitmodules 和 depentent.json 二选一,前者 优先解析
日志文件git_multi_module.log
2021-04-16 16:48:25,347 MainThread INFO 151: Git multi-modules checkout 2021-04-16 16:48:25,347 MainThread INFO 53: No search `.gitmodules` file, sik to git submodules parser. 2021-04-16 16:48:25,347 MainThread INFO 90: Load ./dependent.json file. 2021-04-16 16:48:25,347 MainThread INFO 111: Clone http://gitlab.xxx.com/my-project/my-module-base.git master to ../base 2021-04-16 16:48:27,936 MainThread INFO 114: Done 2021-04-16 16:48:27,936 MainThread INFO 111: Clone http://gitlab.xxx.com/my-project/my-module-config.git master to ../config 2021-04-16 16:48:29,477 MainThread INFO 114: Done
pyinstaller git_multi_module.py -F
生的Exe;git_multi_module.exe
文件到工程目录;exe
文件运行直接解析.submodule
文件,如果存在则解析并跳过后面步骤,否则检测并生产dependent.json
文件;dependent.json
成指定模块地址;git_multi_module.exe
文件拉取子模块代码;developer-manual
工程仓:http://gitlab.xxx.com/my-project/developer-manual.git
{ "my-module-base": { "url": "http://gitlab.xxx.com/my-project/my-module-base.git", "path": "my-module-base", "branch": "master", "version": "" } }
$(RootDir)developer-manual\tools\git_submodules\git_multi_module.exe $(SolutionDir)dependent.json $(RootDir) 1
1>------ 已启动生成: 项目: my_module_config, 配置: Debug Win32 ------ 1> 2021-04-19 14:49:11,670 MainThread INFO 156: Git multi-modules checkout 1> 2021-04-19 14:49:11,670 MainThread INFO 40: Root path: D:\xxx_workplace\my-module-config\..\ 1> 2021-04-19 14:49:11,670 MainThread INFO 41: Config file: D:\xxx_workplace\my-module-config\dependent.json 1> 2021-04-19 14:49:11,671 MainThread INFO 42: Reset before pull: True 1> 2021-04-19 14:49:11,671 MainThread INFO 58: No search `.gitmodules` file, skip git submodules parser. 1> 2021-04-19 14:49:11,672 MainThread INFO 95: Load D:\xxx_workplace\my-module-config\dependent.json file. 1> 2021-04-19 14:49:11,672 MainThread INFO 103: Pull http://gitlab.xxx.com/my-project/my-module-base.git master to D:\xxx_workplace\my-module-base 1> 2021-04-19 14:49:12,542 MainThread INFO 109: Done 1> my_module_config.vcxproj -> D:\xxx_workplace\xxx-module-config\..\bin\Debug\my_module_config.dll
git_multi_module.py
from logging.handlers import RotatingFileHandler import os import json import logging from git import Repo class GitMultiModule(object): dependent_file = "./dependent.json" git_modules_file = ".gitmodules" dependent_template = { "pos-module-base": { "url": "https://github.com/xxx/xxx.git", "path": "", "branch": "master", "version": "" } } _dependents_ = dict() def __init__(self, file_name="", root_path_="./", reset_before_pull_=False): if file_name: self.dependent_file = file_name self.root_path = root_path_ self.reset_before_pull = reset_before_pull_ logging.info("Root path: {}".format(self.root_path)) logging.info("Config file: {}".format(self.dependent_file)) logging.info("Reset before pull: {}".format(self.reset_before_pull)) if not self.load_git_modules_file(): self.load_file() def make_dependent_template(self) -> bool: logging.info("Make dependent template json file...") with open(self.dependent_file, "w", encoding="utf-8") as f: json.dump(self.dependent_template, f, ensure_ascii=False, indent=4) logging.info("Done") msg = "Please configure the `{}` file with your project and restart this tools" logging.info(msg.format(self.dependent_file)) return False def load_git_modules_file(self) -> bool: if not os.path.exists(self.git_modules_file): logging.info("No search `{}` file, skip git submodules parser.".format(self.git_modules_file)) return False logging.info("Loading `{}` file...".format(self.git_modules_file)) from configparser import ConfigParser cf = ConfigParser() cf.read(self.git_modules_file) modules = cf.sections() git_submodules = dict() for section in modules: try: _, name, _ = section.split('"') m_path = cf.get(section, "path") m_url = cf.get(section, "url") m_version = cf.has_option(section, "version") and cf.get(section, "version") or "" m_branch = cf.has_option(section, "branch") and cf.get(section, "branch") or "master" git_submodules[name] = dict(path=m_path, url=m_url, branch=m_branch, version=m_version) except Exception as err: logging.error("Load submodules section error:{}".format(err)) self._dependents_ = git_submodules if git_submodules: return True else: return False def load_file(self) -> bool: if not os.path.exists(self.dependent_file): logging.error("No search `{}` file!".format(self.dependent_file)) return self.make_dependent_template() try: with open(self.dependent_file, encoding="utf-8") as f: json_obj = json.load(f) if isinstance(json_obj, dict): self._dependents_ = json_obj logging.info("Load {} file.".format(self.dependent_file)) return True except Exception as err: logging.error("Load dependent file error:{}".format(err)) return False def pull(self, m_url, m_branch, m_path, m_version): try: logging.info("Pull {} {} to {}".format(m_url, m_branch, m_path)) r = Repo(m_path) self.reset_before_pull and r.git.execute("git reset HEAD --hard") if r.active_branch.name != m_branch: r.git.checkout(m_branch) r.remote().pull() logging.info("Done") except Exception as err: _ = self logging.error("Pull error:{}".format(err)) def clone(self, m_url, m_branch, m_path, m_version): try: logging.info("Clone {} {} to {}".format(m_url, m_branch, m_path)) r = Repo.clone_from(m_url, m_path) # 拉取远程代码 r.git.checkout(m_branch) logging.info("Done") except Exception as err: _ = self logging.error("Pull clone {}".format(err)) def clone_or_pull(self, m_url, m_branch, m_path, m_version): m_path = os.path.abspath(self.root_path + m_path) git_path = os.path.join(m_path, ".git") if os.path.isdir(git_path): self.pull(m_url, m_branch, m_path, m_version) else: self.clone(m_url, m_branch, m_path, m_version) def check_modules(self): if not self._dependents_: logging.error("Dependent configuration error!") return for module_name, config in self._dependents_.items(): m_url = config.get("url") m_path = config.get("path") m_version = config.get("version") or "" m_branch = config.get("branch") or "master" self.clone_or_pull(m_url, m_branch, m_path, m_version) if __name__ == '__main__': log_fmt = r"%(asctime)s %(threadName)s %(levelname)6s %(lineno)5d: %(message)s" log_file = "git_multi_module.log" log_size = 1024 * 124 * 5 handlers = [ logging.StreamHandler(), RotatingFileHandler(log_file, maxBytes=log_size, backupCount=5, encoding="utf-8") ] log_param = dict(level=logging.INFO, handlers=handlers, format=log_fmt) logging.basicConfig(**log_param) logging.info("Git multi-modules checkout") import sys argv_len = len(sys.argv) config_file = argv_len > 1 and sys.argv[1] or "" root_path = argv_len > 2 and sys.argv[2] or "./" reset = argv_len > 3 and sys.argv[3] == "1" or False g = GitMultiModule(config_file, root_path, reset) g.check_modules()