1. Git基础#
1.1 为什么要用Git?什么是Git?#
当你想要使用Git的时候,你一定遇到了版本控制相关的问题。
比如:想要修改A.txt,但是又担心现在的A.txt文件内容仍然需要使用。一些人会选择复制一份A.txt为A_1.txt的,再去修改A_1.txt。长此以往,你可能有了A_99.txt、A_100.txt,并且过了一段时间,你已经不知道哪一文件夹修改了哪一部分。这个时候,点开Git教程,它会给你答案。
再比如:你有一个A.txt需要给你的同事甲协作,让他修改部分内容,同时,你也要修改A.txt。那情况就更麻烦了,如果没有合适的工具,一些人会选择把同事修改完的命名为A_甲_1.txt,再把自己修改的命名为A_1.txt,然后同时打开这两个文件,手动对比区别,最后归纳为A_2.txt。这个时候,你想到之后还要很多次进行一样的操作,太耗费时间,点开Git教程,它会给你答案。
Git是一个分布式版本控制系统。
Git就像一个强大的时间机器,它能记录你项目的所有修改。想象一下,你正在写一篇文章,每修改一个段落,Git都会帮你保存一个快照。这样,你可以随时回到任何一个历史版本,比较不同版本之间的差异,甚至可以把几个人的修改合并到一起。简单来说,Git是一个让你安全、高效地管理项目代码的工具,它能帮你追踪修改、方便协作、并随时回到过去。
下面是版本控制系统的对比和你选择Git的理由:
特性/对比项 | Git | SVN (Subversion) | Mercurial (Hg) | CVS |
---|---|---|---|---|
架构 | 分布式 (Distributed) | 集中式 (Centralized) | 分布式 (Distributed) | 集中式 (Centralized) |
分支管理 | 强大且灵活,轻量级,鼓励频繁使用 | 相对笨重,创建分支成本高 | 较好,与 Git 类似 | 较弱,不鼓励频繁使用 |
性能 | 非常快,尤其在大型项目和大量提交时 | 较慢,性能瓶颈明显 | 相对较快,但不如 Git | 性能较差,尤其在大型项目时 |
离线工作 | 支持,本地有完整仓库 | 不支持,依赖中央服务器 | 支持,本地有完整仓库 | 不支持,依赖中央服务器 |
资源占用 | 占用磁盘空间较多,但操作高效 | 占用磁盘空间较少,但操作相对较慢 | 占用磁盘空间较多,但操作高效 | 占用磁盘空间较少,但操作相对较慢 |
复杂性 | 学习曲线稍陡峭,但功能强大 | 相对简单易学,但功能较少 | 学习曲线较平缓,功能类似 Git | 较为简单,但功能较少 |
流行度 | 目前是行业标准,非常流行 | 逐渐被 Git 取代,但仍有部分应用 | 受欢迎程度不及 Git | 已经过时,不推荐使用 |
选择 Git 的理由 | 个人开发者 | 团队开发者 |
---|---|---|
优势 | - 版本控制的强大能力,方便回溯 | - 高效协作,多人同时开发 |
- 本地操作,离线也能工作 | - 强大的分支管理,灵活应对复杂需求 | |
- 方便实验新想法,随时回滚 | - 代码审查,提高代码质量 | |
- 保护代码,防止误删和丢失 | - 轻松处理合并冲突,避免开发混乱 | |
- 学习现代开发标准,提高竞争力 | - 广泛的社区支持,易于解决问题 | |
- 为协作开发打下基础,方便未来团队合作 | - 支持多种开发模式(如 Gitflow) | |
总结 | 更有效率地管理个人项目,保护代码资产 | 提高团队协作效率,确保项目高质量和快速交付 |
1.2 Git安装#
请自行搜索:“怎么在xxx系统安装Git”。
2. Git基础使用#
以下以Linux环境为例。
让我们开始,新建文件夹 learn_git
,并进入该文件夹:
mkdir learn_git
cd learn_git
以后我们的操作都在 learn_git
文件夹下进行。
2.1 Git基础配置#
在正式使用Git创建并管理项目前,我们还需要对Git进行初始的配置。Git提供了 git config
工具,来帮助我们进行配置。在终端输入:
git config --list --show-origin
我们可以查看所有的配置以及它们所在的文件信息。
终端中输出了配置文件名称、路径以及详细的配置项。Git的配置文件总共有三个,分别存储在不同的地方,并对应不同的权限(优先级:系统>用户>仓库)。
- 存储在安装目录下
etc
路径下的gitconfig
文件,它是系统全局配置文件,它包含系统上每一个用户及他们仓库的通用配置。 - 在当前系统用户下的
.gitconfig
文件,这是当前用户的全局配置文件,它存储了仓库都共享的通用配置选项。 - 存储在仓库目录下的
.git/config
文件,是针对仓库的配置文件,它存储了仓库的配置信息。
如果你安装了 vscode
,你可以设置 git
的默认编辑器为 vscode
:
git config --global core.editor "code --wait”
2.2 Git基础概念#
2.2.1 三个功能区域#
- 工作区(Working Directory)
- 工作区是指包含项目代码的本地目录,是我们平常在编辑器中修改和操作的目录。
- 暂存区(Stage/Index)
- 暂存区是用于存储即将被提交到版本库中的文件快照。我们可以多次预览和审查文件是否正确修改。
- 版本库(Repository)
- 版本库可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。
2.2.2 三种文件状态#
- 已修改(modified):
- 表示文件已被修改但尚未被暂存。我们在工作区修改了文件,并点击了保存,这时候文件状态会变成已修改。也就是说,你已经对文件进行了更改,但还没有使用
git add
命令将其添加到暂存区域。
- 表示文件已被修改但尚未被暂存。我们在工作区修改了文件,并点击了保存,这时候文件状态会变成已修改。也就是说,你已经对文件进行了更改,但还没有使用
- 已暂存(staged):
- 表示对一个已修改文件的当前版本做出了标记,以便在下一次提交时将其纳入版本控制。也就是说,使用
git add
命令将修改的文件添加到暂存区域中。
- 表示对一个已修改文件的当前版本做出了标记,以便在下一次提交时将其纳入版本控制。也就是说,使用
- 已提交(committed):
- 表示数据已经被安全地保存在本地数据库中。也就是说,在你执行了
git commit
命令之后,所有的更改都被保存到了 Git 仓库的历史记录中。
- 表示数据已经被安全地保存在本地数据库中。也就是说,在你执行了
2.2.3 .git文件夹简单介绍#
当我们执行了 git init
初始化仓库之后,Git 会创建一个 .git
文件夹,下面是部分文件信息
HEAD
:指向当前活动分支的指针。config
:版本库的配置文件,包括用户名、邮件地址、编辑器等信息。description
:用于在 GitWeb 等工具中显示有关版本库的描述信息。hooks/
:包含可自定义的 Git 钩子脚本,用于实现特定功能或执行自动化任务。objects/
:包含 Git 对象数据库,其中存储了版本库中所有的文件和提交历史记录。refs/
:包含分支和标签的指针文件,其中保存了每个分支和标签及其所指向的提交 ID。index
:暂存区的索引文件,用于记录下一次提交要包括的文件。
2.3 学习掌握#
2.3.1 创建一个版本库并提交第一个文件#
在已经新建文件夹 learn_git
并进入该文件夹的前提下。
初始化一个Git仓库并设置用户名和邮箱
git init git config user.name "yourname" git config user.email "youremail"
新建一个文件
test.txt
echo "learn_git 2.3.1" > test.txt
查看仓库状态
git status
输出:
位于分支 main 尚无提交 未跟踪的文件: (使用 "git add <文件>..." 以包含要提交的内容) test.txt 提交为空,但是存在尚未跟踪的文件(使用 "git add" 建立跟踪)
将文件添加到暂存区
git add test.txt
再次查看仓库状态
git status
输出:
位于分支 main 尚无提交 要提交的变更: (使用 "git rm --cached <文件>..." 以取消暂存) 新文件: test.txt
提交文件到版本库
git commit -m "add test.txt"
再次查看仓库状态
git status
输出:
位于分支 main 无文件要提交,干净的工作区
2.3.2 修改文件并提交#
修改文件
test.txt
echo "learn_git 2.3.2" > test.txt
查看仓库状态
git status
输出:
位于分支 main 尚未暂存以备提交的变更: (使用 "git add <文件>..." 更新要提交的内容) (使用 "git restore <文件>..." 丢弃工作区的改动) 修改: test.txt 修改尚未加入提交(使用 "git add" 和/或 "git commit -a")
这次我们使用
git commit -am
命令,它会自动将所有已修改的文件添加到暂存区并提交git commit -a -m "modify test.txt to 2.3.2"
再次查看仓库状态
git status
输出:
位于分支 main 无文件要提交,干净的工作区
2.3.3 查看提交历史#
查看提交历史
git log
输出:
commit SHA-1 40-character commit hash (HEAD -> main) Author: yourname <youremail> Date: Day of week Month Date hh:mm:ss Year +UTC modify test.txt to 2.3.2 commit SHA-1 40-character commit hash Author: yourname <youremail> Date: Day of week Month Date hh:mm:ss Year +UTC add test.txt
查看提交历史整洁版
git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
输出:
* 7_hash_2 year-month-day | modify test.txt to 2.3.2 (HEAD -> main) [yourname] * 7_hash_1 year-month-day | add test.txt [yourname]
2.3.4 撤销修改(切换版本)#
查看当前文件内容
cat test.txt
输出:
learn_git 2.3.2
撤销修改,切换到指定版本
git checkout 7_hash_1(为2.3.3查看提交历史整洁版输出结果的哈希值)
输出:
注意:正在切换到 '7_hash_1'。 您正处于分离头指针状态。您可以查看、做试验性的修改及提交,并且您可以在切换 回一个分支时,丢弃在此状态下所做的提交而不对分支造成影响。 如果您想要通过创建分支来保留在此状态下所做的提交,您可以通过在 switch 命令 中添加参数 -c 来实现(现在或稍后)。例如: git switch -c <新分支名> 或者撤销此操作: git switch - 通过将配置变量 advice.detachedHead 设置为 false 来关闭此建议 HEAD 目前位于 7_hash_1 add test.txt
查看当前文件内容
cat test.txt
输出:
learn_git 2.3.1
切换回最新版本
git checkout main
输出:
之前的 HEAD 位置是 7_hash_1 add test.txt 切换到分支 'main'
查看当前文件内容
cat test.txt
输出:
learn_git 2.3.2
2.3.5 撤销修改-本地已保存状态#
修改文件
test.txt
echo "learn_git 2.3.5" > test.txt
查看当前文件内容
cat test.txt
输出:
learn_git 2.3.5
恢复本地修改
git checkout test.txt
查看当前文件内容
cat test.txt
输出:
learn_git 2.3.2
2.3.6 撤销修改-已暂存状态下#
修改文件
test.txt
echo "learn_git 2.3.6" > test.txt
添加到暂存区
git add test.txt
查看仓库状态
git status
输出:
位于分支 main 要提交的变更: (使用 "git restore --staged <文件>..." 以取消暂存) 修改: test.txt
撤销暂存文件
git reset HEAD test.txt
查看仓库状态
git status
输出:
位于分支 main 尚未暂存以备提交的变更: (使用 "git add <文件>..." 更新要提交的内容) (使用 "git restore <文件>..." 丢弃工作区的改动) 修改: test.txt 修改尚未加入提交(使用 "git add" 和/或 "git commit -a")
恢复本地修改
git checkout test.txt
查看仓库状态
git status
输出:
位于分支 main 无文件要提交,干净的工作区
查看当前文件内容
cat test.txt
输出:
learn_git 2.3.2
2.3.7 辨析 git checkout
和 git reset
#
特性 | git checkout | git reset |
---|---|---|
主要用途 | 切换分支或恢复工作树文件 | 重置当前HEAD到指定状态 |
影响范围 | 主要影响工作目录 | 可以影响暂存区和/或工作目录 |
常见用法 | git checkout <branch> git checkout <file> | git reset --soft <commit> git reset --mixed <commit> git reset --hard <commit> |
对提交历史的影响 | 不改变提交历史 | 可以改变提交历史(使用 --hard ) |
文件层面操作 | 可以检出单个文件 | 主要用于提交层面,但也可以重置单个文件 |
分支操作 | 可以创建和切换分支 | 不能直接切换分支 |
数据安全性 | 相对安全,不会丢失数据 | 使用 --hard 可能会丢失未提交的更改 |
撤销操作 | 可以撤销工作目录的修改 | 可以撤销提交、暂存的更改 |
在进行 Git 操作时,请根据您的需要选择正确的命令:
- 如果您想要撤销某些更改并将历史记录回滚到旧的提交,则应使用
git reset
命令。 - 如果您只是想查看其他提交的状态或切换到不同的分支,则应使用
git checkout
命令。
2.3.8 撤销提交#
修改文件
test.txt
echo "learn_git 2.3.8" > test.txt
添加所有修改的文件到暂存区并提交
git commit -am "modify test.txt to 2.3.8"
查看提交历史
git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
输出:
* 7_hash_3 year-month-day | modify test.txt to 2.3.8 (HEAD -> main) [yourname] * 7_hash_2 year-month-day | modify test.txt to 2.3.2 [yourname] * 7_hash_1 year-month-day | add test.txt [yourname]
撤销最后一次提交,并创建一个新的提交来还原更改。
git revert HEAD --no-edit
查看当前文件内容
cat test.txt
输出:
learn_git 2.3.2
查看提交历史
git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
输出:
* 7_hash_4 year-month-day | Revert "modify test.txt to 2.3.8" (HEAD -> main) [yourname] * 7_hash_3 year-month-day | modify test.txt to 2.3.8 [yourname] * 7_hash_2 year-month-day | modify test.txt to 2.3.2 [yourname] * 7_hash_1 year-month-day | add test.txt [yourname]
恢复上一次撤销提交
git reset --hard HEAD^
查看提交历史
git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
输出:
* 7_hash_3 year-month-day | modify test.txt to 2.3.8 (HEAD -> main) [yourname] * 7_hash_2 year-month-day | modify test.txt to 2.3.2 [yourname] * 7_hash_1 year-month-day | add test.txt [yourname]
查看当前文件内容
cat test.txt
输出:
learn_git 2.3.8
撤销到指定提交
git revert 7_hash_2 --no-edit
输出:
自动合并 test.txt 冲突(内容):合并冲突于 test.txt 错误:不能还原 6396343... modify test.txt to 2.3.2 提示: 冲突解决完毕后,用 'git add <路径>' 或 'git rm <路径>' 提示: 命令标记修正后的文件 提示: Disable this message with "git config advice.mergeConflict false"
这时发现有冲突,我们需要手动解决冲突,编辑文件
test.txt
,修改为:learn_git 2.3.2
即采用传入的更改
标记解决并完成revert
git add test.txt git revert --continue
查看提交历史
git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
输出:
* 7_hash_5 year-month-day | Revert "modify test.txt to 2.3.2" (HEAD -> main) [yourname] * 7_hash_3 year-month-day | modify test.txt to 2.3.8 [yourname] * 7_hash_2 year-month-day | modify test.txt to 2.3.2 [yourname] * 7_hash_1 year-month-day | add test.txt [yourname]
2.3.9 删除提交#
修改文件并提交
echo "learn_git 2.3.9" > test.txt git commit -am "modify test.txt to 2.3.9"
查看提交历史
git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
输出:
* 7_hash_6 year-month-day | modify test.txt to 2.3.9 (HEAD -> main) [yourname] * 7_hash_5 year-month-day | Revert "modify test.txt to 2.3.2" [yourname] * 7_hash_3 year-month-day | modify test.txt to 2.3.8 [yourname] * 7_hash_2 year-month-day | modify test.txt to 2.3.2 [yourname] * 7_hash_1 year-month-day | add test.txt [yourname]
删除提交(标记删除
7_hash_3
后面所有提交)git reset --hard 7_hash_3
查看提交历史
git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
输出:
* 7_hash_3 year-month-day | modify test.txt to 2.3.8 (HEAD -> main) [yourname] * 7_hash_2 year-month-day | modify test.txt to 2.3.2 [yourname] * 7_hash_1 year-month-day | add test.txt [yourname]
2.3.10 修改提交内容#
修改文件并提交
echo "learn_git 2.3.10" > test.txt git commit -am "modify test.txt to 2.3.10"
查看提交历史
git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
输出:
* 7_hash_7 year-month-day | modify test.txt to 2.3.10 (HEAD -> main) [yourname] * 7_hash_3 year-month-day | modify test.txt to 2.3.8 [yourname] * 7_hash_2 year-month-day | modify test.txt to 2.3.2 [yourname] * 7_hash_1 year-month-day | add test.txt [yourname]
再次修改文件并覆盖上一次提交
echo "learn_git 2.3.10_1" > test.txt git commit --amend -m "modify test.txt to 2.3.10_1"
查看提交历史
git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
输出:
* 7_hash_8 year-month-day | modify test.txt to 2.3.10_1 (HEAD -> main) [yourname] * 7_hash_3 year-month-day | modify test.txt to 2.3.8 [yourname] * 7_hash_2 year-month-day | modify test.txt to 2.3.2 [yourname] * 7_hash_1 year-month-day | add test.txt [yourname]
2.3.11 移动文件#
移动文件
test.txt
到test/test.txt
mkdir test git mv test.txt test/test.txt
查看仓库状态
git status
输出:
位于分支 main 要提交的变更: (使用 "git restore --staged <文件>..." 以取消暂存) 重命名: test.txt -> test/test.txt 尚未暂存以备提交的变更: (使用 "git add <文件>..." 更新要提交的内容) (使用 "git restore <文件>..." 丢弃工作区的改动) 修改: test/test.txt
提交文件移动
git commit -m "move test.txt to test/test.txt"
查看提交历史
git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
输出:
* 7_hash_9 year-month-day | move test.txt to test/test.txt (HEAD -> main) [yourname] * 7_hash_8 year-month-day | modify test.txt to 2.3.10_1 [yourname] * 7_hash_3 year-month-day | modify test.txt to 2.3.8 [yourname] * 7_hash_2 year-month-day | modify test.txt to 2.3.2 [yourname] * 7_hash_1 year-month-day | add test.txt [yourname]
恢复到上一次提交
git reset --hard 7_hash_8
查看提交历史
git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
输出:
* 7_hash_8 year-month-day | modify test.txt to 2.3.10_1 (HEAD -> main) [yourname] * 7_hash_3 year-month-day | modify test.txt to 2.3.8 [yourname] * 7_hash_2 year-month-day | modify test.txt to 2.3.2 [yourname] * 7_hash_1 year-month-day | add test.txt [yourname]
移动文件
test.txt
到test_1/test.txt
mkdir test_1 mv test.txt test_1
查看仓库状态
git status
输出:
位于分支 main 尚未暂存以备提交的变更: (使用 "git add/rm <文件>..." 更新要提交的内容) (使用 "git restore <文件>..." 丢弃工作区的改动) 删除: test.txt 未跟踪的文件: (使用 "git add <文件>..." 以包含要提交的内容) test_1/ 修改尚未加入提交(使用 "git add" 和/或 "git commit -a")
暂存文件
git add test_1
删除文件
git rm test.txt
查看仓库状态
git status
输出:
位于分支 main 要提交的变更: (使用 "git restore --staged <文件>..." 以取消暂存) 重命名: test.txt -> test_1/test.txt
暂存文件并提交
git commit -am "move test.txt to test_1/test.txt"
查看提交历史
git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
输出:
* 7_hash_10 year-month-day | move test.txt to test_1/test.txt (HEAD -> main) [yourname] * 7_hash_8 year-month-day | modify test.txt to 2.3.10_1 [yourname] * 7_hash_3 year-month-day | modify test.txt to 2.3.8 [yourname] * 7_hash_2 year-month-day | modify test.txt to 2.3.2 [yourname] * 7_hash_1 year-month-day | add test.txt [yourname]
重命名文件
test_1/test.txt
为test_1/test_1.txt
git mv test_1/test.txt test_1/test_1.txt
查看仓库状态
git status
输出:
位于分支 main 要提交的变更: (使用 "git restore --staged <文件>..." 以取消暂存) 重命名: test_1/test.txt -> test_1/test_1.txt
暂存文件并提交
git commit -am "rename test_1/test.txt to test_1/test_1.txt"
查看提交历史
git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
输出:
* 7_hash_11 year-month-day | rename test_1/test.txt to test_1/test_1.txt (HEAD -> main) [yourname] * 7_hash_10 year-month-day | move test.txt to test_1/test.txt [yourname] * 7_hash_8 year-month-day | modify test.txt to 2.3.10_1 [yourname] * 7_hash_3 year-month-day | modify test.txt to 2.3.8 [yourname] * 7_hash_2 year-month-day | modify test.txt to 2.3.2 [yourname] * 7_hash_1 year-month-day | add test.txt [yourname]
2.3.12 忽略文件(.gitignore
)#
创建一个
.gitignore
文件,并设置忽略*.log
文件echo "*.log" > .gitignore
暂存文件并提交
git add .gitignore git commit -m "add .gitignore"
查看提交历史
git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
输出:
* 7_hash_12 year-month-day | add .gitignore (HEAD -> main) [yourname] * 7_hash_11 year-month-day | rename test_1/test.txt to test_1/test_1.txt [yourname] * 7_hash_10 year-month-day | move test.txt to test_1/test.txt [yourname] * 7_hash_8 year-month-day | modify test.txt to 2.3.10_1 [yourname] * 7_hash_3 year-month-day | modify test.txt to 2.3.8 [yourname] * 7_hash_2 year-month-day | modify test.txt to 2.3.2 [yourname] * 7_hash_1 year-month-day | add test.txt [yourname]
创建测试文件
test.log
echo "test.log 2.3.12" > test.log
查看仓库状态
git status
输出:
位于分支 main 无文件要提交,干净的工作区
2.3.13 标签操作#
把最新提交打上标签
git tag v1.0
查看提交历史
git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
输出:
* 7_hash_12 year-month-day | add .gitignore (HEAD -> main, tag: v1.0) [yourname] * 7_hash_11 year-month-day | rename test_1/test.txt to test_1/test_1.txt [yourname] * 7_hash_10 year-month-day | move test.txt to test_1/test.txt [yourname] * 7_hash_8 year-month-day | modify test.txt to 2.3.10_1 [yourname] * 7_hash_3 year-month-day | modify test.txt to 2.3.8 [yourname] * 7_hash_2 year-month-day | modify test.txt to 2.3.2 [yourname] * 7_hash_1 year-month-day | add test.txt [yourname]
删除标签
git tag -d v1.0
查看提交历史
git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
输出:
* 7_hash_12 year-month-day | add .gitignore (HEAD -> main) [yourname] * 7_hash_11 year-month-day | rename test_1/test.txt to test_1/test_1.txt [yourname] * 7_hash_10 year-month-day | move test.txt to test_1/test.txt [yourname] * 7_hash_8 year-month-day | modify test.txt to 2.3.10_1 [yourname] * 7_hash_3 year-month-day | modify test.txt to 2.3.8 [yourname] * 7_hash_2 year-month-day | modify test.txt to 2.3.2 [yourname] * 7_hash_1 year-month-day | add test.txt [yourname]
3. Git进阶使用#
3.1 Git对象存储机制#
Git 使用一种称为对象存储的机制来管理和处理所有文件和目录内容,以及它们之间的关系。
Git 对象存储机制包括以下几个方面:
- Git 对象:Git 中的所有数据都被视为对象。一个对象可以是一个文件的内容(
Blob
)、目录结构(Tree
)、提交记录(Commit
)或标签(Tag
)等。 - SHA-1 哈希:每个 Git 对象都具有与其内容相关联的唯一标识符,该标识符由其内容的 SHA-1 哈希值生成。这使得 Git 可以轻松地检测文件内容的更改。
- Git 数据库:Git 会将所有对象存储在一个数据库中,该数据库位于
.git/objects
目录下。该目录包含一个名为info
的子目录和一个名为pack
的子目录,其中info
子目录包含有关对象的元数据,而pack
子目录包含经过压缩的对象。
3.1.1 Git对象#
Git 数据库包含了许多不同类型的对象,这些对象相互关联形成一个有向无环图(DAG)。在 Git 中,每个对象都由 SHA-1 哈希值唯一标识,并按照其哈希值存储在 .git
目录下的 objects
目录中。
Git 对象包括四种主要类型:blob
、tree
、commit
和 tag
。
Blob
:Blob 对象代表一个文件的内容。每个 blob 都由一个唯一的 SHA-1 哈希值标识,并存储在 .git/objects 目录下。Blob 对象是 Git 数据库的基本单位,它们包含文件的原始内容而不包含任何元数据。Tree
:Tree 对象代表一个目录或文件夹,在 Git 中被称为“树”。它可以包含多个 blob 或其他 tree 对象,以及相关元数据,如文件名和权限等信息。Tree 对象也由一个 SHA-1 哈希值唯一标识,并存储在 .git/objects 目录下的 objects/trees 子目录中。Commit
:Commit 对象代表一个提交记录,包含了提交的作者、提交者、提交时间、提交信息等元数据,以及指向一个 tree 对象的指针。每个 commit 对象都由一个唯一的 SHA-1 哈希值标识,并存储在 .git/objects 目录下的 objects/commits 子目录中。Tag
:Tag 对象代表一个标签,用于标记某个特定的 commit 对象。Tag 对象包含了标签的名称、标签的类型、标签的作者、标签的创建时间等元数据,以及指向一个 commit 对象的指针。Tag 对象也由一个唯一的 SHA-1 哈希值标识,并存储在 .git/objects 目录下的 objects/tags 子目录中。
ps:有关 git cat-file
会帮助你深入了解Git内部机制,这里不多做介绍,需要了解的可以自行查阅。
3.2 分支管理#
3.2.1 创建分支#
在 Git 中,分支是指针,它指向某个提交记录。在 Git 存储库中,默认情况下有一个名为 main
的主分支,该分支指向最新的提交记录。
使用分支可以轻松地将代码库分成不同的版本,并在这些版本之间进行切换和合并操作。例如,如果你想尝试新功能或修复错误,可以创建一个新分支,在该分支上进行更改,而不会影响主分支。一旦更改准备好,就可以将其合并回主分支中。这使得协作变得更加容易,因为团队成员可以在自己的分支上开发新功能,而不必担心与其他人的更改冲突。
创建一个新分支
feature
git checkout -b feature
查看所有分支
git branch
输出:
* feature main
查看仓库状态
git status
输出:
位于分支 feature 无文件要提交,干净的工作区
新建文件
feature.txt
并提交echo "feature branch" > feature.txt git add feature.txt git commit -m "add feature.txt"
查看提交历史
git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
输出:
* 7_hash_13 year-month-day | add feature.txt (HEAD -> feature) [yourname] * 7_hash_12 year-month-day | add .gitignore (main) [yourname] * 7_hash_11 year-month-day | rename test_1/test.txt to test_1/test_1.txt [yourname] * 7_hash_10 year-month-day | move test.txt to test_1/test.txt [yourname] * 7_hash_8 year-month-day | modify test.txt to 2.3.10_1 [yourname] * 7_hash_3 year-month-day | modify test.txt to 2.3.8 [yourname] * 7_hash_2 year-month-day | modify test.txt to 2.3.2 [yourname] * 7_hash_1 year-month-day | add test.txt [yourname]
3.2.2 合并分支#
在 Git 中,合并分支是一种将两个不同的分支中的代码更改合并到一个分支中的操作。这允许团队成员在不同的分支中开发功能和修复错误,并最终将它们合并到主分支或其他稳定分支中。
切换到主分支
git checkout main
合并
feature
分支到main
分支git merge feature
查看提交历史
git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
输出:
* 7_hash_13 year-month-day | add feature.txt (HEAD -> main, feature) [yourname] * 7_hash_12 year-month-day | add .gitignore [yourname] * 7_hash_11 year-month-day | rename test_1/test.txt to test_1/test_1.txt [yourname] * 7_hash_10 year-month-day | move test.txt to test_1/test.txt [yourname] * 7_hash_8 year-month-day | modify test.txt to 2.3.10_1 [yourname] * 7_hash_3 year-month-day | modify test.txt to 2.3.8 [yourname] * 7_hash_2 year-month-day | modify test.txt to 2.3.2 [yourname] * 7_hash_1 year-month-day | add test.txt [yourname]
3.2.3 合并冲突#
在 Git 中,当尝试将两个不同的分支合并时,可能会出现合并冲突。这通常发生在两个分支上都更改了同一文件的同一部分时。
修改文件
feature.txt
并提交echo "changed by main 3.2.3" > feature.txt git commit -am "modify feature.txt by main"
切换到
feature
分支git checkout feature
修改文件
feature.txt
并提交echo "changed by feature 3.2.3" > feature.txt git commit -am "modify feature.txt by feature"
合并
feature
分支到main
分支git checkout main git merge feature
输出:
自动合并 feature.txt 冲突(内容):合并冲突于 feature.txt 自动合并失败,修正冲突然后提交修正的结果。
手动合并冲突
cat feature.txt
输出:
<<<<<<< HEAD changed by main 3.2.3 ======= changed by feature 3.2.3 >>>>>> feature
由于我之前设置了
vscode
作为默认编辑器,所以这里会自动打开vscode
编辑器,手动解决冲突,点击保留双方更改
:changed by main 3.2.3 changed by feature 3.2.3
提交合并结果
git add feature.txt git commit -m "merge feature branch and resolve conflict"
查看提交历史
git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
输出:
* 7_hash_15 year-month-day | merge feature branch and resolve conflict (HEAD -> main) [yourname] |\ | * 7_hash_14 year-month-day | modify feature.txt by feature (feature) [yourname] * | 7_hash_13 year-month-day | modify feature.txt by main [yourname] |/ * 7_hash_12 year-month-day | add .gitignore [yourname] * 7_hash_11 year-month-day | rename test_1/test.txt to test_1/test_1.txt [yourname] * 7_hash_10 year-month-day | move test.txt to test_1/test.txt [yourname] * 7_hash_8 year-month-day | modify test.txt to 2.3.10_1 [yourname] * 7_hash_3 year-month-day | modify test.txt to 2.3.8 [yourname] * 7_hash_2 year-month-day | modify test.txt to 2.3.2 [yourname] * 7_hash_1 year-month-day | add test.txt [yourname]
3.2.4 变基和合并#
Rebasing
(变基) 和 Merging
(合并) 是 Git 中常用的两种集成分支的方法。这两种方法都可以将一个分支的更改合并到另一个分支中,但它们的内部实现不同,因此其使用场景和结果也略有不同。
Rebasing
Rebasing
是一种将分支更改应用于目标分支的方法,它会将分支的每个提交都转移到目标分支的顶部,并在每个提交之间将目标分支的更改应用于分支更改。这意味着,当你使用Rebasing
方法时,最终的提交历史记录是一个线性的历史记录,其中所有更改都按照时间顺序排列。- 主要优点:
- 提交历史记录更加干净和有序。
- 可以快速解决由于分支变化而导致的代码冲突。
- 主要缺点:
- 可能需要耗费大量时间和精力来处理冲突。
- 对于多人协作开发而言,可能需要进行协调才能确保不出现问题。
- 主要优点:
Merging
Merging
是一种将分支更改合并到目标分支的方法。它会创建一个新的合并提交,该提交包含了目标分支和要合并的分支的全部更改。这意味着,当你使用Merging
方法时,最终的提交历史记录将包含合并提交以及两个分支的更改历史记录。- 主要优点:
- 容易理解和使用。
- 不需要手动处理冲突。
- 主要缺点:
- 提交历史记录可能会变得杂乱无序,难以阅读和理解。
- 如果分支更改频繁,可能会导致大量的冲突。
- 主要优点:
综上所述,Rebasing
和 Merging
都是有效的集成分支的方法,它们适用于不同的场景。
- 如果你需要保持提交历史记录的整洁和有序,或者需要快速解决由于分支变化而导致的代码冲突,则可以选择使用
Rebasing
方法。 - 如果你需要简单地将一个分支的更改合并到另一个分支中,并且不关心最终的提交历史记录,则可以选择使用
Merging
方法。
3.2.5 使用变基合并分支#
重置到合并前的最后一个提交
git reset --hard 7_hash_13
查看提交历史
git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
输出:
* 7_hash_13 year-month-day | modify feature.txt by main (HEAD -> main) [yourname] * 7_hash_12 year-month-day | add .gitignore [yourname] * 7_hash_11 year-month-day | rename test_1/test.txt to test_1/test_1.txt [yourname] * 7_hash_10 year-month-day | move test.txt to test_1/test.txt [yourname] * 7_hash_8 year-month-day | modify test.txt to 2.3.10_1 [yourname] * 7_hash_3 year-month-day | modify test.txt to 2.3.8 [yourname] * 7_hash_2 year-month-day | modify test.txt to 2.3.2 [yourname] * 7_hash_1 year-month-day | add test.txt [yourname]
变基
feature
分支到main
分支git checkout feature git rebase main
手动合并冲突
cat feature.txt
输出合并后结果:
changed by main 3.2.3 changed by feature 3.2.3
提交合并结果
git add feature.txt git commit -m "rebase feature branch and resolve conflict" git rebase --continue
查看提交历史
git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
输出:
* 7_hash_16 year-month-day | rebase feature branch and resolve conflict (HEAD -> feature) [yourname] * 7_hash_13 year-month-day | modify feature.txt by main [yourname] * 7_hash_12 year-month-day | add .gitignore [yourname] * 7_hash_11 year-month-day | rename test_1/test.txt to test_1/test_1.txt [yourname] * 7_hash_10 year-month-day | move test.txt to test_1/test.txt [yourname] * 7_hash_8 year-month-day | modify test.txt to 2.3.10_1 [yourname] * 7_hash_3 year-month-day | modify test.txt to 2.3.8 [yourname] * 7_hash_2 year-month-day | modify test.txt to 2.3.2 [yourname] * 7_hash_1 year-month-day | add test.txt [yourname]
切换到
main
分支git checkout main
合并
feature
分支到main
分支git merge feature
查看提交历史
git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
输出:
* 7_hash_17 year-month-day | rebase feature branch and resolve conflict (HEAD -> main, feature) [yourname] * 7_hash_13 year-month-day | modify feature.txt by main [yourname] * 7_hash_12 year-month-day | add .gitignore [yourname] * 7_hash_11 year-month-day | rename test_1/test.txt to test_1/test_1.txt [yourname] * 7_hash_10 year-month-day | move test.txt to test_1/test.txt [yourname] * 7_hash_8 year-month-day | modify test.txt to 2.3.10_1 [yourname] * 7_hash_3 year-month-day | modify test.txt to 2.3.8 [yourname] * 7_hash_2 year-month-day | modify test.txt to 2.3.2 [yourname] * 7_hash_1 year-month-day | add test.txt [yourname]
3.3 多存储库#
到目前为止,我们一直在使用单个 git 存储库。然而,git 擅长处理多个存储库。这些额外的存储库可以本地存储,也可以通过网络连接访问。
本节我们将创建一个名为 learn_git_cloned
的新存储库。展示如何从一个存储库移动更改到另一个存储库,并且当两个存储库之间发生冲突时如何处理。
目前,我们将使用本地存储库(即存储在本地硬盘上的存储库)进行工作,但是,在本节中学到的大多数内容都适用于多个存储库,无论它们是在本地还是通过网络远程存储。
3.3.1 克隆存储库#
在
learn_git
同级目录下克隆learn_git
存储库git clone learn_git learn_git_cloned
进入
learn_git_cloned
目录,并查看提交历史cd learn_git_cloned git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
输出:
* 7_hash_17 year-month-day | rebase feature branch and resolve conflict (HEAD -> main, feature) [yourname] * 7_hash_13 year-month-day | modify feature.txt by main [yourname] * 7_hash_12 year-month-day | add .gitignore [yourname] * 7_hash_11 year-month-day | rename test_1/test.txt to test_1/test_1.txt [yourname] * 7_hash_10 year-month-day | move test.txt to test_1/test.txt [yourname] * 7_hash_8 year-month-day | modify test.txt to 2.3.10_1 [yourname] * 7_hash_3 year-month-day | modify test.txt to 2.3.8 [yourname] * 7_hash_2 year-month-day | modify test.txt to 2.3.2 [yourname] * 7_hash_1 year-month-day | add test.txt [yourname]
查看远程存储库
git remote -v
输出:
origin learn_git (fetch) origin learn_git (push)
查看详细信息
git remote show origin
输出:
* 远程 origin 获取地址:learn_git 推送地址:learn_git HEAD 分支:main 远程分支: feature 已跟踪 main 已跟踪 为 'git pull' 配置的本地分支: main 与远程 main 合并 为 'git push' 配置的本地引用: main 推送至 main (最新)
查看分支
git branch -a
输出:
* main remotes/origin/HEAD -> origin/main remotes/origin/feature remotes/origin/main
3.3.2 从原始存储库拉取更改#
在
learn_git
存储库中创建文件test.txt
并提交cd learn_git echo "add test.txt by learn_git 3.3.2" > test.txt git add test.txt git commit -m "add test.txt by learn_git 3.3.2"
查看提交历史
git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
输出:
* 7_hash_18 year-month-day | add test.txt by learn_git 3.3.2 (HEAD -> main) [yourname] * 7_hash_17 year-month-day | rebase feature branch and resolve conflict (feature) [yourname] * 7_hash_13 year-month-day | modify feature.txt by main [yourname] * 7_hash_12 year-month-day | add .gitignore [yourname] * 7_hash_11 year-month-day | rename test_1/test.txt to test_1/test_1.txt [yourname] * 7_hash_10 year-month-day | move test.txt to test_1/test.txt [yourname] * 7_hash_8 year-month-day | modify test.txt to 2.3.10_1 [yourname] * 7_hash_3 year-month-day | modify test.txt to 2.3.8 [yourname] * 7_hash_2 year-month-day | modify test.txt to 2.3.2 [yourname] * 7_hash_1 year-month-day | add test.txt [yourname]
进入
learn_git_cloned
目录,拉取learn_git
存储库的更改cd learn_git_cloned git pull origin main
在
learn_git
存储库中修改文件test.txt
并提交cd learn_git echo "modify test.txt by learn_git 3.3.2" > test.txt git add test.txt git commit -m "modify test.txt by learn_git 3.3.2"
进入
learn_git_cloned
目录,获取learn_git
存储库的更改cd learn_git_cloned git fetch
查看提交历史
git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short --all
输出:
* 7_hash_19 year-month-day | modify test.txt by learn_git 3.3.2 (origin/main, origin/HEAD) [yourname] * 7_hash_18 year-month-day | add test.txt by learn_git 3.3.2 (HEAD -> main) [yourname] * 7_hash_17 year-month-day | rebase feature branch and resolve conflict (origin/feature) [yourname] * 7_hash_13 year-month-day | modify feature.txt by main [yourname] * 7_hash_12 year-month-day | add .gitignore [yourname] * 7_hash_11 year-month-day | rename test_1/test.txt to test_1/test_1.txt [yourname] * 7_hash_10 year-month-day | move test.txt to test_1/test.txt [yourname] * 7_hash_8 year-month-day | modify test.txt to 2.3.10_1 [yourname] * 7_hash_3 year-month-day | modify test.txt to 2.3.8 [yourname] * 7_hash_2 year-month-day | modify test.txt to 2.3.2 [yourname] * 7_hash_1 year-month-day | add test.txt [yourname]
合并已经获取的更改
git merge origin/main
查看提交历史
git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
输出:
* 7_hash_19 year-month-day | modify test.txt by learn_git 3.3.2 (HEAD -> main, origin/main, origin/HEAD) [yourname] * 7_hash_18 year-month-day | add test.txt by learn_git 3.3.2 [yourname] * 7_hash_17 year-month-day | rebase feature branch and resolve conflict (origin/feature) [yourname] * 7_hash_13 year-month-day | modify feature.txt by main [yourname] * 7_hash_12 year-month-day | add .gitignore [yourname] * 7_hash_11 year-month-day | rename test_1/test.txt to test_1/test_1.txt [yourname] * 7_hash_10 year-month-day | move test.txt to test_1/test.txt [yourname] * 7_hash_8 year-month-day | modify test.txt to 2.3.10_1 [yourname] * 7_hash_3 year-month-day | modify test.txt to 2.3.8 [yourname] * 7_hash_2 year-month-day | modify test.txt to 2.3.2 [yourname] * 7_hash_1 year-month-day | add test.txt [yourname]
3.3.3 向原始存储库推送更改#
设置用户名和邮箱,并且设置为裸仓库
git config user.name "yourname" git config user.email "youremail" git config --bool core.bare true
在
learn_git_cloned
存储库中修改文件test.txt
并提交echo "modify test.txt by learn_git 3.3.3" > test.txt git add cloned.txt git commit -m "modify test.txt by learn_git 3.3.3"
查看提交历史
git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
输出:
* 7_hash_20 year-month-day | modify test.txt by learn_git 3.3.3 (HEAD -> main) [yourname] * 7_hash_19 year-month-day | modify test.txt by learn_git 3.3.2 (origin/main, origin/HEAD) [yourname] * 7_hash_18 year-month-day | add test.txt by learn_git 3.3.2 [yourname] * 7_hash_17 year-month-day | rebase feature branch and resolve conflict (origin/feature) [yourname] * 7_hash_13 year-month-day | modify feature.txt by main [yourname] * 7_hash_12 year-month-day | add .gitignore [yourname] * 7_hash_11 year-month-day | rename test_1/test.txt to test_1/test_1.txt [yourname] * 7_hash_10 year-month-day | move test.txt to test_1/test.txt [yourname] * 7_hash_8 year-month-day | modify test.txt to 2.3.10_1 [yourname] * 7_hash_3 year-month-day | modify test.txt to 2.3.8 [yourname] * 7_hash_2 year-month-day | modify test.txt to 2.3.2 [yourname] * 7_hash_1 year-month-day | add test.txt [yourname]
设置
learn_git
存储库接收更改cd learn_git git config receive.denyCurrentBranch updateInstead
推送更改到
learn_git
存储库cd learn_git_cloned git push origin main
查看提交历史
git log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
输出:
* 7_hash_20 year-month-day | modify test.txt by learn_git 3.3.3 (HEAD -> main, origin/main, origin/HEAD) [yourname] * 7_hash_19 year-month-day | modify test.txt by learn_git 3.3.2 [yourname] * 7_hash_18 year-month-day | add test.txt by learn_git 3.3.2 [yourname] * 7_hash_17 year-month-day | rebase feature branch and resolve conflict (origin/feature) [yourname] * 7_hash_13 year-month-day | modify feature.txt by main [yourname] * 7_hash_12 year-month-day | add .gitignore [yourname] * 7_hash_11 year-month-day | rename test_1/test.txt to test_1/test_1.txt [yourname] * 7_hash_10 year-month-day | move test.txt to test_1/test.txt [yourname] * 7_hash_8 year-month-day | modify test.txt to 2.3.10_1 [yourname] * 7_hash_3 year-month-day | modify test.txt to 2.3.8 [yourname] * 7_hash_2 year-month-day | modify test.txt to 2.3.2 [yourname] * 7_hash_1 year-month-day | add test.txt [yourname]