Git Guide

Git 是一个分布式版本控制系统,对比 SVN 这类集中式版本控制系统,分布式版本控制系统可以完全去中心化工作,无需与远程中央服务器通信,在本地即可进行全部版本控制操作,即便是离线。

Git 使用指针管理分支,分支是指针指向某次提交,而 SVN 中的分支则是目录的拷贝,这使得 Git 拥有强大的分支管理能力,分支的切换、合并和删除等操作更迅速和灵活。Git 的提交采用快照机制来存储文件的状态,快照存储相对于 SVN 的差异存储,不需要重新计算差异或补丁,这意味着在提交、分支切换或版本回滚等操作时,效率更高,操作也更为高效和可靠。

基本原理

三大分区

Git 项目一共有三大分区:

1
2
3
* Workspace/Working Tree/Working Directory:工作区
* Index/Stage:暂存区、索引
* Repository/Git Directory:版本库、仓库、资源库、Git 目录
  • 工作区

工作区是当前正在编辑和修改的项目目录,它包含了实际的项目文件。

暂存区是一个中间区域(在 .git/index 文件中),用于暂时存放想要提交到版本库的更改,在 commit 之前,可以选择将工作区中的文件添加到暂存区。

  • 暂存区

暂存区设计目的是提供更灵活的版本控制和工作流程。通过暂存区,开发者可以实现选择修改、组织修改、撤销修改。选择要提交的特定文件或文件某次特定修改,将多个相关的修改组织在一起提交,无需频繁地提交,另外,暂存区内的修改可随时撤销,而不会产生提交历史记录。

  • 版本库

版本库是 Git 的核心部分(在 .git 目录下),它存储了项目的完整历史记录。版本库包含了所有的提交记录、分支、标签等信息。每次提交更改时,Git 会将暂存区的内容保存为一个新的提交,并更新版本库。

Git Flow 流程图

这三大分区也对应着三种状态,分别是 untrack 和 modified、staged、committed。Git 基本的工作流程是,在工作区中修改文件,git add 将想要下次提交的更改选择性地暂存,git commit 提交更改,找到暂存区的文件,将快照永久性存储到 Git 目录。git add 让文件进入 staged,git commit 让文件进入 committed。

Git 对象

Git Object 位于 .git/objects 目录下,一共有三种对象类型:

1
2
3
* blob object: 数据对象
* tree object: 树对象
* commit object: 提交对象

每个 Git Object 都有一个唯一的 object id (由 SHA-1 生成,40 位,分为 blob id、tree id、commit id),这也是 object 在 Git 仓库中的唯一身份证,所有类型的 object 文件名都以 object id 命名。

  • Blob Object

blob object 存储的是文件的具体内容,对文件内容及 mate 信息做 SHA-1 计算,得到一个 blob id,通过这个 id 就能找到文件内容。

当对工作区修改或新增的文件执行 git add 时,文件内容被写入到对象库中的一个新的 blob object 中,而暂存区的目录树被更新,该对象的 id 被记录在暂存区的文件索引中。

以如下 Git 项目为例,Git 项目目录结构、暂存区内容以及 .git/objects/ 目录结构分别如下:

1
2
3
4
5
tree .
.
├── a.txt
└── children
└── b.txt
1
2
3
git ls-files --stage
100644 9d07aa0df55c353e18eea6f1b401946b5dad7bce 0 a.txt
100644 6dd90d24d319b452859920bf74120405fcdaa017 0 children/b.txt
1
2
3
4
5
6
tree .git/objects/
.git/objects/
├── 6d
│ └── d90d24d319b452859920bf74120405fcdaa017
├── 9d
│ └── 07aa0df55c353e18eea6f1b401946b5dad7bce

cat object 文件返回结果是乱码,因为 Git 将信息压缩成二进制文件,需要通过 git cat-file [-t] [-p] 查看,-t 可以查看 object 的类型,-p 可以查看 object 储存的具体内容:

1
cat .git/objects/6d/d90d24d319b452859920bf74120405fcdaa017 # 乱码 xK□□OR0f022□□
1
2
3
4
5
git cat-file -t 9d07 # blob
git cat-file -p 9d07 # 111

git cat-file -t 6dd9 # blob
git cat-file -p 6dd9 # 222
  • Tree Object 和 Commit Object

tree object 存储的内容是一个目录的快照,包括文件/文件夹的权限、类型、内容对应的 SHA-1 值(blob id)、名称(从左往右),commit object 存储的内容是这个 commit 对应的快照是哪一个,以及作者、提交时间、提交信息。

当对暂存区的文件执行 git commit 时,暂存区的内容会在 Git 仓库中生成新的 tree object,再生成新的 commit object 指向这个 tree object,然后再将指针(HEAD、Branch Reference)指向新的 commit object 上:

1
2
3
4
5
6
git cat-file -t 8d02
tree

git cat-file -p 8d02
100644 blob 9d07aa0df55c353e18eea6f1b401946b5dad7bce a.txt
040000 tree 6f70e1ef80653594b824dc4769b6b17b7db6e570 children

注:040000 tree ... 又对其他 object 进行引用,所有的 tree object 就是一棵树结构(目录树)。

1
2
3
4
5
6
7
8
9
git cat-file -t 31c8
commit

git cat-file -p 31c8
tree 8d02b64862ddca88bcf103e1fd52cd82471c66e1
author Tracy <240866271@qq.com> 1693535910 +0800
committer Tracy <240866271@qq.com> 1693535910 +0800

feat: init

注:如果有父提交,还会有 parent xxx 信息,Git 也是通过这个来确定 commit 的分支归属的。

HEAD、分支(分支也是一个引用,指向一个 commit)、TagFETCH_HEAD 都是指针/引用(refs),内容都是一个 commit id,指向对应的 commit。它们被保存在 .git/refs/**/*.git/HEAD.git/FETCH_HEAD 文件中。

HEAD 指针保存在 .git/HEAD 文件中,默认指向当前分支的最新提交,通过 git checkout commitId 可进入“头部/头指针(HEAD)分离状态(detache HEAD state)”,即 HEAD 不再指向分支引用,而是直接指向某个 commit。

1
2
3
git rev-parse HEAD # 查看当前 HEAD 的位置
git symbolic-ref HEAD # 查看当前 HEAD 所指向的分支,如果头部分离了,会提示 ref HEAD is not a symbolic ref
git show-ref [branchName] # 查看分支的引用

FETCH_HEAD 是一个特殊的指针,它指向最近一次使用 git fetch 命令从远程仓库获取的提交。它通常用于在合并远程分支之前查看或操作获取的提交。

安装

Linux 上使用 apt(或 yum) 安装 sudo apt install git,Windows 上可以从 Git 官网下载安装程序,Mac OS 上,Xcode 默认集成了 Git,如果没有安装 Xcode,可通过 Homebrew 单独安装。

另外,Windows 中要想在 CMD 中执行 Git,需要将 Git 添加到环境变量 path 中。

1
2
C:\Program Files\Git\bin
C:\Program Files\Git\mingw64\libexec\git-core
1
git help [-a|--all] [-g|--guide] [-i|--info|-m|--man|-w|--web] [COMMAND|GUIDE] # git help help 查看 help 命令如何使用

注:Windows 版本的 Git 自带 git-gui,包括 gitk,为 Git 提供可视化界面。

配置

Git 共有三级配置,优先级依次是本地配置 > 全局配置 > 系统配置。系统配置在 /etc/gitconfig 文件中(Windows 在 /mingw64/etc/gitconfig。Windows Git Bash 中的根目录是 Git 的安装目录),全局配置在 ~/.gitconfig 文件中,本地配置在本地仓库的 .git/config 文件中。cat ~/.gitconfig 如下:

1
2
3
4
5
6
7
8
9
[user]
email = 240866271@qq.com
name = Tracy
[winUpdater]
recentlySeenVersion = 2.17.0.windows.1
[credential]
helper = manager
[commit]
template = D:/commit-template

