Last active
May 5, 2026 14:32
-
-
Save Bryan2333/5ca1e7bb41549b9eac2bc8e0266d3546 to your computer and use it in GitHub Desktop.
msys/git的wrapper脚本 让非msys2环境的程序也可以正常调用git
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import ( | |
| "fmt" | |
| "os" | |
| "os/exec" | |
| "os/signal" | |
| "regexp" | |
| "strings" | |
| "syscall" | |
| ) | |
| // 【配置区】请确保以下两个路径与你电脑上的 MSYS2 安装路径一致 | |
| const ( | |
| RealGitPath = `C:\msys64\usr\bin\git.exe` | |
| MsysBinPath = `C:\msys64\usr\bin` | |
| ) | |
| // SSH Agent socket 路径(MSYS2 风格),与 ~/.bashrc 中的配置保持一致 | |
| const SSHAgentSockName = `.ssh/agent.sock` | |
| // 匹配 Windows 绝对路径,例如 C:\ 或 d:/ | |
| var winPathRegex = regexp.MustCompile(`^(?i)([a-z]):[\\/](.*)$`) | |
| // 匹配带有等号的 Windows 路径参数,例如 --work-tree=C:\path | |
| var eqWinPathRegex = regexp.MustCompile(`^([^=]+=)(?i)([a-z]):[\\/](.*)$`) | |
| // translateArg 将 Windows 风格的路径转换为 MSYS2 (Unix) 风格 | |
| func translateArg(arg string) string { | |
| // 1. 处理形如 --work-tree=C:\foo\bar | |
| if m := eqWinPathRegex.FindStringSubmatch(arg); m != nil { | |
| prefix := m[1] | |
| drive := strings.ToLower(m[2]) | |
| rest := strings.ReplaceAll(m[3], "\\", "/") | |
| return fmt.Sprintf("%s/%s/%s", prefix, drive, rest) | |
| } | |
| // 2. 处理形如 C:\foo\bar | |
| if m := winPathRegex.FindStringSubmatch(arg); m != nil { | |
| drive := strings.ToLower(m[1]) | |
| rest := strings.ReplaceAll(m[2], "\\", "/") | |
| return fmt.Sprintf("/%s/%s", drive, rest) | |
| } | |
| // 3. 相对路径中的反斜杠替换 | |
| if strings.Contains(arg, "\\") { | |
| arg = strings.ReplaceAll(arg, "\\", "/") | |
| } | |
| return arg | |
| } | |
| // translateEnvPath 将环境变量中的 Windows 路径值转为 MSYS2 路径 | |
| func translateEnvPath(value string) string { | |
| if m := winPathRegex.FindStringSubmatch(value); m != nil { | |
| drive := strings.ToLower(m[1]) | |
| rest := strings.ReplaceAll(m[2], "\\", "/") | |
| return fmt.Sprintf("/%s/%s", drive, rest) | |
| } | |
| return value | |
| } | |
| // ensureSSHAgentBridge 确保 socat 桥接进程正在运行 | |
| func ensureSSHAgentBridge(sockPath string) { | |
| if _, err := os.Stat(sockPath); err == nil { | |
| return | |
| } | |
| os.Remove(sockPath) | |
| bridge := exec.Command( | |
| MsysBinPath+`\socat.exe`, | |
| "UNIX-LISTEN:"+sockPath+",fork", | |
| "EXEC:npiperelay.exe -ei -s //./pipe/openssh-ssh-agent,pipes", | |
| ) | |
| bridge.Start() | |
| } | |
| func main() { | |
| // 防御性检查:防止无限循环调用 | |
| selfPath, err := os.Executable() | |
| if err == nil && strings.EqualFold(selfPath, RealGitPath) { | |
| fmt.Fprintln(os.Stderr, "Fatal Error: Wrapper is pointing to itself causing infinite loop.") | |
| os.Exit(1) | |
| } | |
| // 转换所有参数 | |
| var args []string | |
| for _, arg := range os.Args[1:] { | |
| args = append(args, translateArg(arg)) | |
| } | |
| cmd := exec.Command(RealGitPath, args...) | |
| // 绑定标准输入/输出/错误流 | |
| cmd.Stdin = os.Stdin | |
| cmd.Stdout = os.Stdout | |
| cmd.Stderr = os.Stderr | |
| // 设置环境变量 | |
| env := os.Environ() | |
| var newEnv []string | |
| hasMsystem := false | |
| // 用于记录 HOME 和 USERPROFILE 的状态 | |
| hasHome := false | |
| userProfileTranslated := "" | |
| // 需要翻译路径值的关键环境变量 | |
| pathEnvKeys := map[string]bool{ | |
| "HOME": true, | |
| "USERPROFILE": true, | |
| "TEMP": true, | |
| "TMPDIR": true, | |
| "TMP": true, | |
| "GIT_SSH": true, | |
| "SSH_ASKPASS": true, | |
| "GIT_ASKPASS": true, | |
| } | |
| for _, e := range env { | |
| upperE := strings.ToUpper(e) | |
| // 补全 PATH | |
| if strings.HasPrefix(upperE, "PATH=") { | |
| currentPath := e[5:] | |
| newEnv = append(newEnv, "PATH="+MsysBinPath+string(os.PathListSeparator)+currentPath) | |
| continue | |
| } | |
| if strings.HasPrefix(upperE, "MSYSTEM=") { | |
| hasMsystem = true | |
| newEnv = append(newEnv, e) | |
| continue | |
| } | |
| // 检查原始环境中是否自带了 HOME | |
| if strings.HasPrefix(upperE, "HOME=") { | |
| hasHome = true | |
| } | |
| // SSH_AUTH_SOCK 由 wrapper 统一管理,跳过原始值 | |
| if strings.HasPrefix(upperE, "SSH_AUTH_SOCK=") { | |
| continue | |
| } | |
| translated := false | |
| for key := range pathEnvKeys { | |
| prefix := key + "=" | |
| if strings.HasPrefix(upperE, strings.ToUpper(prefix)) { | |
| value := e[len(prefix):] | |
| transVal := translateEnvPath(value) | |
| newEnv = append(newEnv, prefix+transVal) | |
| if strings.ToUpper(key) == "USERPROFILE" { | |
| userProfileTranslated = transVal | |
| } | |
| translated = true | |
| break | |
| } | |
| } | |
| if !translated { | |
| newEnv = append(newEnv, e) | |
| } | |
| } | |
| // 指定 MSYSTEM 让 MSYS2 程序以正确模式运行 | |
| if !hasMsystem { | |
| newEnv = append(newEnv, "MSYSTEM=MSYS") | |
| } | |
| // 如果环境变量中没有HOME,就使用%USERPROFILE%作为HOME目录 | |
| if !hasHome && userProfileTranslated != "" { | |
| newEnv = append(newEnv, "HOME="+userProfileTranslated) | |
| } | |
| // 注入 SSH_AUTH_SOCK,确保非 MSYS2 环境(如 VSCode)也能使用 ssh-agent 桥接 | |
| msysHome := "" | |
| for _, e := range newEnv { | |
| if strings.HasPrefix(strings.ToUpper(e), "HOME=") { | |
| msysHome = e[5:] | |
| break | |
| } | |
| } | |
| if msysHome != "" { | |
| sockPath := msysHome + "/" + SSHAgentSockName | |
| ensureSSHAgentBridge(sockPath) | |
| newEnv = append(newEnv, "SSH_AUTH_SOCK="+sockPath) | |
| } | |
| cmd.Env = newEnv | |
| // 启动命令并挂载信号拦截 | |
| err = cmd.Start() | |
| if err != nil { | |
| fmt.Fprintf(os.Stderr, "Git Wrapper failed to start: %v\n", err) | |
| os.Exit(1) | |
| } | |
| sigChan := make(chan os.Signal, 1) | |
| signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) | |
| go func() { | |
| <-sigChan | |
| if cmd.Process != nil { | |
| cmd.Process.Kill() | |
| } | |
| }() | |
| err = cmd.Wait() | |
| // 退出状态码传递 | |
| if err != nil { | |
| if exitError, ok := err.(*exec.ExitError); ok { | |
| if status, ok := exitError.Sys().(syscall.WaitStatus); ok { | |
| os.Exit(status.ExitStatus()) | |
| } | |
| os.Exit(exitError.ExitCode()) | |
| } | |
| fmt.Fprintf(os.Stderr, "Git Wrapper Error: %v\n", err) | |
| os.Exit(1) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment