template.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  1. import os
  2. import random
  3. import subprocess
  4. from abc import ABCMeta, abstractmethod
  5. from git import Repo
  6. from os.path import exists, split
  7. from time import time
  8. from system import plugin_class_loading, get_path
  9. sys_seeting = dict(
  10. shell=True,
  11. stdin=subprocess.PIPE,
  12. stdout=subprocess.PIPE,
  13. stderr=subprocess.STDOUT,
  14. universal_newlines=True,
  15. )
  16. git_path = "git" # git的地址,如果配置了环境变量则不需要修改
  17. stop_key = "【操作完成】" # 存储stopKey的global变量
  18. passwd = (
  19. "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM" # stopKey的候选词库
  20. )
  21. class GitBase(metaclass=ABCMeta):
  22. def __init__(self, repo_dir, *args, **kwargs):
  23. self.repo_dir = ''
  24. self.repo = None
  25. self.init(repo_dir)
  26. @abstractmethod
  27. def init(self, repo_dir):
  28. pass
  29. @staticmethod
  30. def make_stop_key(): # 生成一个随机stopKey
  31. code = ""
  32. for _ in range(8): # 八位随机数
  33. code += passwd[random.randint(0, len(passwd) - 1)] # 时间戳+8位随机数
  34. stop_key_ = (str(time()) + code).replace(".", "")
  35. return stop_key_
  36. def get_flie_list(self, file_list, is_file=True, pat=" "):
  37. if file_list == ".":
  38. file = ".."
  39. else:
  40. file_ = []
  41. for i in file_list:
  42. if i[: len(self.repo_dir)] == self.repo_dir:
  43. file_.append(i[len(self.repo_dir) + 1:]) # +1是为了去除/
  44. if not is_file:
  45. return file_
  46. file = pat.join(file_)
  47. return file
  48. @plugin_class_loading(get_path(r'template/gitrepo'))
  49. class ViewClasses(GitBase, metaclass=ABCMeta):
  50. def status(self): # 执行status
  51. return subprocess.Popen(
  52. f"echo 仓库状态: && {git_path} status && echo {self.make_stop_key()}",
  53. cwd=self.repo_dir,
  54. **sys_seeting,
  55. )
  56. def rm(self, file_list): # 删除版本库中的文件
  57. file = self.get_flie_list(file_list)
  58. return subprocess.Popen(
  59. f"echo 删除... && {git_path} rm {file}", cwd=self.repo_dir, **sys_seeting
  60. )
  61. def dir_list(self, all_=True):
  62. listfile = []
  63. if all_:
  64. listfile += [
  65. f'[当前分支] {self.repo.active_branch} 工作区{"不" if self.repo.is_dirty() else ""}干净 -> {self.name}'
  66. ]
  67. listfile += [
  68. f'{"[配置文件]" if i == ".git" else "[未跟踪]"if i in self.repo.untracked_files else "[已跟踪]"} {i}'
  69. for i in os.listdir(self.repo_dir)
  70. ]
  71. return listfile
  72. def log(self, graph, pretty, abbrev): # 执行log
  73. args = ""
  74. if graph:
  75. args += " --graph"
  76. if pretty:
  77. args += " --pretty=oneline"
  78. if abbrev:
  79. args += " --abbrev-commit"
  80. return subprocess.Popen(
  81. f"echo 仓库日志: && {git_path} log{args} && echo {self.make_stop_key()}",
  82. cwd=self.repo_dir,
  83. **sys_seeting,
  84. )
  85. def do_log(self): # 执行reflog
  86. return subprocess.Popen(
  87. f"echo 操作记录: && {git_path} reflog && echo {self.make_stop_key()}",
  88. cwd=self.repo_dir,
  89. **sys_seeting,
  90. )
  91. def diff(self, master="HEAD"): # 执行diff
  92. return subprocess.Popen(
  93. f"echo 文件日志: && {git_path} diff {master} && echo {self.make_stop_key()}",
  94. cwd=self.repo_dir,
  95. **sys_seeting,
  96. )
  97. def branch_view(self): # 查看本地分支和远程分支
  98. return subprocess.Popen(
  99. f"echo 仓库分支: && {git_path} branch -a && echo 远程仓库信息: && {git_path} remote -v && "
  100. f"echo 分支详情: && {git_path} branch -vv && echo {self.make_stop_key()}",
  101. cwd=self.repo_dir,
  102. **sys_seeting,
  103. )
  104. def get_stash_list(self): # 工作区列表
  105. return subprocess.Popen(
  106. f"echo 工作区列表: && {git_path} stash list && echo {self.make_stop_key()}",
  107. cwd=self.repo_dir,
  108. **sys_seeting,
  109. )
  110. def get_tag_list(self, condition=""):
  111. if condition != "":
  112. condition = f" -l {condition}"
  113. return subprocess.Popen(
  114. f"echo 标签列表: && {git_path} tag{condition} && echo {self.make_stop_key()}",
  115. cwd=self.repo_dir,
  116. **sys_seeting,
  117. )
  118. def search_commit(self, condition):
  119. return subprocess.Popen(
  120. f"echo 查询结果: && {git_path} show {condition} && echo {self.make_stop_key()}",
  121. cwd=self.repo_dir,
  122. **sys_seeting,
  123. )
  124. @plugin_class_loading(get_path(r'template/gitrepo'))
  125. class NewClasses(GitBase, metaclass=ABCMeta):
  126. def add(self, file_list):
  127. file = self.get_flie_list(file_list)
  128. return subprocess.Popen(
  129. f"echo 添加文件... && {git_path} add {file} && echo {self.make_stop_key()}",
  130. cwd=self.repo_dir,
  131. **sys_seeting,
  132. )
  133. def commit_file(self, m):
  134. return subprocess.Popen(
  135. f'echo 提交文件: && {git_path} commit -m "{m}" && echo {self.make_stop_key()}',
  136. cwd=self.repo_dir,
  137. **sys_seeting,
  138. )
  139. def new_branch(self, branch_name, origin): # 新建分支
  140. return subprocess.Popen(
  141. f"echo 新建分支... && {git_path} branch {branch_name} {origin} && echo {self.make_stop_key()}",
  142. cwd=self.repo_dir,
  143. **sys_seeting,
  144. )
  145. def save_stash(self): # 保存工作区
  146. return subprocess.Popen(
  147. f"echo 保存工作区... && {git_path} stash && echo {self.make_stop_key()}",
  148. cwd=self.repo_dir,
  149. **sys_seeting,
  150. )
  151. def cherry_pick(self, commit): # 补丁
  152. return subprocess.Popen(
  153. f"echo 补丁... && {git_path} cherry-pick {commit} && echo {self.make_stop_key()}",
  154. cwd=self.repo_dir,
  155. **sys_seeting,
  156. )
  157. def remote_add(self, remote, remote_name):
  158. return subprocess.Popen(
  159. f"echo 添加远程仓库... && {git_path} remote add {remote_name} {remote} && echo {self.make_stop_key()}",
  160. cwd=self.repo_dir,
  161. **sys_seeting,
  162. )
  163. def bind_branch(self, local_name, remote_name):
  164. return subprocess.Popen(
  165. f"echo 分支绑定... && {git_path} branch --set-upstream-to={remote_name} {local_name} && echo "
  166. f"{self.make_stop_key()}",
  167. cwd=self.repo_dir,
  168. **sys_seeting,
  169. )
  170. def add_tag(self, tag, commit, message=""):
  171. a = " -a"
  172. if message != "":
  173. message = f' -m "{message}"' # 自带空格
  174. else:
  175. a = ""
  176. if commit != "":
  177. commit = f" {commit}" # 自带空格
  178. return subprocess.Popen(
  179. f"echo 添加标签... && {git_path} tag{a} {tag}{commit}{message} && echo {self.make_stop_key()}",
  180. cwd=self.repo_dir,
  181. **sys_seeting,
  182. )
  183. @plugin_class_loading(get_path(r'template/gitrepo'))
  184. class RemoveClass(GitBase, metaclass=ABCMeta):
  185. def del_cached_file(self, file_list):
  186. file = self.get_flie_list(file_list)
  187. return subprocess.Popen(
  188. f"echo 撤销文件... && {git_path} rm --cached {file} && echo {self.make_stop_key()}",
  189. cwd=self.repo_dir,
  190. **sys_seeting,
  191. )
  192. def del_branch(self, branch_name, del_type): # 删除分支
  193. return subprocess.Popen(
  194. f"echo 删除分支... && {git_path} branch -{del_type} {branch_name} && echo {self.make_stop_key()}",
  195. cwd=self.repo_dir,
  196. **sys_seeting,
  197. )
  198. def drop_stash(self, stash_num="0"): # 删除工作区
  199. return subprocess.Popen(
  200. f"echo 删除工作区... && {git_path} stash drop stash@{{{stash_num}}} && echo {self.make_stop_key()}",
  201. cwd=self.repo_dir,
  202. **sys_seeting,
  203. )
  204. def del_remote(self, remote_name):
  205. return subprocess.Popen(
  206. f"echo 删除远程仓库... && {git_path} remote remove {remote_name} && echo {self.make_stop_key()}",
  207. cwd=self.repo_dir,
  208. **sys_seeting,
  209. )
  210. def del_tag(self, tag):
  211. return subprocess.Popen(
  212. f"echo 删除本地标签... && {git_path} tag -d {tag} && echo {self.make_stop_key()}",
  213. cwd=self.repo_dir,
  214. **sys_seeting,
  215. )
  216. def del_branch_remote(self, remote, remote_branch):
  217. remote_split = remote.split("/")
  218. remote_name = remote_split[0] # 获取主机名 1)
  219. branch_split = remote_branch.split("/")
  220. if len(branch_split) >= 2 and remote_name == "":
  221. remote_name = branch_split[0] # 2)
  222. remote_branch = "/".join(branch_split[1:]) # 2)
  223. return subprocess.Popen(
  224. f"echo 删除远程分支... && {git_path} push {remote_name} :{remote_branch} && echo {self.make_stop_key()}",
  225. cwd=self.repo_dir,
  226. **sys_seeting,
  227. )
  228. def del_tag_remote(self, remote, tag):
  229. return subprocess.Popen(
  230. f"echo 删除远程标签... && {git_path} push {remote} :refs/tags/{tag} && echo {self.make_stop_key()}",
  231. cwd=self.repo_dir,
  232. **sys_seeting,
  233. )
  234. @plugin_class_loading(get_path(r'template/gitrepo'))
  235. class BackClasses(GitBase, metaclass=ABCMeta):
  236. def reset(self, head="HEAD~1", reset_type=0):
  237. if reset_type == 0:
  238. type_ = "--mixed" # 退回到工作区
  239. elif reset_type == 1:
  240. type_ = "--soft" # 退回到暂存区
  241. else:
  242. type_ = "--hard" # 退回到暂存区
  243. return subprocess.Popen(
  244. f"echo 回退... && {git_path} reset {type_} {head} && echo {self.make_stop_key()}",
  245. cwd=self.repo_dir,
  246. **sys_seeting,
  247. )
  248. def checkout(self, file_list):
  249. if len(file_list) >= 1: # 多于一个文件,不用--,空格
  250. file = self.get_flie_list(file_list, pat=" ")
  251. return subprocess.Popen(
  252. f"echo 丢弃修改: && {git_path} checkout {file} && echo {self.make_stop_key()}",
  253. cwd=self.repo_dir,
  254. **sys_seeting,
  255. )
  256. elif len(file_list) == 1:
  257. return subprocess.Popen(
  258. f"echo 丢弃修改: && {git_path} checkout -- {file_list[0]} && echo {self.make_stop_key()}",
  259. cwd=self.repo_dir,
  260. **sys_seeting,
  261. )
  262. else:
  263. return subprocess.Popen(
  264. f"echo 丢弃修改: && {git_path} checkout * && echo {self.make_stop_key()}",
  265. cwd=self.repo_dir,
  266. **sys_seeting,
  267. )
  268. def apply_stash(self, stash_num="0"): # 恢复工作区
  269. return subprocess.Popen(
  270. f"echo 恢复工作区... && {git_path} stash apply stash@{{{stash_num}}} && echo {self.make_stop_key()}",
  271. cwd=self.repo_dir,
  272. **sys_seeting,
  273. )
  274. def reset_file(self, hard, file_list): # 注意版本回退是:Reset_File
  275. file = self.get_flie_list(file_list)
  276. return subprocess.Popen(
  277. f"""echo 回退文件... && {git_path} reset {hard} {file} && echo {self.make_stop_key()}""",
  278. cwd=self.repo_dir,
  279. **sys_seeting,
  280. )
  281. @plugin_class_loading(get_path(r'template/gitrepo'))
  282. class ParallelClasses(GitBase, metaclass=ABCMeta):
  283. def switch_branch(self, branch_name): # 切换分支
  284. return subprocess.Popen(
  285. f"echo 切换分支... && {git_path} switch {branch_name} && echo {self.make_stop_key()}",
  286. cwd=self.repo_dir,
  287. **sys_seeting,
  288. )
  289. def merge_branch(self, branch_name, no_ff, m=""): # 合并分支
  290. if no_ff:
  291. no_ff = f' --no-ff -m "{m}"' # --no-ff前有空格
  292. else:
  293. no_ff = ""
  294. return subprocess.Popen(
  295. f"echo 合并分支... && {git_path} merge{no_ff} {branch_name} && echo {self.make_stop_key()}",
  296. cwd=self.repo_dir,
  297. **sys_seeting,
  298. )
  299. def merge_abort(self): # 退出冲突处理
  300. return subprocess.Popen(
  301. f"echo 冲突处理退出... && {git_path} merge --abort && echo {self.make_stop_key()}",
  302. cwd=self.repo_dir,
  303. **sys_seeting,
  304. )
  305. def rename_branch(self, old_name, new_name):
  306. return subprocess.Popen(
  307. f"""echo 回退文件... && {git_path} branch -m {old_name} {new_name} && echo {self.make_stop_key()}""",
  308. cwd=self.repo_dir,
  309. **sys_seeting,
  310. )
  311. @plugin_class_loading(get_path(r'template/gitrepo'))
  312. class RemoteClasses(GitBase, metaclass=ABCMeta):
  313. def push_tag(self, tag, remote_name):
  314. return subprocess.Popen(
  315. f"echo 推送标签... && {git_path} push {remote_name} {tag} && echo {self.make_stop_key()}",
  316. cwd=self.repo_dir,
  317. **sys_seeting,
  318. )
  319. def pull_push(
  320. self,
  321. pull_or_push=0,
  322. remote="",
  323. remote_branch="",
  324. local="",
  325. allow=False,
  326. u=False,
  327. f=False,
  328. ):
  329. # 处理逻辑
  330. # 1)remote去斜杠第一个作为主机名字
  331. # 2) 从remote分离主机名(如果没指定)
  332. # 3) 如果local为空,用HEAD填充
  333. # 4) 如果以上后,主机名仍为空,则local和分支均为空
  334. split_ = remote.split("/")
  335. remote_name = split_[0] # 获取主机名 1)
  336. branch_split = remote_branch.split("/")
  337. if len(branch_split) >= 2 and remote_name == "":
  338. remote_name = branch_split[0] # 2)
  339. remote_branch = "/".join(branch_split[1:]) # 2)
  340. if local.replace(" ", "") == "":
  341. local = "HEAD" # 3)
  342. if remote_name == "": # 4)
  343. branch = ""
  344. else:
  345. if pull_or_push == 1:
  346. # 注意,local不可以为空,也不会为空
  347. if remote_branch != "":
  348. # git push <远程主机名> <本地分支名>:<远程分支名>
  349. branch = f"{local}:{remote_branch}"
  350. else:
  351. branch = f"{local}" # 要去掉冒号
  352. else:
  353. if remote_branch != "HEAD":
  354. branch = f"{remote_branch}:{local}"
  355. else:
  356. branch = f"{remote_branch}"
  357. if allow:
  358. history = " --allow-unrelated-histories"
  359. else:
  360. history = ""
  361. push_pull = {0: "pull", 1: f"push{' -u' if u else ''}{' -f' if f else ''}"}
  362. return subprocess.Popen(
  363. f"""echo 与服务器连接... && {git_path} {push_pull.get(pull_or_push, "pull")}{history} {remote_name} {branch}
  364. && echo {self.make_stop_key()}""",
  365. cwd=self.repo_dir,
  366. **sys_seeting,
  367. )
  368. def fetch(self, remote, remote_branch, local):
  369. # 处理逻辑
  370. # 1)remote去斜杠第一个作为主机名字
  371. # 2) 从remote分离主机名(如果没指定)
  372. # 3) 如果local为空,用HEAD填充
  373. # 4) 如果以上后,主机名仍为空,则local和分支均为空
  374. split_ = remote.split("/")
  375. remote_name = split_[0] # 获取主机名 1)
  376. branch_split = remote_branch.split("/")
  377. if len(branch_split) >= 2 and remote_name == "":
  378. remote_name = branch_split[0] # 2)
  379. remote_branch = "/".join(branch_split[1:]) # 2)
  380. if local.replace(" ", "") == "":
  381. local = "HEAD" # 3)
  382. if remote_name == "": # 4)
  383. branch = ""
  384. else:
  385. if remote_branch != "HEAD":
  386. # git push <远程主机名> <本地分支名>:<远程分支名>
  387. branch = f"{remote_branch}:{local}"
  388. else:
  389. branch = f"{remote_branch}"
  390. return subprocess.Popen(
  391. f"""echo 更新远程仓库... && {git_path} fetch {remote_name} {branch} && echo {self.make_stop_key()}""",
  392. cwd=self.repo_dir,
  393. **sys_seeting,
  394. )
  395. def clone(self, url):
  396. return subprocess.Popen(
  397. f"echo 克隆操作不被允许 && echo {self.make_stop_key()}",
  398. cwd=self.repo_dir,
  399. **sys_seeting,
  400. )
  401. @plugin_class_loading(get_path(r'template/gitrepo'))
  402. class GitRepo(ViewClasses, NewClasses, RemoveClass, BackClasses, ParallelClasses, RemoteClasses): # git的基类
  403. def init(self, repo_dir):
  404. self.url = None
  405. try:
  406. if exists(repo_dir + r"/.git"): # 是否为git 仓库
  407. pass
  408. elif repo_dir.endswith(".git"):
  409. repo_dir = repo_dir[:-5] # -5,得把/去掉
  410. else:
  411. assert False
  412. except (AssertionError, IndexError):
  413. subprocess.Popen(
  414. f"{git_path} init", cwd=self.repo_dir, **sys_seeting
  415. ).wait()
  416. self.repo_dir = repo_dir # 仓库地址(末尾不带/)
  417. self.repo = Repo(repo_dir) # 读取一个仓库
  418. self.name = split(repo_dir)[-1]
  419. self.have_clone = True
  420. def customize_command(self, command: str):
  421. return subprocess.Popen(
  422. f"{command} && echo {self.make_stop_key()}",
  423. cwd=self.repo_dir,
  424. **sys_seeting,
  425. )
  426. def make_dir(self, dir_):
  427. if len(dir_) == "":
  428. return dir_
  429. if dir_[0].startswith(os.sep):
  430. inside = ""
  431. else:
  432. inside = os.sep
  433. return self.repo_dir + inside + dir_
  434. @plugin_class_loading(get_path(r'template/gitrepo'))
  435. class CloneGit(GitRepo): # Clone一个git
  436. def init(self, repo_dir, *args, **kwargs):
  437. self.Repo_Dic = repo_dir # 仓库地址
  438. self.url = None
  439. self.name = split(repo_dir)[-1]
  440. self.have_clone = False
  441. def clone(self, url):
  442. if self.have_clone:
  443. super(CloneGit, self).clone(url)
  444. self.have_clone = True
  445. return subprocess.Popen(
  446. f"echo 正在克隆... && {git_path} clone {url} {self.Repo_Dic}",
  447. cwd=split(self.Repo_Dic)[0],
  448. **sys_seeting,
  449. )
  450. def after_clone(self):
  451. self.repo = Repo(self.Repo_Dic)