注:mingw(Minimalist GNU on Windows) 是一款 Windows 上的 GNU 工具集(含 vim、ssh client…),mingw64 是其 64 位版。Windows 版 Git 自带 mingw。

查看配置信息

1
2
3
4
5
6
git config --system --list # 查看系统配置
git config --global --list # 查看全局配置
git config --local --list # 查看 repository 配置
git config --list # 查看当前配置(配置信息会合并,结果是本地、全局、系统三者的合并)
git config <setting> # 查看特定配置,比如 git config user.name
...

设置配置信息

最常见的自定义配置是用户信息(用户名和邮件地址,commit 时用)、Commit Message Template、Credential Helper。

  • 用户信息

用户信息用于 commit 时标示用户身份,git log 的提交日志中可以查看,如果没有配置,在 MacOS 下会使用操作系统用户名。

1
2
git config --global user.name "Tracy" # 姓名
git config --global user.email "240866271@qq.com" # 邮箱

注:–global 参数是全局参数,也就是这些命令在这台电脑的所有 Git 仓库下都有用。

  • 提交模版

Commit Message Template

1
git config --global commit.template /d/commit-template
  • 换行

由于各操作系统文本文件所使用的换行符不一样,UNIX/Linux/OS X 使用的是 LF,Windows/Dos 使用的 CRLF。Git 默认提供了一个“换行符自动转换”功能。

1
git config --global core.autocrlf xx # true、input(推荐使用)、false

true 表示开启自动转换,迁入时将文件换行风格转换成 Unix 风格,迁出时根据本地系统确定是否转换成 CRLF,input 表示迁入的时候将换行风格转换成 Unix 风格,迁出时不做处理,false 表示迁入迁出都不对换行风格进行处理。

  • 其他

除了上述常用配置,还有其他的配置,比如颜色、别名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 显示颜色
git config --global color.ui true

# 别名
git config --global alias.st status # st 就表示 status,git st 等价于 git status
git config --global alias.co checkout # co 就表示 checkout
git config --global alias.unstage 'reset HEAD' # git unstage test.js 等价于 git reset HEAD test.js,将暂存区的修改撤销(unstage),重新放回工作区
git config --global alias.last 'log -1' # git last 显示最后一次提交信息
git config --global alias.lg
"log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit" # log 别名
git config --global init.defaultBranch main # # git init 默认分支名,从 master 改为 main

# 设置 GUI 编码为 utf-8(可用来处理 git-gui gitk 中文乱码问题)
git config --global gui.encoding utf-8

注,可直接编辑配置文件来设置配置信息。

1
2
3
git config --system -e # 系统配置
git config --global -e # 全局配置
git config -e # 本地配置

.gitignore

.gitignore 配置文件用于忽略文件,不被添加到版本库中,其配置语法如下。

1
2
3
4
5
以斜杠 / 开头表示目录
以星号 * 通配多个字符
以问号 ? 通配单个字符
以方括号 [] 包含单个字符的匹配列表
以叹号 ! 表示不忽略匹配到的文件或目录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 忽略所有 txt 结尾的文件
*.txt
# 忽略 doc 目录下 所有 txt 文件,不包括子目录(doc/subdir/a.txt)
doc/*.txt
# 忽略 doc 目录下所有 txt 的文件,包括子目录
doc/**/*.txt

# README.txt 除外
!README.txt

# 忽略 build/ 目录下的所有文件(不管 build 是根目录还是子目录,都会被忽略)
build/

# 忽略根目录下的 TODOLIST 文件(不包括 subdir/TODOLIST)
/TODOLIST

注:Git 对于 .gitignore 配置文件是按行从上到下进行规则匹配的,如果前面的规则匹配的范围更大,则后面的规则将不会生效。

.gitattributes

  • merge.ours.driver

merge.ours.driver 配置在有冲突时使用 ours(当前分支) 合并策略,常用来,比如根据不同分支做不同持续集成的 .gitlab-ci.yml。

1
2
git config --global merge.ours.driver true
git config --global -l

