凭证存储

如果你使用的是 SSH 方式连接远端,并且设置了一个没有口令的密钥,这样就可以在不输入用户名和密码的情况下安全地传输数据。 然而,这对 HTTP 协议来说是不可能的 —— 每一个连接都是需要用户名和密码的。 这在使用双重认证的情况下会更麻烦,因为你需要输入一个随机生成并且毫无规律的 token 作为密码。

幸运的是,Git 拥有一个凭证系统来处理这个事情。 下面有一些 Git 的选项:

  • 默认所有都不缓存。 每一次连接都会询问你的用户名和密码。
  • “cache” 模式会将凭证存放在内存中一段时间。 密码永远不会被存储在磁盘中,并且在15分钟后从内存中清除。
  • “store” 模式会将凭证用明文的形式存放在磁盘中,并且永不过期。 这意味着除非你修改了你在 Git 服务器上的密码,否则你永远不需要再次输入你的凭证信息。 这种方式的缺点是你的密码是用明文的方式存放在你的 home 目录下。
  • 如果你使用的是 Mac,Git 还有一种 “osxkeychain” 模式,它会将凭证缓存到你系统用户的钥匙串中。 这种方式将凭证存放在磁盘中,并且永不过期,但是是被加密的,这种加密方式与存放 HTTPS 凭证以及 Safari 的自动填写是相同的。
  • 如果你使用的是 Windows,你可以安装一个叫做 “winstore” 的辅助工具。 这和上面说的 “osxkeychain” 十分类似,但是是使用 Windows Credential Store 来控制敏感信息。 可以在https://gitcredentialstore.codeplex.com 下载。

你可以设置 Git 的配置来选择上述的一种方式

$

--file <path> 参数,可以自定义存放密码的文件路径(默认是~/.git-credentials)。 “cache” 模式有 --timeout <seconds>

$

所有 配置列表中的辅助工具,它们会按自己的方式处理用户名和密码。 如果你在闪存上有一个凭证文件,但又希望在该闪存被拔出的情况下使用内存缓存来保存用户名密码,.gitconfig

[credential]
helper
=
store --file /mnt/thumbdrive/.git-credentials
    helper = cache --timeout 30000

底层实现

这些是如何实现的呢? Git 凭证辅助工具系统的命令是 git credential,这个命令接收一个参数,并通过标准输入获取更多的参数。举一个例子更容易理解。 我们假设已经配置好一个凭证辅助工具,这个辅助工具保存了 mygithost

$
 
git
 
credential
 
fill
 


protocol=https 


host=mygithost



protocol=https 


host=mygithost

username=bob

password=s3cre7

$
 
git
 
credential
 
fill
 


protocol=https

host=unknownhost



Username for 'https://unknownhost': bob

Password for 'https://bob@unknownhost':

protocol=https

host=unknownhost

username=bob

password=s3cre7



这是开始交互的命令。



Git-credential 接下来会等待标准输入。 我们提供我们所知道的信息:协议和主机名。



一个空行代表输入已经完成,凭证系统应该输出它所知道的信息。



接下来由 Git-credential 接管,并且将找到的信息打印到标准输出。



如果没有找到对应的凭证,Git 会询问用户的用户名和密码,我们将这些信息输入到在标准输出的地方(这个例子中是同一个控制台)。

凭证系统实际调用的程序和 Git 本身是分开的;具体是哪一个以及如何调用与 credential.helper

配置值

行为

foo

执行 git-credential-foo

foo -a --opt=bcd

执行 git-credential-foo -a --opt=bcd

/absolute/path/foo -xyz

执行 /absolute/path/foo -xyz

!f() { echo "password=s3cre7"; }; f

!

上面描述的辅助工具可以被称做 git-credential-cachegit-credential-storegetstoreerase对于 store 和 erase 两个行为是不需要返回数据的(Git 也会忽略掉)。 然而对于 get,Git 对辅助工具的返回信息十分感兴趣。

如果辅助工具没有任何有用的信息,它可以直接退出而不需要输出任何东西,但如果它有这些信息,它在提供的信息后面增加它所拥有的信息。 这些输出会被视为一系列的赋值语句;每一个提供的数据都会将 Git 已有的数据替换掉。

