plum-tools 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plum_tools/.plum_tools.yaml +20 -0
- plum_tools/__init__.py +10 -0
- plum_tools/conf.py +65 -0
- plum_tools/exceptions.py +38 -0
- plum_tools/find_imports.py +142 -0
- plum_tools/gitrepo.py +144 -0
- plum_tools/gitstash.py +106 -0
- plum_tools/pipmi.py +415 -0
- plum_tools/pping.py +84 -0
- plum_tools/prn.py +485 -0
- plum_tools/pssh.py +127 -0
- plum_tools/utils/__init__.py +11 -0
- plum_tools/utils/git.py +97 -0
- plum_tools/utils/parser.py +52 -0
- plum_tools/utils/printer.py +99 -0
- plum_tools/utils/sshconf.py +222 -0
- plum_tools/utils/utils.py +253 -0
- plum_tools-0.5.0.dist-info/METADATA +224 -0
- plum_tools-0.5.0.dist-info/RECORD +22 -0
- plum_tools-0.5.0.dist-info/WHEEL +4 -0
- plum_tools-0.5.0.dist-info/entry_points.txt +8 -0
- plum_tools-0.5.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
default_ssh_conf:
|
|
2
|
+
user: root
|
|
3
|
+
port: 22
|
|
4
|
+
identityfile: ~/.ssh/id_rsa
|
|
5
|
+
|
|
6
|
+
host_type_default: 10.10.100
|
|
7
|
+
host_type_2: 192.168.1
|
|
8
|
+
|
|
9
|
+
ipmi_interval: 100
|
|
10
|
+
|
|
11
|
+
projects:
|
|
12
|
+
default:
|
|
13
|
+
src: ~/PythonProjects/plum_tools
|
|
14
|
+
dest: /home/seekplum/plum_tools
|
|
15
|
+
exclude:
|
|
16
|
+
- .idea
|
|
17
|
+
- .vscode
|
|
18
|
+
- '*.pyc'
|
|
19
|
+
- '*.log'
|
|
20
|
+
delete: 0
|
plum_tools/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""
|
|
2
|
+
#=============================================================================
|
|
3
|
+
# ProjectName: plum_tools
|
|
4
|
+
# FileName: __init__.py
|
|
5
|
+
# Author: seekplum
|
|
6
|
+
# Email: 1131909224m@sina.cn
|
|
7
|
+
# HomePage: seekplum.github.io
|
|
8
|
+
# Create: 2019-04-04 22:01
|
|
9
|
+
#=============================================================================
|
|
10
|
+
"""
|
plum_tools/conf.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
#=============================================================================
|
|
3
|
+
# ProjectName: plum_tools
|
|
4
|
+
# FileName: conf
|
|
5
|
+
# Desc: 相关常量配置信息
|
|
6
|
+
# Author: seekplum
|
|
7
|
+
# Email: 1131909224m@sina.cn
|
|
8
|
+
# HomePage: seekplum.github.io
|
|
9
|
+
# Create: 2018-07-06 18:39
|
|
10
|
+
#=============================================================================
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
from enum import IntEnum, StrEnum
|
|
15
|
+
|
|
16
|
+
VERSION = "0.5.0"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
STASH_UUID = "plum123456789987654321plum"
|
|
20
|
+
COMMAND_TIMEOUT = 3 # 执行命令超时时间
|
|
21
|
+
PROCESSES_NUMBER = 100 # 进程数量
|
|
22
|
+
LOCAL_HOST = "__localhost__"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class GitCommand(StrEnum):
|
|
26
|
+
"""git 相关命令"""
|
|
27
|
+
|
|
28
|
+
STASH_LIST = "git stash list" # 检查是否有文件在储藏区
|
|
29
|
+
STATUS_DEFAULT = "git status" # 检查文件状态
|
|
30
|
+
STATUS_SHORT = "git status -s" # 检查文件状态,简短输出,只能看到文件是否有改动,无法确认是否落后、超前远程分支
|
|
31
|
+
BRANCH_ABBREV = "git rev-parse --abbrev-ref HEAD" # 查询当前分支名
|
|
32
|
+
STASH_SAVE = 'git stash save "%s"' # 保存修改的文件到储藏区
|
|
33
|
+
STASH_POP = "git stash pop --index %s" # 把储藏的文件恢复
|
|
34
|
+
GIT_CHECKOUT = "git checkout %s" # 切换分支
|
|
35
|
+
|
|
36
|
+
PULL_KEYWORD = '"git pull"' # 落后远程分支关键字
|
|
37
|
+
PUSH_KEYWORD = '"git push"' # 超前远程分支关键字
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class OsCommand(StrEnum):
|
|
41
|
+
"""系统 相关命令"""
|
|
42
|
+
|
|
43
|
+
FIND_COMMAND = 'find %s -name ".git"' # 通过系统命令查找文件路径
|
|
44
|
+
PING_COMMAND = "ping -W 3 -c 1 %s" # ping命令 -W 超时时间 -c 次数
|
|
45
|
+
# ipmi命令
|
|
46
|
+
IPMI_COMMAND = "ipmitool -I lanplus -H %(ip)s -U %(user)s -P %(password)s %(command)s"
|
|
47
|
+
STAT_COMMAND = "stat %s"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class SSHConfig(IntEnum):
|
|
51
|
+
"""ssh配置相关"""
|
|
52
|
+
|
|
53
|
+
DEFAULT_SSH_PORT = 22
|
|
54
|
+
CONNECT_TIMEOUT = 3
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class PathConfig(StrEnum):
|
|
58
|
+
"""相关配置文件"""
|
|
59
|
+
|
|
60
|
+
HOME = os.environ["HOME"]
|
|
61
|
+
ROOT = os.path.dirname(os.path.abspath(__file__))
|
|
62
|
+
PLUM_YML_NAME = ".plum_tools.yaml" # 项目需要的配置文件名
|
|
63
|
+
PLUM_YML_PATH = os.path.join(HOME, PLUM_YML_NAME) # 项目需要的配置文件路径
|
|
64
|
+
SSH_CONFIG_NAME = ".ssh/config" # ssh配置文件名
|
|
65
|
+
SSH_CONFIG_PATH = os.path.join(HOME, SSH_CONFIG_NAME) # ssh配置文件路径
|
plum_tools/exceptions.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
#=============================================================================
|
|
3
|
+
# ProjectName: plum-tools
|
|
4
|
+
# FileName: exceptions
|
|
5
|
+
# Desc: 所有的异常类
|
|
6
|
+
# Author: seekplum
|
|
7
|
+
# Email: 1131909224m@sina.cn
|
|
8
|
+
# HomePage: seekplum.github.io
|
|
9
|
+
# Create: 2018-07-05 22:02
|
|
10
|
+
#=============================================================================
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RunCmdError(Exception):
|
|
15
|
+
"""执行命令异常"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, message: str, out_msg: str, err_msg: str) -> None:
|
|
18
|
+
"""初始化参数
|
|
19
|
+
|
|
20
|
+
:param message: 错误提示信息
|
|
21
|
+
:param out_msg: 执行命令输出结果
|
|
22
|
+
:param err_msg: 执行命令错误信息
|
|
23
|
+
"""
|
|
24
|
+
super().__init__(message) # pylint: disable=R1725
|
|
25
|
+
self.out_msg = out_msg
|
|
26
|
+
self.err_msg = err_msg
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class RunCmdTimeout(Exception):
|
|
30
|
+
"""执行系统命令超时"""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SSHException(Exception):
|
|
34
|
+
"""ssh异常"""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class SystemTypeError(Exception):
|
|
38
|
+
"""系统类型异常"""
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import sys
|
|
3
|
+
import typing as t
|
|
4
|
+
from enum import StrEnum
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from .utils.parser import add_extra_argument, get_base_parser
|
|
8
|
+
|
|
9
|
+
STD_LIB_MODULE_NAMES = sys.stdlib_module_names
|
|
10
|
+
MODULE_NAME_PAIRS = {
|
|
11
|
+
"PIL": "pillow",
|
|
12
|
+
"grpc": "grpcio",
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class FindMode(StrEnum):
|
|
17
|
+
STD = "std" # 标准库
|
|
18
|
+
ALL = "all" # 所有库
|
|
19
|
+
THIRD_PARTY = "3rd-party" # 第三方库。这个会把项目本身的包也包含进来
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
ModuleDict = t.TypedDict(
|
|
23
|
+
"ModuleDict",
|
|
24
|
+
{
|
|
25
|
+
"name": str,
|
|
26
|
+
"type": t.Literal["import", "from"],
|
|
27
|
+
"origin_name": str,
|
|
28
|
+
"mode": t.Literal[FindMode.STD, FindMode.THIRD_PARTY],
|
|
29
|
+
},
|
|
30
|
+
)
|
|
31
|
+
GeneratorModuleDict = t.Generator[ModuleDict, None, None]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def calc_mode(name: str) -> t.Literal[FindMode.STD, FindMode.THIRD_PARTY]:
|
|
35
|
+
if name in STD_LIB_MODULE_NAMES:
|
|
36
|
+
return FindMode.STD
|
|
37
|
+
return FindMode.THIRD_PARTY
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def parse_imports(source: str) -> GeneratorModuleDict:
|
|
41
|
+
tree = ast.parse(source)
|
|
42
|
+
|
|
43
|
+
for node in ast.walk(tree):
|
|
44
|
+
if isinstance(node, ast.Import):
|
|
45
|
+
for name in node.names:
|
|
46
|
+
first_name = name.name.split(".")[0]
|
|
47
|
+
yield ModuleDict(
|
|
48
|
+
type="import",
|
|
49
|
+
origin_name=name.name,
|
|
50
|
+
name=first_name,
|
|
51
|
+
mode=calc_mode(first_name),
|
|
52
|
+
)
|
|
53
|
+
elif isinstance(node, ast.ImportFrom):
|
|
54
|
+
if node.module:
|
|
55
|
+
if node.level > 0: # 不考虑相对导入
|
|
56
|
+
continue
|
|
57
|
+
first_name = node.module.split(".")[0]
|
|
58
|
+
yield ModuleDict(
|
|
59
|
+
type="from",
|
|
60
|
+
origin_name=node.module,
|
|
61
|
+
name=first_name,
|
|
62
|
+
mode=calc_mode(first_name),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def find_imports_by_file(file_path: str) -> GeneratorModuleDict:
|
|
67
|
+
with open(file_path, encoding="utf-8") as f:
|
|
68
|
+
yield from parse_imports(f.read())
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def find_imports(
|
|
72
|
+
project_dir: str, *, mode: FindMode = FindMode.ALL, ignore_paths: t.List[str] | None = None
|
|
73
|
+
) -> GeneratorModuleDict:
|
|
74
|
+
exists = set()
|
|
75
|
+
project_path = Path(project_dir)
|
|
76
|
+
ignore_path_set = {str(Path(path)) for path in ignore_paths or []}
|
|
77
|
+
ignore_dir_parts = {".venv", "__pycache__"}
|
|
78
|
+
my_module_names = {path.name for path in project_path.iterdir()}
|
|
79
|
+
src_path = project_path / "src"
|
|
80
|
+
if src_path.is_dir():
|
|
81
|
+
my_module_names.update(path.name for path in src_path.iterdir())
|
|
82
|
+
|
|
83
|
+
for file in project_path.rglob("*.py"):
|
|
84
|
+
file_path = str(file)
|
|
85
|
+
relative_parts = file.relative_to(project_path).parts
|
|
86
|
+
# 遍历目录中的所有文件,除了.venv和__pycache__目录
|
|
87
|
+
if file_path in ignore_path_set or any(part in ignore_dir_parts for part in relative_parts):
|
|
88
|
+
continue
|
|
89
|
+
if file.absolute() == Path(__file__).absolute():
|
|
90
|
+
continue
|
|
91
|
+
try:
|
|
92
|
+
for module in find_imports_by_file(file_path):
|
|
93
|
+
if module["name"] in exists or module["name"] in my_module_names:
|
|
94
|
+
continue
|
|
95
|
+
exists.add(module["name"])
|
|
96
|
+
if mode in (FindMode.ALL, module["mode"]):
|
|
97
|
+
yield module
|
|
98
|
+
except Exception as e:
|
|
99
|
+
print(f"Error parsing {file_path}: {e}")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def main():
|
|
103
|
+
parser = get_base_parser()
|
|
104
|
+
parser.add_argument(
|
|
105
|
+
"--mode",
|
|
106
|
+
"-m",
|
|
107
|
+
type=FindMode,
|
|
108
|
+
choices=list(FindMode),
|
|
109
|
+
default=FindMode.THIRD_PARTY,
|
|
110
|
+
help="Find mode",
|
|
111
|
+
)
|
|
112
|
+
parser.add_argument(
|
|
113
|
+
"--project-dir",
|
|
114
|
+
"-p",
|
|
115
|
+
type=str,
|
|
116
|
+
dest="project_dir",
|
|
117
|
+
default=".",
|
|
118
|
+
help="Project directory",
|
|
119
|
+
)
|
|
120
|
+
parser.add_argument(
|
|
121
|
+
"--ignore-path",
|
|
122
|
+
"-i",
|
|
123
|
+
required=False,
|
|
124
|
+
action="store",
|
|
125
|
+
dest="ignore_paths",
|
|
126
|
+
nargs="+",
|
|
127
|
+
help="Ignore paths",
|
|
128
|
+
)
|
|
129
|
+
add_extra_argument(parser)
|
|
130
|
+
args = parser.parse_args()
|
|
131
|
+
modules = sorted(
|
|
132
|
+
find_imports(args.project_dir, mode=args.mode, ignore_paths=args.ignore_paths),
|
|
133
|
+
key=lambda x: (x["mode"], x["name"]),
|
|
134
|
+
)
|
|
135
|
+
extra_modules = args.extra
|
|
136
|
+
for module in modules:
|
|
137
|
+
name = module["name"]
|
|
138
|
+
if name in MODULE_NAME_PAIRS:
|
|
139
|
+
name = MODULE_NAME_PAIRS[name]
|
|
140
|
+
elif name in extra_modules:
|
|
141
|
+
name = extra_modules[name]
|
|
142
|
+
print(name)
|
plum_tools/gitrepo.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""
|
|
2
|
+
#=============================================================================
|
|
3
|
+
# ProjectName: plum-tools
|
|
4
|
+
# FileName: exceptions
|
|
5
|
+
# Desc: 查找指定路径下所有被改动的git仓库
|
|
6
|
+
# Author: seekplum
|
|
7
|
+
# Email: 1131909224m@sina.cn
|
|
8
|
+
# HomePage: seekplum.github.io
|
|
9
|
+
# Create: 2018-07-05 22:02
|
|
10
|
+
#=============================================================================
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import functools
|
|
14
|
+
import os
|
|
15
|
+
from collections.abc import Generator
|
|
16
|
+
from multiprocessing import Pool
|
|
17
|
+
|
|
18
|
+
from .conf import PROCESSES_NUMBER
|
|
19
|
+
from .utils.git import check_is_git_repository, check_repository_modify_status, check_repository_stash
|
|
20
|
+
from .utils.parser import get_base_parser
|
|
21
|
+
from .utils.printer import print_error, print_warn
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def find_git_project_for_python(path: str) -> Generator[str, None, None]:
|
|
25
|
+
"""查找目录下所有的 git仓库
|
|
26
|
+
|
|
27
|
+
:param path 要被检查的目录
|
|
28
|
+
:example path "/tmp/git"
|
|
29
|
+
|
|
30
|
+
>>> for path in find_git_project_for_python("/tmp"):
|
|
31
|
+
... print path
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
for root, _, _ in os.walk(path):
|
|
36
|
+
if check_is_git_repository(root):
|
|
37
|
+
yield root
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def check_project(path: str, stash: bool = True) -> dict:
|
|
41
|
+
"""检查git项目
|
|
42
|
+
|
|
43
|
+
:param path 仓库路径
|
|
44
|
+
:example path /tmp/git
|
|
45
|
+
|
|
46
|
+
:param stash 是否显示储藏信息
|
|
47
|
+
:example stash True
|
|
48
|
+
|
|
49
|
+
:return result {
|
|
50
|
+
path: 仓库路径
|
|
51
|
+
output: 检查输出信息
|
|
52
|
+
status:
|
|
53
|
+
True 仓库有文件进行了修改或有储藏文件
|
|
54
|
+
False 仓库没有和远程一致。且没有储藏文件
|
|
55
|
+
}
|
|
56
|
+
:example result {
|
|
57
|
+
"path": "/tmp/git",
|
|
58
|
+
"status": False,
|
|
59
|
+
"output": ""
|
|
60
|
+
}
|
|
61
|
+
"""
|
|
62
|
+
result = {
|
|
63
|
+
"path": path,
|
|
64
|
+
"status": False,
|
|
65
|
+
}
|
|
66
|
+
# 检查文件是否改动
|
|
67
|
+
status_result, status_out = check_repository_modify_status(path)
|
|
68
|
+
if status_result:
|
|
69
|
+
result["status"] = True
|
|
70
|
+
result["output"] = status_out
|
|
71
|
+
return result
|
|
72
|
+
|
|
73
|
+
if not stash:
|
|
74
|
+
return result
|
|
75
|
+
# 检查是否有文件储藏
|
|
76
|
+
stash_result, stash_out = check_repository_stash(path)
|
|
77
|
+
if stash_result:
|
|
78
|
+
result["status"] = True
|
|
79
|
+
result["output"] = stash_out
|
|
80
|
+
return result
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def check_projects(projects: list[str], detail: bool, stash: bool = True) -> None:
|
|
84
|
+
"""检查指导目录下所有的仓库是否有修改
|
|
85
|
+
|
|
86
|
+
当仓库中有内容被修改时,打印黄色警告信息
|
|
87
|
+
|
|
88
|
+
:param projects 需要检查的目录列表
|
|
89
|
+
:example ["/tmp"]
|
|
90
|
+
|
|
91
|
+
:param detail 是否显示详细错误信息
|
|
92
|
+
:example False
|
|
93
|
+
|
|
94
|
+
:param stash 是否显示储藏信息
|
|
95
|
+
:example False
|
|
96
|
+
"""
|
|
97
|
+
targets = [path for project_path in projects for path in find_git_project_for_python(project_path)]
|
|
98
|
+
with Pool(processes=PROCESSES_NUMBER) as pool:
|
|
99
|
+
result = pool.map(functools.partial(check_project, stash=stash), targets)
|
|
100
|
+
for item in result:
|
|
101
|
+
# 仓库中文件没有被改动而且没有文件被储藏了
|
|
102
|
+
if not item["status"]:
|
|
103
|
+
continue
|
|
104
|
+
print_warn(item["path"])
|
|
105
|
+
|
|
106
|
+
# 打印详细错误信息
|
|
107
|
+
if not detail:
|
|
108
|
+
continue
|
|
109
|
+
print_error(item["output"])
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def main() -> None:
|
|
113
|
+
"""程序主入口"""
|
|
114
|
+
parser = get_base_parser()
|
|
115
|
+
parser.add_argument(
|
|
116
|
+
"-p",
|
|
117
|
+
"--path",
|
|
118
|
+
action="store",
|
|
119
|
+
required=False,
|
|
120
|
+
dest="path",
|
|
121
|
+
nargs="+",
|
|
122
|
+
default=[os.environ["HOME"]],
|
|
123
|
+
help="The directory path to check",
|
|
124
|
+
)
|
|
125
|
+
parser.add_argument(
|
|
126
|
+
"-d",
|
|
127
|
+
"--detail",
|
|
128
|
+
action="store_true",
|
|
129
|
+
required=False,
|
|
130
|
+
dest="detail",
|
|
131
|
+
default=False,
|
|
132
|
+
help="display staged details",
|
|
133
|
+
)
|
|
134
|
+
parser.add_argument(
|
|
135
|
+
"-s",
|
|
136
|
+
"--stash",
|
|
137
|
+
action="store_true",
|
|
138
|
+
required=False,
|
|
139
|
+
dest="stash",
|
|
140
|
+
default=False,
|
|
141
|
+
help="display stash details",
|
|
142
|
+
)
|
|
143
|
+
args = parser.parse_args()
|
|
144
|
+
check_projects(args.path, args.detail, args.stash)
|
plum_tools/gitstash.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""
|
|
2
|
+
#=============================================================================
|
|
3
|
+
# ProjectName: plum_tools
|
|
4
|
+
# FileName: gitstash
|
|
5
|
+
# Desc: 外部传入一个branch,保存本地未提交的修改,然后切换到branch,将上次该branch保存的未提交的结果stash pop出来
|
|
6
|
+
# 命令: gitstash master
|
|
7
|
+
# 描述: 在当前路径下切换到master分支
|
|
8
|
+
# Author: seekplum
|
|
9
|
+
# Email: 1131909224m@sina.cn
|
|
10
|
+
# HomePage: seekplum.github.io
|
|
11
|
+
# Create: 2018-07-07 16:29
|
|
12
|
+
#=============================================================================
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
from .conf import STASH_UUID, GitCommand
|
|
19
|
+
from .exceptions import RunCmdError
|
|
20
|
+
from .utils.git import check_repository_modify_status, check_repository_stash, get_current_branch_name
|
|
21
|
+
from .utils.parser import get_base_parser
|
|
22
|
+
from .utils.printer import print_warn
|
|
23
|
+
from .utils.utils import cd, run_cmd
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class GitCheckoutStash:
|
|
27
|
+
"""切换分支前储藏文件"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, current_branch: str, new_branch: str) -> None:
|
|
30
|
+
"""初始化信息
|
|
31
|
+
|
|
32
|
+
:param current_branch: 当前分支名
|
|
33
|
+
:example current_branch master
|
|
34
|
+
|
|
35
|
+
:param new_branch: 新的分支名
|
|
36
|
+
:example new_branch release-1.0.0
|
|
37
|
+
"""
|
|
38
|
+
self._current_branch = current_branch
|
|
39
|
+
self._current_path = os.getcwd() # 当前执行命令的路径
|
|
40
|
+
self._new_branch = new_branch
|
|
41
|
+
self._mark = "-"
|
|
42
|
+
self._stash_uuid = f"{self._current_branch}{self._mark}{STASH_UUID}"
|
|
43
|
+
|
|
44
|
+
def _stash(self) -> None:
|
|
45
|
+
"""储藏文件"""
|
|
46
|
+
# 分支有修改,先进行stash储藏
|
|
47
|
+
is_modify, _ = check_repository_modify_status(self._current_path)
|
|
48
|
+
if is_modify:
|
|
49
|
+
cmd = GitCommand.STASH_SAVE % self._stash_uuid
|
|
50
|
+
with cd(self._current_path):
|
|
51
|
+
run_cmd(cmd)
|
|
52
|
+
|
|
53
|
+
def _check_branch(self) -> bool:
|
|
54
|
+
"""检查分支是否一致
|
|
55
|
+
|
|
56
|
+
:return
|
|
57
|
+
True: 当前分支和要切换的分支一致
|
|
58
|
+
False: 当前分支和要切换的分支不一致
|
|
59
|
+
"""
|
|
60
|
+
return self._current_branch == self._new_branch
|
|
61
|
+
|
|
62
|
+
def _checkout(self) -> None:
|
|
63
|
+
"""切换分支"""
|
|
64
|
+
# 切换到新分支
|
|
65
|
+
cmd = GitCommand.GIT_CHECKOUT % self._new_branch
|
|
66
|
+
with cd(self._current_path):
|
|
67
|
+
run_cmd(cmd)
|
|
68
|
+
|
|
69
|
+
def _apply(self) -> None:
|
|
70
|
+
"""恢复储藏的文件"""
|
|
71
|
+
# 检查切换后的分支是否有储藏文件
|
|
72
|
+
is_stash, stash_out = check_repository_stash(self._current_path)
|
|
73
|
+
if not is_stash:
|
|
74
|
+
return
|
|
75
|
+
for line in stash_out.splitlines():
|
|
76
|
+
stash_string, _ = line.split(":", 1)
|
|
77
|
+
stash_save = f"{self._new_branch}{self._mark}{STASH_UUID}"
|
|
78
|
+
if stash_save.strip() in line:
|
|
79
|
+
cmd = GitCommand.STASH_POP % stash_string
|
|
80
|
+
with cd(self._current_path):
|
|
81
|
+
run_cmd(cmd)
|
|
82
|
+
break
|
|
83
|
+
|
|
84
|
+
def checkout(self) -> None:
|
|
85
|
+
"""储藏文件切换分支"""
|
|
86
|
+
# 不需要切换
|
|
87
|
+
if self._check_branch():
|
|
88
|
+
return
|
|
89
|
+
self._stash()
|
|
90
|
+
self._checkout()
|
|
91
|
+
self._apply()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def main() -> None:
|
|
95
|
+
"""程序主入口"""
|
|
96
|
+
parser = get_base_parser()
|
|
97
|
+
parser.add_argument(dest="branch", action="store", help="specify branch")
|
|
98
|
+
args = parser.parse_args()
|
|
99
|
+
new_branch = args.branch
|
|
100
|
+
try:
|
|
101
|
+
current_branch = get_current_branch_name()
|
|
102
|
+
except RunCmdError:
|
|
103
|
+
print_warn("当前目录不是一个git仓库")
|
|
104
|
+
sys.exit(1)
|
|
105
|
+
stash = GitCheckoutStash(current_branch, new_branch)
|
|
106
|
+
stash.checkout()
|