配置 .gitattributes 文件如下

1
.gitlab-ci.yml merge=ours

注:merge.ours.driver 默认就是 true,不用设置,只需要配置 .gitattributes 文件即可。

版本管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 初始化仓库
git init <folder> # 带 --bare 参数,可创建一个裸库

# 将文件添加到暂存区
git add -p <file>

# 版本提交(将暂存区文件添加到本地仓库中。会生成唯一的 hash 值作为 commit id,即版本号)
git commit -m "commit message" # 如果想要记述的更加详细,不加 -m 参数即可
git commit --amend # 修改最后一次 commit message(如果不是最后一次,需要组合使用 rebase 命令,才能完成)

# 从版本库中删除文件
git rm <file> # 删除工作区文件,并删除暂存区对应文件记录
git rm --cached <file> # 删除暂存区文件记录

# 查看仓库状态
# Git 文件的 4 种状态:untracked(未跟踪)、staged(已暂存)、unmodified/committed(已提交)、modified(已修改),它们之间相互转换流程
git status # 查看所有文件状态
git status [filename] # 查看指定文件状态

# 比较差异
git diff # 比较 workspace 与 index 的差异
git diff HEAD # 比较 workspace 与最新提交记录的差异
git diff --cached/--staged # 比较 index 与 local repositorty 的差异
git diff hash1 hash2 # 比较两个提交记录的差异

# 查看记录
git log # 查看当前分支的提交记录(从 HEAD 到第一次)。可以加参数,比如 --pretty=oneline
git reflog # 查看操作记录(包括被 reset 掉的)

# 版本切换(修改 HEAD 指针到不同的提交位置)
git reset [--hard | --soft | --mixed] [commitId | HEAD] [fileName] # 默认参数 --mixed 和 HEAD
git reset # 取消暂存(to unstage),git reset --mixed HEAD 的简写,即将 HEAD 移动到最新提交(相当于没变),将差异保存到工作区
git reset --hard HEAD^ # 切换到上一个版本
git reset --hard 130f10a # 切换到指定版本
git reset --mixed HEAD^ # 修改最新版本(HEAD 指向了上一个版本,但差异保留在了工作区),--soft 同理

# 版本反做
git revert [-n | --no-commit] [commitId] # 参数 -n|--no-commit 作用是不进入提交编辑界面,需手动提交,撤销和提交分开
git revert HEAD # 反做最新版本

# 丢弃工作区的修改,回到最近一次 git commit 或 git add 时的状态
git checkout -- README.md

# 把误删的文件恢复到最新版本,checkout 其实用版本库里的版本替换工作区的版本
git checkout -- README.md

通过哈希值指定提交记录不太方便(尽管只需要 4 位,但是也需要通过 log 查询出具体 commit id),所以 Git 引入了相对引用,^ 表示向上移动 1 个提交记录,~<num> 表示向上移动多个提交记录。比如,HEAD (即 HEAD~0) 表示当前版本,HEAD^ 是上一个版本,HEAD^^ 是上上一个版本,依此类推,HEAD~100 表示往上第 100 个版本,master^ 表示 master 分支的父提交。

  • reset

git reset 三种模式 --mixed--soft--hard 最主要区别是对目标提交到当前提交的差异以及工作区和暂存区未提交部分处理方式不同。--hard 下工作区和暂存区未提交会被清除,差异也全部清除,--soft 下工作区和暂存区保留,差异会存入暂存区,--mixed 下工作区保留,差异和暂存区都会存入工作区。--hard 参数具破坏性的,它会永久删除工作区和暂存区中未提交的更改(已提交的可通过 git reflog 找回,但未提交的不能),务必小心,所以 --hard 常用来切换版本,--mixed--soft 用来修改提交。

注意:如果本地分支已经被提交到了远程仓库,在使用了 git reset 后本地库 HEAD 指向的版本比远程库的要旧,push 时会报错 Updates were rejected because the tip of your current branch is behind,这时候需要使用 git push -f 强制推上去。

  • revert