这有一个和上面一样的例子,但是跳过了 git-credential 这一步,直接到 git-credential-store:

$
 
git
 
credential-store
 
--file
 
~/git.store
 
store
 


protocol=https

host=mygithost

username=bob

password=s3cre7

$
 
git
 
credential-store
 
--file
 
~/git.store
 
get
 


protocol=https

host=mygithost



username=bob 


password=s3cre7


我们告诉 git-credential-store 去保存凭证:当访问 https://mygithost

现在我们取出这个凭证。 我们提供连接这部分的信息(https://mygithost)以及一个空行。

git-credential-store~/git.store

https://bob:s3cre7@mygithost


仅仅是一系列包含凭证信息URL组成的行。 osxkeychain 和 winstore 辅助工具使用它们后端存储的原生格式,而 cache

自定义凭证缓存

已经知道 git-credential-store 之类的是和 Git 是相互独立的程序,就不难理解 Git 凭证辅助工具可以是 任意get

store

  1.  和 

erasegit-credential-store

  1. 凭证文件的路径一般是固定的,但我们应该允许用户传入一个自定义路径以防万一。

我们再一次使用 Ruby 来编写这个扩展,但只要 Git 能够执行最终的程序,任何语言都是可以的。 这是我们的凭证辅助工具的完整代码:

#!/usr/bin/env ruby



require
 
'optparse'



path
 
=
 
File
.
expand_path
 
'~/.git-credentials'
 


OptionParser
.
new
 
do
 
|
opts
|

    
opts
.
banner
 
=
 
'USAGE: git-credential-read-only [options] <action>'

    
opts
.
on
(
'-f'
,
 
'--file PATH'
,
 
'Specify path for backing store'
)
 
do
 
|
argpath
|

        
path
 
=
 
File
.
expand_path
 
argpath

    
end


end
.
parse!



exit
(
0
)
 
unless
 
ARGV
[
0
]
.
downcase
 
==
 
'get'
 


exit
(
0
)
 
unless
 
File
.
exists?
 
path



known
 
=
 
{
}
 


while
 
line
 
=
 
STDIN
.
gets

    
break
 
if
 
line
.
strip
 
==
 
''

    
k
,
v
 
=
 
line
.
strip
.
split
 
'='
,
 
2

    
known
[
k
]
 
=
 
v


end



File
.
readlines
(
path
)
.
each
 
do
 
|
fileline
|
 

    
prot
,
user
,
pass
,
host
 
=
 
fileline
.
scan
(
/
^(.*?):
\/
\/
(.*?):(.*?)@(.*)$
/
)
.
first

    
if
 
prot
 
==
 
known
[
'protocol'
]
 
and
 
host
 
==
 
known
[
'host'
]
 
then

        
puts
 
"
protocol=
#{
prot
}
"

        
puts
 
"
host=
#{
host
}
"

        
puts
 
"
username=
#{
user
}
"

        
puts
 
"
password=
#{
pass
}
"

        
exit
(
0
)

    
end


end


我们在这里解析命令行参数,允许用户指定输入文件,默认是 ~/.git-credentials.

这个程序只有在接受到 get

这个循环从标准输入读取数据,直到读取到第一个空行。 输入的数据被保存到 known

这个循环读取存储文件中的内容,寻找匹配的行。 如果 known我们把这个辅助工具保存为 git-credential-read-only,放到我们的 PATH

$
=
protocol=https
host=mygithost
protocol=https
host=mygithost
username=bob
password=s3cre7

由于这个的名字是 “git-” 开头,所以我们可以在配置值中使用简便的语法:

$
read

正如你看到的,扩展这个系统是相当简单的,并且可以为你和你的团队解决一些常见问题。

总结

你已经接触了很多能够精确地操控提交和暂存区的高级工具。 当你碰到问题时,你应该可以很容易找出是哪个分支在什么时候由谁引入了它们。 如果你想在项目中使用子项目,你也已经知道如何来满足这些需求。 到此,你应该能毫无压力地在命令行中使用 Git 来完成日常中的大部分事情。