最近作者遇到一个糟心的问题,每次 git push 都会弹 Git Credential Manager (GCM):
每次都要打开浏览器重复验证,就好像永远也记不住上次的登录凭据一样!我一开始还以为是bug,向 Visual Studio 和GCM的GitHub仓库都报告问题,但是都一筹莫展。更奇怪的是,我另一台操作系统、Git和 Visual Studio 完全相同的电脑就没有这个问题!这不禁让我怀疑难道不是bug?
查阅GCM的自述文档我学到了一条可以输出详细日志的调试命令:
$env:GCM_TRACE=1;$env:GIT_TRACE=1;git push
从浩如烟海的日志中我发现了端倪:
21:15:58.903838 run-command.c:928 trace: start_command: git-remote-https origin https://github.com/Ebola-Chan-bot/Low_level_quick_digital_IO.git
21:15:59.082071 exec-cmd.c:266 trace: resolved executable dir: C:/Program Files/Git/mingw64/libexec/git-core
21:15:59.971400 run-command.c:667 trace: run_command: 'git credential-manager get'
21:15:59.973994 run-command.c:928 trace: start_command: 'C:/Program Files/Git/usr/bin/sh.exe' -c 'git credential-manager get' 'git credential-manager get'
21:16:00.280837 exec-cmd.c:266 trace: resolved executable dir: C:/Program Files/Git/mingw64/libexec/git-core
21:16:00.321499 git.c:783 trace: exec: git-credential-manager get
21:16:00.321617 run-command.c:667 trace: run_command: git-credential-manager get
21:16:00.321617 run-command.c:928 trace: start_command: git-credential-manager get
21:16:00.699296 exec-cmd.c:266 trace: resolved executable dir: C:/Program Files/Git/mingw64/libexec/git-core
21:16:00.726475 git.c:479 trace: built-in: git config --null --list
21:16:00.798238 ...\Application.cs:106 trace: [RunInternalAsync] Version: 2.6.0.0
21:16:00.799237 ...\Application.cs:107 trace: [RunInternalAsync] Runtime: .NET Framework 4.8.9290.0
21:16:00.799237 ...\Application.cs:108 trace: [RunInternalAsync] Platform: Windows (x86-64)
21:16:00.799237 ...\Application.cs:109 trace: [RunInternalAsync] OSVersion: 10.0 (build 27764)
21:16:00.800238 ...\Application.cs:110 trace: [RunInternalAsync] AppPath: C:\Program Files\Git\mingw64\bin\git-credential-manager.exe
21:16:00.800238 ...\Application.cs:111 trace: [RunInternalAsync] InstallDir: C:\Program Files\Git\mingw64\bin\
21:16:00.800238 ...\Application.cs:112 trace: [RunInternalAsync] Arguments: get
21:16:00.839248 ...GitCommandBase.cs:32 trace: [ExecuteAsync] Start 'get' command...
21:16:00.853590 ...GitCommandBase.cs:46 trace: [ExecuteAsync] Detecting host provider for input:
21:16:00.856828 ...GitCommandBase.cs:47 trace: [ExecuteAsync] username=Ebola-chan-bot
21:16:00.856828 ...GitCommandBase.cs:47 trace: [ExecuteAsync] wwwauth=Basic realm="GitHub"
21:16:00.861824 ...viderRegistry.cs:149 trace: [GetProviderAsync] Performing auto-detection of host provider.
21:16:00.862833 ...viderRegistry.cs:162 trace: [GetProviderAsync] Auto-detect probe timeout is 2 ms.
21:16:00.865826 ...viderRegistry.cs:170 trace: [GetProviderAsync] Checking against 4 host providers registered with priority 'Normal'.
21:16:00.867825 ...GitCommandBase.cs:49 trace: [ExecuteAsync] Host provider 'GitHub' was selected.
21:16:00.869829 ...bHostProvider.cs:175 trace: [GetCredentialAsync] Looking for existing credential in store with service=https://github.com account=Ebola-chan-bot...
21:16:00.891566 ...bHostProvider.cs:181 trace: [GetCredentialAsync] No existing credentials found.
21:16:00.891566 ...bHostProvider.cs:184 trace: [GetCredentialAsync] Creating new credential...
git检测到仓库的URL中显示的用户名是Ebola-Chan-bot,但GCM收到的用户名却是Ebola-chan-bot,只有一个字母的大小写有区别!显然GCM是大小写敏感的,一个字母之差就找不到之前保存的凭据了。但这是怎么回事,为什么大写会变成小写呢?
查了git和GCM的代码库,一无所获,想想也是,谁会有毛病写代码给用户名保留首字母大写其它字母全小写呢?一遍遍查了网上各种相关问题后,我终于极其偶然地注意到一个之前忽略的细节:用户目录下有一个.gitconfig。打开一看,果然:
[user]
name = 埃博拉酱
[credential "https://github.com"]
username = Ebola-chan-bot
[credential]
helper = manager
罪魁祸首竟在此处!虽然我完全不记得何时在这里写了个错误的用户名(可能是 Visual Studio 或者Code之类的IDE碰到了某个功能自动写的?无从得知了……),但总之删之,问题解决……
原来,git会先从.gitconfig中读取凭据用户名,如果找到了就传递给GCM,然后GCM就从Windows凭据库中查找匹配的凭据,如果找不到就弹窗。由于弹窗以后交给GitHub处理认证,GitHub当然会提供正确的用户名存入Windows凭据库。而每次GCM查找的都是错误的用户名,当然是永远也找不到的。而如果.gitconfig中未设置用户名,GCM就会从Windows凭据库中遍历所有的GitHub相关凭据,这才能够找到上次记住的凭据。
另一个重要的征兆是浏览器中GitHub认证页面顶部的横幅:
可以看到GCM会建议你以一个用户名登录。这里我将.gitconfig中的用户名改为了YouIdiot,于是GCM就建议你登录为YouIdiot用户。