git revert 也能做到版本回滚,但是原理与 git reset 完全不一样,git reset 是在各个版本中切换,而 git revert 会生成新的版本。

reset 前:

reset 后(目标版本之后的版本不见了):

rever 前:

revert 后(生成了一个新的版本):

分支管理

分支是用来进行并行作业的。git init 会默认创建 master 主分支。git commit 每次提交,Git 都会自动把它们串成一条时间线,这条时间线就是一个分支。如果只有一个分支,那么也只有一条时间线。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 查看分支(当前分支前面标有 × 号)
git branch # 不带参数查看本地分支,-a 查看所有分支,-r 列出远程跟踪的分支(不是远程分支)

# 创建分支(基于当前分支)
git branch <branch-name> # 只创建不切换

# 切换分支
git checkout dev

# 创建(基于当前分支)和切换分支
git checkout -b dev # 相当于 git branch dev、git checkout dev
git checkout --orphan dev # 创建一个没有任何的提交记录的空分支,但是当前分支的内容都有,可用 git rm -rf . 删除原来代码树下的所有文件

# 恢复文件(放弃某个文件的修改,而不是整个工作目录的所有修改)
git checkout -- <file>

# 重新命名分支
git branch -m <old-branch-name> <new-branch-name>

# 合并分支(合并指定分支到当前分支,比如当前分支是 master)
git merge dev
git merge --no-ff -m "merge with no-ff" dev # --no-ff 参数,表示禁用 Fast forward

# 合并某一次提交
git cherry-pick <commit-hash> # commit-hash 另一个分支中的某次提交

# 删除分支
git branch -d dev # 删除已合并的本地分支(但不能删除当前分支,需要切换,才能删除)。-d -r 参数删除远程追踪分支(只是删除 git branch -r 列表中的追踪分支,并不会删除远程分支)
git branch -D dev # 删除未合并的本地分支

# 看看分支的历史提交记录
git log --graph --pretty=oneline --abbrev-commit

# 强制使用远程分支来覆盖本地分支
git checkout <branch-name>
git fetch origin
git reset --hard origin/<branch-name>

头部分离

git checkout 除了常见的创建/切换分支、切换标签、恢复文件外,还能切换提交,通过 git checkout commitIdgit checkout --detach branchName 进入”头部/头指针(HEAD)分离状态(detached HEAD state)“,该状态下 HEAD 不再指向任何分支,而是一个提交。

detached HEAD 不是分支,只是一种临时状态,常用于查看、修改、调试特定的提交,或者基于该 commit,创建新的分支,当然也可以回滚提交(不推荐,detached HEAD 不是具体分支,不能 push,还需要创建新分支来承载)。在 detached HEAD 上进行提交时,这些提交只是临时的,但如果切换到其他分支,这些提交可能会被丢失,因此,在 detached HEAD 状态下进行的工作完成后,需将其合并到一个具名分支上,以确保提交的持久性。

以以下场景为例,在执行完 git checkout 版本二 commitId 后,HEAD 引用指向了版本二,但是当前分支的引用仍然指向版本三,这个仓库目前处于一个头部分离状态。

执行前:

执行后:

远程仓库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 拉取远程仓库提交,但不合并(git fetch 的目的是 git merge FETCH_HEAD 合并,或者 git checkout FETCH_HEAD 切换到 FETCH_HEAD 查看)
git fetch # 默认下,git fetch 取回所有分支的提交,如果只想取回特定分支的提交,可以指定分支名,比如:git fetch origin master

# 拉取远程仓库提交,且合并(这相当于执行 git fetch 和 git merge FETCH_HEAD 两个命令)
git pull [远程仓库] [远程分支名]:[本地分支名] # origin 是默认的远程仓库名称
git pull origin dev # 如果省略本地分支名,则表示远程分支与当前分支合并

