目录

  1. 概览

  2. 搭建 Git 测试服务器

  3. 常规命令

  4. Git 暂存区

  5. Git 底层存储

  6. Git 分支

  7. 分支合并

    1. git merge
    2. git rebase
    3. git cherry-pick
  8. 回滚

    1. git revert
    2. git reset
    3. git stash
    4. git checkout
  9. 归档

  10. 参考文档


1. 概览


2. 搭建 Git 测试服务器

下面在 CentOS 7.9 上搭建 Git 测试服务器。

  1. 安装 Git

  2. 创建用户和组

  3. 将客户端的公钥添加到 git 用户的 $HOME/.ssh/authorized_keys 文件中

  4. 初始化 Git 存储库

  5. 克隆 Git 存储库(在客户端执行)


3. 常规命令

3.1. git init [/path/to/your/respository]

创建空存储库或重新初始化已存在的存储库。Git 存储库是带有 objectsrefs/headsrefs/tags 子目录,以及模版文件的 .git 目录。同时也将创建指向当前分支的 HEAD 文件。

重新初始化已存在的存储库是安全的,因为 git init 不会重写已存在的任何东西。重新运行 git init 的主要原因有:

git init 命令还有一个比较常用的选项:--bare。当指定该选项时,将创建一个裸存储库裸存储库没有工作目录(Working Directory),只保存修订版本,可以直接作为服务器存储库。比如:

当未给 git init 命令指定路径时,缺省路径是当前目录(.)。

3.2. git remote

git remote 命令用于管理远程存储库:

3.3. git push

使用本地的引用更新远程存储库的引用,同时将发送相关的对象(在 git push 时,将在本地为远程分支建立远程追踪分支)。git push 的语法是:

其中,repository 既可以是远程仓库的 URL,也可以是远程仓库的名称。

在 Git 中,分支(Branches)标签(Tags)都是对 Commit 对象的引用。可以使用 git push 将分支或标签推送到远程存储库上。

refspec 参数的完整格式是:[+][[<src>]:]<dst>,它表示使用本地的 src 引用 更新远程存储库上的 dst 引用。当省略 src,而不省略冒号时,表示使用本地的空引用更新远程存储库的某个引用,也意味着删除远程存储库上的引用,比如:

当同时省略 src 和冒号(“:”)时,会将名称为 dst 的本地引用推送到远程存储库的同名引用。比如:

向远程存储库推送分支时,可以使用 git push (-u|--set-upstream) <remote-repository> [<local-branch>:]<remote-branch> 将本地分支和远程分支关联起来。以后,在 <local-branch> 分支上,执行不带参数的 git push 时,会自动将 <local-branch> 推送到远程分支 <remote-repository>/<remote-branch>。比如:

在建立关联后,打开 .git/config 将发现类似下面的内容:

3.4. git clone <remote-repository> [<directory>]

将远程存储库克隆到新创建的目录中。如果未指定 <directory> 参数,则使用远程存储库名去掉 .git 后缀作为目录名。git clone 除了将远程存储库完整地镜像下来外,还会做如下事情:


4. Git 暂存区

暂存区也被称为 indexstage,它是介于工作目录(Working Direcotry)和 Git 目录(Git Directory,即 .git 目录)之间的中间状态,许多 Git 命令涉及到暂存区的概念,比如 git commitgit addgit diffgit status 等。

git-staging-area.png

说明:

暂存区保存的是包含文件索引的目录树。该目录树中记录文件名、文件的状态信息(时间戳、文件长度等),以及文件对应的 blob 对象的 ID。暂存区保存在 .git/index 文件中。Git 存储库的结构如下图所示:

git-index.jpg

其中,HEAD 指向当前所在的分支,而分支(和标签)则是指向 commit 对象的引用。


5. Git 底层存储

Git 底层存储的是文件快照,也就是整个文件的内容。而不是一个版本相对于另一版本的差分。

在 Git 中,有四类对象:blob、tree、commit、tag。Git 对象存储在对象库中(位于 .git/objects 目录)。

5.1. blob 对象

blob 对象用于存储文件内容,值得注意的是:blob 只保存文件内容,忽略其它元数据,比如文件名、路径等。blob 对象的 ID 是文件内容的 SHA1 值。可以使用 git hash-object <filename> 命令计算文件内容的 SHA1 值,假设文件内容的 SHA1 值是 95f8cd0ca82c7d33423d8fb9b52bf6995eec3757,那么对应的 blob 对象存储在 .git/objects/95/f8cd0ca82c7d33423d8fb9b52bf6995eec3757 文件中。当执行 git add <filename> 命令时,将生成相应的 blob 对象(也会将 blob 对象的 ID 更新到暂存区的文件索引中)。

可以使用 git cat-file -t <SHA1值>,查看对象的类型。使用 git cat-file -p <SHA1值>,查看对象的内容。比如:

5.2. tree 对象

每次提交时(即执行 git commit 时),将生成一个 tree 对象(当然也生成一个 commit 对象),tree 对象代表当次提交时的目录信息,其内容包含:

从项目根目录开始,每个目录都对应一个 tree 对象,因为目录具有层级关系,所以 tree 对象具有包含关系。比如:

下面是 tree 对象包含 tree 对象的示例:

tree-object.png

5.3. commit 对象

每次提交时(即执行 git commit 时),将生成一个 commit 对象(同时也生成一个 tree 对象)。commit 对象中包含:

下面看一个示例:

5.4. tag 对象

tag 对象其实是为 commit 对象起一个更易读的名字。


6. Git 分支

6.1. 本地分支(local branch)

6.2. 远程分支(remote branch)

6.3. 远程跟踪分支(remote-tracking branch)

6.4. 跟踪分支(tracking branch)


