44import argparse
55import os
66import json
7- import traceback
87import threading
9- from urllib . parse import urlparse
8+ from loguru import logger
109from urllib .request import urlopen , Request
1110from urllib .error import HTTPError
1211from colorama import Fore , Style , init
13- from tempfile import NamedTemporaryFile
14- import configparser
1512from config import ConfigHandler
16- from mirrors import test_latency , select_mirror , MIRRORS , convert_url
13+ from mirrors import select_mirror , convert_url
14+ from downloader import download_file
1715from proxy import ProxyHandler
1816
1917init (autoreset = True )
2018
19+ parser = argparse .ArgumentParser (description = 'Git加速工具,支持镜像源和代理' )
20+ parser .add_argument ('command' , type = str , help = 'git命令, 或是fgit命令' )
21+ parser .add_argument ('--use-proxy' , type = str , help = '设置HTTP代理(格式: http://[user:pass@]host:port)' )
22+ parser .add_argument ('--verbose' , action = 'store_true' , help = '显示详细输出' )
23+ args , unknown_args = parser .parse_known_args ()
24+
25+ logger .remove ()
26+ logger .add (sys .stderr , level = 'DEBUG' , colorize = True , format = '{time:HH:mm:ss} | {level} | {message}' ) if args .verbose else logger .add (sys .stderr , level = 'INFO' , colorize = True , format = '{time:HH:mm:ss} | {level} | {message}' )
27+
2128GIT_COMMANDS_NEED_MIRROR = {'clone' , 'pull' , 'push' , 'fetch' }
2229
2330headers = {'User-Agent' : 'Mozilla/5.0' ,
2734
2835def main ():
2936 config = ConfigHandler ()
30- parser = argparse .ArgumentParser (description = 'Git加速工具,支持镜像源和代理' )
31- parser .add_argument ('command' , type = str , help = 'git命令' )
32- parser .add_argument ('--use-proxy' , type = str , help = '设置HTTP代理(格式: http://[user:pass@]host:port)' )
33- parser .add_argument ('--verbose' , action = 'store_true' , help = '显示详细输出' )
34- args , unknown_args = parser .parse_known_args ()
3537
3638 proxy = ProxyHandler (args .use_proxy , config , args .verbose )
3739 env = proxy .setup_proxy_env ()
3840
39- if args .verbose :
40- if proxy .proxy_url :
41- print (Fore .CYAN + f"🔧 运行于代理模式" + Style .RESET_ALL )
42- else :
43- print (Fore .CYAN + f"🔧 运行于镜像模式" + Style .RESET_ALL )
44- print (Fore .CYAN + f"命令参数: { ' ' .join (sys .argv )} " + Style .RESET_ALL )
41+ if proxy .proxy_url :
42+ logger .debug (Fore .CYAN + "🔧 运行于代理模式" + Style .RESET_ALL )
43+ else :
44+ logger .debug (Fore .CYAN + "🔧 运行于镜像模式" + Style .RESET_ALL )
45+ logger .debug (Fore .CYAN + f"命令参数: { ' ' .join (sys .argv )} " + Style .RESET_ALL )
4546
47+
48+ if args .command == 'download-zip' :
49+ handle_download_zip (args , unknown_args , config , env , args .verbose )
50+ return
51+
4652 if args .command not in GIT_COMMANDS_NEED_MIRROR :
4753 subprocess .run (['git' ] + sys .argv [1 :], env = env )
4854 return
@@ -55,6 +61,41 @@ def main():
5561 finally :
5662 proxy .restore_proxy_settings ()
5763
64+ def handle_download_zip (args , unknown_args , config , env , verbose ):
65+ downloader_config = config .get_downloader_config ()
66+ if not downloader_config :
67+ logger .warning (Fore .YELLOW + "🧐 下载配置不存在, 使用默认配置" + Style .RESET_ALL )
68+ chunk_size = downloader_config .get ('chunk_size' , 1024 )
69+ MIN_FILE_SIZE = downloader_config .get ('min_file_size' , 1 )
70+
71+ original_url = unknown_args [0 ]
72+ if '://' not in original_url and '/' in original_url :
73+ if '@' in original_url : # SSH格式
74+ original_url = f"https://{ original_url .split ('@' )[1 ].replace (':' , '/' , 1 )} "
75+ else : # 简写格式
76+ original_url = f"https://github.com/{ original_url } "
77+
78+ original_url = original_url .split ('.git' )[0 ]
79+ repo_name = original_url .split ('/' )[- 1 ].split ('.git' )[0 ]
80+ if os .path .exists (os .path .join (os .getcwd (), repo_name + '.zip' )):
81+ logger .warning (Fore .YELLOW + f"😪 压缩包 { repo_name } .zip 已存在" + Style .RESET_ALL )
82+ return
83+
84+ repo_status = get_repo (original_url )
85+ if repo_status is None :
86+ logger .warning (Fore .YELLOW + "🧐 无法获取到仓库信息, 尝试下载" + Style .RESET_ALL )
87+ elif repo_status is False and not input_with_timeout (Fore .YELLOW + "🧐 仓库可能不存在,5秒内按任意键忽略..." + Style .RESET_ALL , 5 ):
88+ return
89+
90+ mirror_list = select_mirror (config , args .verbose )
91+ for mirror in mirror_list :
92+ new_url = convert_url (original_url , mirror ) + '/archive/refs/heads/main.zip'
93+ logger .info (Fore .GREEN + f"🔄 尝试镜像源 { mirror } [{ mirror_list .index (mirror ) + 1 } /{ len (mirror_list )} ]: { new_url } " + Style .RESET_ALL )
94+ if download_file (new_url , os .path .join (os .getcwd (), repo_name + '.zip' ), chunk_size = chunk_size , MIN_FILE_SIZE = MIN_FILE_SIZE ):
95+ return
96+ logger .error (Fore .RED + "❌ 所有镜像源尝试失败" + Style .RESET_ALL )
97+ return
98+
5899def handle_clone (args , unknown_args , config , env , verbose , proxy ):
59100 original_url = unknown_args [0 ]
60101 if '://' not in original_url and '/' in original_url :
@@ -65,12 +106,12 @@ def handle_clone(args, unknown_args, config, env, verbose, proxy):
65106
66107 repo_name = original_url .split ('/' )[- 1 ].split ('.git' )[0 ]
67108 if os .path .exists (os .path .join (os .getcwd (), repo_name )):
68- print (Fore .YELLOW + f"😪 仓库 { repo_name } 已存在" + Style .RESET_ALL )
109+ logger . warning (Fore .YELLOW + f"😪 仓库 { repo_name } 已存在" + Style .RESET_ALL )
69110 return
70111
71- repo_status = get_repo (original_url , verbose )
112+ repo_status = get_repo (original_url )
72113 if repo_status is None :
73- print (Fore .YELLOW + "🧐 无法获取到仓库信息, 尝试克隆" + Style .RESET_ALL )
114+ logger . warning (Fore .YELLOW + "🧐 无法获取到仓库信息, 尝试克隆" + Style .RESET_ALL )
74115 elif repo_status is False and not input_with_timeout (Fore .YELLOW + "🧐 仓库可能不存在,5秒内按任意键忽略..." + Style .RESET_ALL , 5 ):
75116 return
76117
@@ -80,29 +121,29 @@ def handle_clone(args, unknown_args, config, env, verbose, proxy):
80121 if result .returncode == 0 :
81122 return
82123 else :
83- print (Fore .RED + "❌ 在代理模式下克隆失败, 尝试使用镜像模式..." + Style .RESET_ALL )
124+ logger . error (Fore .RED + "❌ 在代理模式下克隆失败, 尝试使用镜像模式..." + Style .RESET_ALL )
84125 mirror_list = select_mirror (config , verbose )
85126 for mirror in mirror_list :
86127 new_url = convert_url (original_url , mirror )
87- print (Fore .GREEN + f"🔄 尝试镜像源 { mirror } [{ mirror_list .index (mirror ) + 1 } /{ len (mirror_list )} ]: { new_url } " + Style .RESET_ALL )
128+ logger . info (Fore .GREEN + f"🔄 尝试镜像源 { mirror } [{ mirror_list .index (mirror ) + 1 } /{ len (mirror_list )} ]: { new_url } " + Style .RESET_ALL )
88129 cmd = ['git' , 'clone' , new_url ] + unknown_args [1 :]
89130 result = subprocess .run (cmd , env = env , check = False )
90131 if result .returncode == 0 :
91132 repo_path = os .path .join (os .getcwd (), repo_name )
92133 subprocess .run (['git' , '-C' , repo_path , 'remote' , 'set-url' , 'origin' , original_url ], check = True ) # 还原原始 URL
93134 return
94- print (Fore .RED + "❌ 所有镜像源尝试失败" + Style .RESET_ALL )
135+ logger . error (Fore .RED + "❌ 所有镜像源尝试失败" + Style .RESET_ALL )
95136
96137def handle_other_commands (args , unknown_args , config , env , verbose , proxy ):
97138 if not os .path .exists (os .path .join (os .getcwd (), '.git' )):
98- print (Fore .YELLOW + "❌ 当前目录不是有效的 Git 仓库" + Style .RESET_ALL )
139+ logger . warning (Fore .YELLOW + "❌ 当前目录不是有效的 Git 仓库" + Style .RESET_ALL )
99140 return
100141 git_args = [args .command ] + unknown_args
101142 result = subprocess .run (['git' ] + git_args , env = env , check = False )
102143 if result .returncode == 0 :
103144 return
104145 elif proxy .proxy_url :
105- print (Fore .RED + "❌ 在代理模式下运行失败, 尝试使用镜像模式..." + Style .RESET_ALL )
146+ logger . error (Fore .RED + "❌ 在代理模式下运行失败, 尝试使用镜像模式..." + Style .RESET_ALL )
106147
107148 mirror_list = select_mirror (config , verbose )
108149 for mirror in mirror_list :
@@ -113,43 +154,38 @@ def handle_other_commands(args, unknown_args, config, env, verbose, proxy):
113154 return
114155 finally :
115156 restore_git_config ()
116- print (Fore .RED + "❌ 所有镜像源尝试失败" + Style .RESET_ALL )
157+ logger . error (Fore .RED + "❌ 所有镜像源尝试失败" + Style .RESET_ALL )
117158
118- def get_repo (repo : str , verbose : bool = False ) -> bool | None :
159+ def get_repo (repo : str ) -> bool | None :
119160 if repo .endswith ('.git' ):
120161 repo = repo [:- 4 ]
121162 if '://' in repo and '/' in repo :
122163 repo = repo .split ('/' )[- 2 ] + '/' + repo .split ('/' )[- 1 ] # 处理 URL 形式的仓库名
123- if verbose :
124- print (Fore .CYAN + f"🔍 正在获取仓库: { repo } " + Style .RESET_ALL )
164+ logger .debug (Fore .CYAN + f"🔍 正在获取仓库: { repo } " + Style .RESET_ALL )
125165 url = f"https://api.github.com/repos/{ repo } "
126166 req = Request (url , headers = headers )
127167 try :
128168 with urlopen (req ) as response :
129169 if response .status == 200 :
130170 result = json .loads (response .read ().decode ())
131171 # return [result['full_name'], result['id']]
132- print (Fore .GREEN + f"✅ 获取到仓库信息: { result ['full_name' ]} ({ result ['id' ]} )" + Style .RESET_ALL )
172+ logger . info (Fore .GREEN + f"✅ 获取到仓库信息: { result ['full_name' ]} ({ result ['id' ]} )" + Style .RESET_ALL )
133173 return True
134174 elif response .status == 404 :
135- print (Fore .RED + f "❌ 获取仓库信息失败,该仓库可能不存在或未公开" + Style .RESET_ALL )
175+ logger . warning (Fore .RED + "❌ 获取仓库信息失败,该仓库可能不存在或未公开" + Style .RESET_ALL )
136176 return False
137177 else :
138- if verbose :
139- print (Fore .RED + f"❌ 获取仓库信息失败: { response .status } { response .reason } " + Style .RESET_ALL )
178+ logger .debug (Fore .RED + f"❌ 获取仓库信息失败: { response .status } { response .reason } " + Style .RESET_ALL )
140179 return None
141180 except HTTPError as e :
142181 if e .code == 404 :
143- print (Fore .RED + f "❌ 获取仓库信息失败,该仓库可能不存在或未公开" + Style .RESET_ALL )
182+ logger . warning (Fore .RED + "❌ 获取仓库信息失败,该仓库可能不存在或未公开" + Style .RESET_ALL )
144183 return False
145184 else :
146- if verbose :
147- print (Fore .RED + f"❌ 获取仓库信息失败: { e } " + Style .RESET_ALL )
185+ logger .debug (Fore .RED + f"❌ 获取仓库信息失败: { e } " + Style .RESET_ALL )
148186 return None
149187 except Exception as e :
150- if verbose :
151- print (Fore .RED + f"❌ 获取仓库信息失败: { e } " + Style .RESET_ALL )
152- traceback .print_exc ()
188+ logger .debug (Fore .RED + f"❌ 获取仓库信息失败: { e } " + Style .RESET_ALL )
153189 return None
154190
155191def modify_git_config (mirror ):
@@ -159,7 +195,7 @@ def restore_git_config():
159195 subprocess .run (['git' , 'config' , '--local' , '--unset' , 'url.https://github.com/.insteadOf' ])
160196
161197def input_with_timeout (prompt , timeout ):
162- print (prompt )
198+ logger . info (prompt )
163199 result = []
164200 thread = threading .Thread (target = lambda : result .append (sys .stdin .read (1 )))
165201 thread .daemon = True
@@ -169,13 +205,12 @@ def input_with_timeout(prompt, timeout):
169205
170206if __name__ == '__main__' :
171207 try :
172- print (Fore .GREEN + f "fastgit🚀 by NaivG" + Style .RESET_ALL )
208+ print (Fore .GREEN + "fastgit🚀 by NaivG" + Style .RESET_ALL )
173209 main ()
174210 except KeyboardInterrupt :
175- print (Fore .YELLOW + "❗ 操作已取消" + Style .RESET_ALL )
211+ logger . warning (Fore .YELLOW + "❗ 操作已取消" + Style .RESET_ALL )
176212 except Exception as e :
177- print (Fore .RED + f"❌ 错误: { str (e )} " + Style .RESET_ALL )
178- traceback .print_exc ()
213+ logger .exception (Fore .RED + f"❌ 错误: { str (e )} " + Style .RESET_ALL )
179214 sys .exit (1 )
180215 finally :
181216 sys .exit (0 )
0 commit comments