# 向远程仓库库推送提交(git push [远程仓库] [本地分支名]:[远程分支名])(为防止冲突,push 前要先 pull)
git push -u origin master # 当前分支可能与多个主机存在追踪关系(tracking),所以首次要用 -u(--set-upstream)指定一个默认主机(upstream),以后可直接 git push
git push origin master # 如果省略远程分支名,则表示将本地分支推送与之存在"追踪关系"的远程分支,如果该远程分支不存在,则会被新建
git push origin :dev # 如果省略本地分支名,则表示删除指定的远程分支,因为这等同于推送一个空的本地分支到远程分支
git push origin # 将当前分支推送到 origin 主机的对应分支
git push # 如果当前分支只有一个追踪分支,那么连主机名都可以省略
git push --force # --force/-f 强制推送

# 删除远程分支
git push origin --delete dev # 删除 origin 主机的 dev 分支。注:远程的默认分支,不能为当前将要删的目标分支,如果是,需要将默认分支切换到其他分支上,再删

# 从远程库 clone,默认情况只能看到 master 分支,需要在 dev 分支,必须创建远程 origin 的 dev 分支到本地
git checkout -b dev origin/dev
git checkout -b branch-name origin/branch-name
git branch --set-upstream branch-name origin/branch-name # 关联

注:不带任何参数的 git push,默认只推送当前分支,这叫做 simple 方式。此外,还有一种 matching 方式,会推送所有有对应的远程分支的本地分支。Git 2.0 版本之前,默认采用 matching 方法,现在改为默认采用 simple 方式。如果要修改这个设置,可以采用 git config 命令。

1
2
3
4
5
# matching
git config --global push.default matching

# simple
git config --global push.default simple

标签管理

Git 中的标签和分支有点类似,都是引用或者说指针,不过标签的位置是固定的,在给指定提交打好标签以后,它就固定于此位置,而分支的位置是会不断变动的,随着分支的向前推移或者向后回滚,都在不断变化。分支和标签的用处也不一样,分支用于并行作业,而标签用于处理发布。

Git 标签分为两种类型:轻量型(lightweight)和附注型(annotated)。轻量标签是指向特定提交对象的引用,而附注标签则是仓库中的一个独立对象,它有自身的校验和信息,包含着标签的名字,电子邮件地址和日期,以及标签说明,标签本身也允许使用 GNU Privacy Guard (GPG) 来签署或验证。建议使用附注标签,以便保留相关信息。标签名应采用统一的格式,v${MajorVersion}.${MinorVersion}.${FixVersion}-${TypeLabel},其中 TypeLabel (alpha、 beta…) 可选。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 查看标签
git tag # 查看所有 tag
git tag -l v1.0.* # 查看符合模式的 tag
git show v1.0.0 # 查看 tag 信息

# 新建标签
git tag v1.0.0 # 新建轻量 tag
git tag v1.0.0 9fceb02 # 为某个 commit 新建 tag(一般用于后期加注标签)。用 git log --pretty=oneline --abbrev-commit 查看 commit id
git tag -a v1.0.0 -m "message" # 新建带注释的 tag(用 -a 来创建一个带备注的 tag,-m 指定说明文字)

# 推送标签
git push origin v1.0.0 # 推送某个具体 tag
git push origin --tags # 推送所有 tag

# 切换标签
git checkout v1.0.0 # 切换方法跟分支一样

# 删除标签
git tag -d v1.0.0 # 删除本地某个 tag
git push origin :refs/tags/v1.0.0 # 删除远端某个 tag

存储管理

Stash 可用在一些特殊的工作场景中,比如,需要临时修复 Bug,可以把当前工作现场储藏起来,等 Bug 修复后恢复现场后继续工作。在 Git Flow 下,Stash 基本用不到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 保存存储
git stash
git stash save "save message" # 带注释的存储

# 查看存储列表
git stash list

# 查看存储详情
git stash show # 默认查看第一个 stash,即 stash@{0},可以以指定。另外,-p 参数,可以查看到具体的详情

# 恢复存储
git stash pop # 恢复的同时把存储也删了,默认使用第一个 stash,即 stash@{0},也可以指定 git stash pop stash@{$num}
git stash apply # 恢复某个存储,但不会把存储从存储列表中删除,默认使用第一个存储,也可以指定