7. 分支合并

7.1. git merge

git merge 用于分支合并,它用于把其它分支commit 的内容合并进当前分支。下面用一个实例进行说明:

1,在 master 分支上创建测试文件 git_merge_test.txt。

2,以 master 分支为起点,创建新分支 test-git-merge-branch。

3,切换到 master 分支,修改文件,再将 test-git-merge-branch 上的修改 merge 进 master 分支。

4,可见存在冲突因为两个分支都修改了同一个文件的同一行

其中,“=======”分隔符上面的部分是 HEAD 分支(即当前所在的分支 master)的内容;下面的部分是 test-git-merge-branch 分支中的内容。接下来,人工解决冲突,比如:

然后使用 git add 将冲突标记为已解决

最后,使用 git commit 进行提交:

可以使用 git log --graph [<filename>],查看文件的分支合并图。

git merge 的过程中,Git 将进行一些特殊处理:找到两个分支的末端和它们的共同祖先,然后进行三方合并,之后对三方合并的结果做快照,创建指向它的 commit 对象。

7.2. git rebase

git push 时,Git 将比较 Commit History,如果 Commit History 不一致,Git 将拒绝提交(比较危险的做法是:使用 git push -f 将远程存储库上的分支强制更新为本地存储库的。但是,这样做会影响其他人的工作!)。

可以使用 git rebase 解决这种情况。接下来用一个示例进行讲述。

1,在测试服务器上创建空的存储库。

2,构造需要 rebase 的现场。

3,切换到 test-rebase-2,先将 main 分支更新到最新状态,然后修改、提交、推送。

4,回到 test-rebase-1,将之前的 commit 推送到远程存储库。

git push 被拒绝。

5,使用 git fetch(不是 git pullgit pull = git fetch + git merge)将远程追踪分支的状态更新到与远程存储库相同。

6,开始 rebase。

可见 rebase 时发生冲突。如果想要停止 git rebase,可以使用 git rebase --abort

7,打开发生冲突的文件 text,人工解决冲突,然后使用 git add 将该冲突标记为 resolved

8,使用 git rebase --continue,继续解决下一个冲突,直到解决所有冲突(注意,无需 git commit)。

9,使用 git log --graph 查看 Commit History。

可见 Commit History 是线性的,非常清晰。

git rebase <another> 的流程是:

7.3. git cherry-pick

git cherry-pick 可以选择一个分支的一个或多个 commit 进行合并。相比之下,git merge 可以称为完全合并。比如,线上的分支是 v1.0,正在开发的分支是 v2.0,如果只想把 v2.0 的某些但不是全部特性合并到线上分支 v1.0,那么应该使用 git cherry-pick,而不是 git merge。因为 git merge 会将 v2.0 上所有的改动都合并到 v1.0,最终导致版本混乱。下面用一个示例,进行说明。

1,构造测试场景。

现在,只想把 v2.0 中对 feature 1 的修改合并进 v1.0。不想将 feature 3 合并到 v1.0。

2,通过 git log 找到要合并的 commit ID。

要合并进 v1.0 的 commit ID 是 659ac7a541d8a2d61d1897a398f9255115f94f6a。

3,使用 git cherry-pick 进行合并。

执行 git cherry-pick 时,可能出现冲突。解决方法与 git merge 一样。


8. 回滚

8.1. git revert

通过生成新提交的方式,撤销某些已经存在的提交。git revert 不会改变 Commit History(这很重要,因为 git push 时,Git 将比较 Commit History,如果 Commit History 不一致,那么将导致 Push 被拒绝)。

下面继续使用讲述 git cherry-pick 时使用的存储库。

1,通过 git log 找到要撤销的 commit。

假设想要撤销的 commit 是 659ac7a541d8a2d61d1897a398f9255115f94f6a。

2,创造在 git revert 时,产生冲突的场景。

3,开始 git revert

git rebase 类似,git revert 也可能产生冲突。当产生冲突时,可以通过 git revert --abort 结束 Revert;也可以人工解决冲突,解决完成后,使用 git add 将冲突标记为“已解决”,然后使用 git revert --continue 继续 Revert,直到所有冲突都被解决,git revert 完成。在整个过程中,无需 git commit

通过 git log,可以看到 git revert 没修改 Commit History,而是产生一个新 commit。

8.2 git reset

分支和标签其实是指向 commit 对象的引用,可以通过 git reset 重置(Reset)当前分支指向的 commit。

git reset 有两个重要的选项:--soft(默认)、--hard

仍然以上面的存储库为例,进行说明。

可以看到 git reset 引起 Commit History 的改变,所以在 git push 时,可能被拒绝。

8.3. git stash

许多 Git 命令要求工作目录是“干净”的,比如 git revertgit merge 等。如果在执行这类命令时,工作目录上已有改动,但是既不想丢弃,也不想提交,那么就可以使用 git stash 将这些未提交的改动“暂存”起来(存在 .git/refs/stash),可以使用 git stash 多次保存工作进度;还可以通过 git stash list 显示进度列表;使用 git stash pop [<stash>] 恢复工作进度;使用 git stash drop [<stash>] 删除工作进度。

仍然以上面的存储库为例,进行说明。

8.4. git checkout

git checkout 的作用是将存储库中特定的修订版本检出到工作目录(切换分支),也可以利用它撤销未提交的修改


9. 归档

对于 Java 项目,部署到线上的是 jar 包或 war 包。但对于 Python、PHP 等项目,部署到线上的可能是源代码。此时,我们不希望代码目录中包含与存储库相关的文件(比如 SVN 中的 .svn 目录、Git中的 .git 目录)。对于 SVN,可以使用 svn export 命令。对与 Git,则可以使用 git archive 命令。

比如:


10. 参考文档