Skip to content

Instantly share code, notes, and snippets.

@Bryan2333
Last active May 5, 2026 14:32
Show Gist options
  • Select an option

  • Save Bryan2333/5ca1e7bb41549b9eac2bc8e0266d3546 to your computer and use it in GitHub Desktop.

Select an option

Save Bryan2333/5ca1e7bb41549b9eac2bc8e0266d3546 to your computer and use it in GitHub Desktop.
msys/git的wrapper脚本 让非msys2环境的程序也可以正常调用git
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