# 删除存储
git stash drop # 删除某个存储,默认使用第一个 stash,即 stash@{0},也可以指定
git stash clear # 删除所有存储

远程仓库

远程中心化仓库可用来备份存储和共享协作。

登录

  • SSH 登录

使用 SSH 登录 GitHub 流程以下:

1
2
3
4
cd ~/.ssh # 查看是否已经生成 ssh 密钥
ssh-keygen -t rsa -C "any comment can be here" # 生成密钥(注释一般为 Email,但不一定非要 GitHub 开户 Email)
cat id_rsa.pub # 复制 rsa,添加至 GitHub
ssh -T git@github.com # 测试证书登录是否设置成功

ssh-keygen 生成的 rsa 默认命名是 id_rsa (私钥) 和 id_rsa.pub (公钥),如果在键入上述命令回车之后,重新输入了命名,那此时生成的两个文件就是 [命名] 和 [命名].pub

  • Git Credential

除了使用 SSH 实现免密登录,还可通过 Git Credential 保存登录名和密码(默认所有都不缓存,每一次连接都会询问你的用户名和密码),每次自动输入用户名和密码,实现免密登录,Git 使用 credential.help 来存储本地凭证,其所支持的选项如下。

1
2
3
4
cache: cache 模式会将凭证存放在内存中一段时间,密码不会被存储在磁盘中,并且在 15 分钟后从内存中清除
store: store 模式会将凭证用明文的形式存放在磁盘中(默认在 home 目录),永不过期
osxkeychain: Mac 下 store 的加密版,凭证保存在用户的钥匙串中
manager: Windows 下 store 的加密版,凭证保存在 Windows 的凭据管理器中,在 system 级别被设置(credential.helper=manager),优先于 global 和 local

可通过 Git 的帮助文档来查看我们系统支持哪种 helper。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Linux 下
git help -a | grep credential
# 输出结果
credential remote-ftps
credential-cache remote-ftps
credential-cache--daemo remote-ftps
credential-store remote-ftps

# Window 下(CMD 使用 findstr,PowerShell 使用 sls。或者在 C:\Program Files\Git\mingw64\libexec\git-core 目录下执行 (ls).Name | sls credential)
git help -a | sls credential
# 输出结果
git-credential-manager.exe
git-credential-store.exe
git-credential-wincred.exe
git-credential.exe
1
git config --list | grep credential # 查看默认配置

Mac 默认 credential helper 是 osxkeychain,Windows 默认是 manager,而 Linux 默认不存储,需手动设置,以 store 模式为例。

1
git config --global credential.helper store

这样,会生成 ~/.git-credentials 文件,用户信息会以明文保存在里面,https://{userName}:{password}@github.com,比如 https://Tracy-xu:395083226%40gh@github.com

注:要操作一个远程仓库,首先要有 Git 服务器登录权限,然后要有项目权限(项目的所有者,或者 Collaborators – 协作者)。

基本命令

1
2
3
4
5
6
git clone [remote url] [local url] # 克隆远程仓库(在 GitHub 可以使用 HTTPS 和 SSH 协议)
git remote add [shortname] [url] # 添加远程仓库
git remote rm [remote name] # 删除远程仓库
git remote set-url [remote name] [new url] # 修改远程仓库
git remote rename [old remote name] [new remote name] # 修改远程仓库名
git remote # 查看远程库的信息,-v 查看详细信息

搭建 Git 服务器

SSH 作为 Git 所支持通信协议其中之一,如果想通过 SSH 来使用 Git,则需要安装 SSH Server。Linux 一般都自带 SSH,而 Windows 配置起来麻烦,所以这里以 Linux 为例。

当然,在实际使用中不单单只会用到一个简单的非常底层的 Git 服务器,还有其他很多功能会使用到(比如,提供图形化界面的用户管理、SSH 管理、Log、Issue、Pull Request,甚至 Wiki 和持续集成),所以一般会使用 GitHub、GitLab、GitBucket 基于 Git 实现的 Git 仓库托管系统。

  1. 安装 Git
