持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第6天,点击查看活动详情
Git 分支管理:再谈本地数据库与远端数据库
本地分支操作
区域概念
- 工作区:我们平时编辑代码的目录就被称为工作区;
- 暂存区:顾名思义,就是一个暂时防止修改记录的地方。英文为 stage 或 index,所以有些文章中索引也指的是暂存区。我们使用 git add 后文件就会放到暂存区了,一般存放在**.git** 目录下的 index 文件里。
- 版本库:.git 就是版本库,它虽然在我们编辑代码的目录下,但是它不算工作区。它是各种修改的版本信息最终存储的地方
- git add ====> 工作区 -> 暂存区
- git commit ====> 暂存区 -> 版本库
这个流程并不是不可逆的。我们之后学习了 git reset 命令,就知道如何从版本库回退到暂存区,从暂存区回退到工作区了。
以下就是 git reset 回退的方式:
git reset --mixed
:版本库 -> 暂存区
git reset --soft
:暂存区 -> 工作区
git reset --hard
: 版本库 -> 暂存区 -> 工作区
HEAD
HEAD就是当前活跃分支的游标。
就是说我们可以将 HEAD 视为“当前分支”,一般情况下,HEAD 都指向了最顶端的 commit,表示当前版本。当使用 git checkout 切换分支时,HEAD 会指向新分支的尖端。
$ cat .git/HEAD
ref: refs/heads/main
可以看到,HEAD 是对提交对象的引用,它都有个名字,一般是分支名称 (或标签名称,标签是为了更方便地参考提交而给它标上易懂的名称)。一个仓库可以有任意个 head,而且都会选一个 head 作为当前头,这个头部别名为 HEAD,它总是大写。注意:head 和 HEAD 并不相同,“head”(小写)是指存储库中任何一个命名的head; “HEAD”(大写)专门指当前活动的头部。
我们之前学习的 git checkout
就可以用来移动头部,我们前文略有提及的 git reset
也是通过移动头部实现回退的,当你提交时,HEAD 也会移动。也就是说,无论你做了什么,你在 commit 之间移动时,HEAD 也会和你一起移动。
总而言之,HEAD 是一个引用符号,它可以指向我们在提交历史中的任何位置。一般情况下,头都指向了当前分支最顶端。
git stash
有时侯我们在本地开发的一些更改还不想提交,那么就可以暂存起来。
git commit
的内容不会被缓存,git add
的内容会被缓存- stash 的数据结构是个堆栈,先进后出
- 常用于保存当前工作进度
首先要了解为什么在 Git 中隐藏更改如此重要,因为我们在工作中常常会遇到这种情况。你正在开发分支 A 的新特性,开发了 30%,此时有人反馈给你一个分支 B 的 bug,你需要马上解决,但是分支 A 的新特性你还不想提交.
比如说,我们现在创建一条分支 feature/stash
$ git checkout -b feature/stash
Switched to a new branch 'feature/stash'
我们新增一个文本文档 example2.txt
。这时候我们什么都不做,切回去 main 分支
$ git checkout main
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
main 中也出现了 example2.txt
。如果你在 分支 A 开发的特性比较复杂,还可能导致切换分支失败。这时候肯定是不能删除新开发内容的,stash 就派上了用场。
一般情况下,我们都是这样使用 stash 的:
- 使用 stash 保存对分支 A 的更改
- checkout 到分支B
- 修复分支 B 的 bug,提交(并推送)到远程
- checkout 到分支 A
- 取回第一步保存的更改
这些步骤中就涉及到两个 stash 最常用的命令
git stash
: 将工作区中没有提交的内容缓存并移除git stash pop [stash_id]
:恢复具体某一次的版本,如果不指定stash_id,则默认恢复最新的存储进度
此外,还有几个有用的命令
git stash list
:查看之前存储的所有版本列表- 存储的版本名一般是
stash@{id}
, id 从 0 开始
- 存储的版本名一般是
git stash apply
: 将堆栈中的内容应用到当前目录,不同于git stash pop,该命令不会将内容从堆栈中删除,也就说该命令能够将堆栈的内容多次应用到工作目录中,适应于多个分支的情况git stash drop [stash_id]
:删除掉某个id的存储,不指定 id 则默认删除最新的存储git stash clear
:清空所有存储,建议慎用,用不好等于活白干了git stash show
:查看最新的存储和当前工作区的差异
git diff
我们在 example.txt 新增一句话,然后看看效果
$ git diff
diff --git a/example.txt b/example.txt
index f77115b..a6f8ef4 100644
--- a/example.txt
+++ b/example.txt
@@ -6,4 +6,6 @@ I'm used to merging branches with git merge.
I love using git rebase, it works well.
-cccccc
\ No newline at end of file
+cccccc
+
+git diff test
\ No newline at end of file
git diff
:查看尚未存放到暂存区的改动,可以看到具体的改动,比git status
提供的信息更具体
如果我们已经 git add
了,git diff 就查不到文件差异了,因为 git diff 只查看工作区文件差异
$ git add .
$ git diff
我们需要使用 git diff --cached
,来查看暂存区的文件差异。
当然,我们也可以使用 git diff HEAD
来查看工作区和暂存区的所有改动
如果觉得显示的信息过多,你只想知道有什么文件变动,可以使用如下语句
$ git diff --cached --stat
example.txt | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
- 如果只是查看工作区的变动,不用加 --cached
远端数据库
我们之前学习了 pull 和 push 的基本用法,这里在扩展一下,并介绍一个新的命令 fetch
git pull
- 拉去远程的main分支并和本地feature/test合并
git pull origin main:feature/test
git push
我们之前也使用过删除远端分支的语句,就是用 push 执行的
git push origin --delete branchname
如果本地版本与远端版本有差异,但我们想要强制推送上去,可以使用 --force 命令
git push --force origin branchname
- 这个命令会导致远端出现一个 non-fast-forward 的合并,我们应该尽量避免,要使用时一定要确定好不会造成代码丢失或者合并出问题。
将本地所有分支推送到远端数据库,不论是否存在分支
git push --all origin
git fetch
这个命令同样时用于远程获取代码库的。
我们使用 pull 时,就会将远程数据库的东西自动合并。然而我们并不是每次都是想合并代码的,有时仅仅只是需要确认本地数据库的内容。
fetch 会将新内容导入到没有名字的分支中,他有一个FETCH_HEAD,指向了目前已经从远端拉去过来的分支的最新版本。(这些记录会保存到.git/FETCH_HEAD
文件里)
我们有一条遗留在 git_test 项目中一直没使用到的 master 分支。我们现在切换到 master 分支上进行 fetch 操作。一般,我们都是这样使用 fetch 的
-
fetch 远端 main 分支,放到一条临时的分支上
$ git fetch origin main:tmp From https://github.com/FelixWuu/git_test * [new branch] main -> tmp
-
对比下差异
$ git diff tmp diff --git a/README.md b/README.md deleted file mode 100644 index 87bbc63..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# git_test -git test diff --git a/example.txt b/example.txt index a6f8ef4..7f17b10 100644 --- a/example.txt +++ b/example.txt @@ -1,11 +1,3 @@ Hello, Git! -Learning git branch knowledge. - -I'm used to merging branches with git merge. - -I love using git rebase, it works well. - -cccccc -
-
合并,一般使用
git merge branchname
即可,我这里存在一些无关历史,所以加上参数--allow-unrelated-histories
$ git merge tmp --allow-unrelated-histories Auto-merging example.txt CONFLICT (add/add): Merge conflict in example.txt Automatic merge failed; fix conflicts and then commit the result.
存在冲突,解决一下冲突,该保留的保留,该删除的删除
-
收尾工作,比如推送分支到远端或者继续开发,或者删除不想保留的tmp分支
也就是说,fetch 的流程如下
其实 pull 的本质就是 fetch + merge,拉取,然后合并,如果没有冲突,就会顺利合并;如果存在冲突,则会告诉我们有冲突。