概述
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
文件拉取子模块代码;
Visual stdio 配置
- 拉取
developer-manual
工程仓:http://gitlab.xxx.com/my-project/developer-manual.git
- 同记录拉取开发工程如: my-module-config, 配置依赖模块, 如:
{
"my-module-base": {
"url": "http://gitlab.xxx.com/my-project/my-module-base.git",
"path": "my-module-base",
"branch": "master",
"version": ""
}
}
- 打开VS 中的 项目 -》 属性 -》 配置属性 -》 生成事件 -》 预先生成事件 -》 命令行 填写入:
$(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()