1
sudo apt-get install git                # CentOS 用 yum 带 -y 参数安装(-y 参数不用一步步询问)
  1. 创建一个 Git 用户,用来运行 Git 服务
1
2
3
4
sudo adduser git

# 默认情况下 SSH 不允许空密码用户登录(可在 sshd_config 中设置,参考相关章节),所以还需给 git 这个用户设置一个密码。
sudo passwd git
  1. 设置证书登录

收集所有需要登录的用户的公钥,就是他们自己的 id_rsa.pub 文件,把所有公钥导入到 /home/git/.ssh/authorized_keys 文件里,一行一个(参考 Linux 相关章节。在没有像 GitHub、GitLab 这样的平台来管理 authorized_keys,管理员手动管理 authorized_keys 很麻烦)。

1
2
3
4
5
6
# 验证证书登录是否设置成功(如果让输入密码则没有成功,如果不让输入则设置成功)
ssh -T git@192.168.10.140

# 不让输入密码,回车后,原生的 Git Service 没有任何返回,GitHub 会有返回
ssh -T git@github.com
Hi Tracy-xu! You've successfully authenticated, but GitHub does not provide shell access.

注:~/.ssh 目录在用户目录下(不同用户 ~ 下输入 pwd,结果会是 /root 或者 /home/xxx),修改 ~/.ssh/authorized_keys 只对当前用户生效。另外,如果不设置证书登录,默认会使用密码登录。

  1. 初始化 Git 仓库
1
2
3
4
5
# 选定一个目录作为 Git 仓库,假定是 /srv/sample.git,在 /srv 目录下输入命令
sudo git init --bare sample.git

# 把 owner 改为 git(如果这一步不设置,push 时会报权限不足:insufficient permission for adding an object to repository databa)
sudo chown -R git:git sample.git

git init 不同的是,git init --bare 被用来创建“裸库”,裸库没有 work tree(工作区),只有 .git 目录,记录着版本历史,一般用于公共的远程中央仓库,因为服务器上的 Git 仓库纯粹是为了共享,所以不让用户直接登录到服务器上去改工作区。另外,服务器上的 Git 仓库通常都以 .git 结尾。

  1. 禁用 Shell 登录

出于安全考虑,第二步创建的 git 账户不允许登录 Shell(GitHub 就是如此,ssh git@github.com ),只允许 Git 相关操作,所以要把 Shell 登录改为 git-shell 登录。编辑 /etc/passwd 文件,为 git 用户指定的 git-shell 即可(参考 Linux passwd 相关章节)。

1
2
3
4
git:x:1001:1001::/home/git:/bin/bash

# 改为
git:x:1001:1001::/home/git:/usr/bin/git-shell

还要复制一个名为 git-shell-commands 的目录,要不然 ssh git@192.168.10.140 时会报 fatal:Interactive git shell is not enabled 错误。

1
2
3
4
cp /usr/share/doc/git-1.8.3.1/contrib/git-shell-commands /home/git -R
chown git /home/git/git-shell-commands/ -R
chmod +x /home/git/git-shell-commands/help
chmod +x /home/git/git-shell-commands/list

现在,不管是开机登录,还是 su 切换用户登录,还是 SSH 远程登录,git 用户登录的都将是 git-shell,进来看到的是 git> 而不是 [root@localhost ~]#。另外,不能将 git 账户登录权限改为 /sbin/nologin,禁用登录,要不然客户端在 clone 时会报 fatal: protocol error: bad line length character: This 错误。

  1. 克隆远程仓库
1
git clone git@192.168.10.140:/srv/sample.git

直接 clone 会提示这是一个空仓库。也可以在本地创建一个仓库,然后添加 remote 地址。

1
2
3
4
git init
...
git remote add origin ssh://git@192.168.10.140:/srv/sample.git # clone 时可以省略 ssh:// 协议,设置地址时不可省略
git push --set-upstream origin master # 可以缩写为 git push -u origin master

参考资料

*pro git
*learngitbranching