学会Git 04:Git 分支管理:再谈本地数据库与远端数据库

181 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第6天,点击查看活动详情

Git 分支管理:再谈本地数据库与远端数据库

本地分支操作

区域概念

  • 工作区:我们平时编辑代码的目录就被称为工作区;
  • 暂存区:顾名思义,就是一个暂时防止修改记录的地方。英文为 stage 或 index,所以有些文章中索引也指的是暂存区。我们使用 git add 后文件就会放到暂存区了,一般存放在**.git** 目录下的 index 文件里。
  • 版本库:.git 就是版本库,它虽然在我们编辑代码的目录下,但是它不算工作区。它是各种修改的版本信息最终存储的地方
  1. git add ====> 工作区 -> 暂存区
  2. 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 的:

  1. 使用 stash 保存对分支 A 的更改
  2. checkout 到分支B
  3. 修复分支 B 的 bug,提交(并推送)到远程
  4. checkout 到分支 A
  5. 取回第一步保存的更改

这些步骤中就涉及到两个 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 的

  1. fetch 远端 main 分支,放到一条临时的分支上

    $ git fetch origin main:tmp
    From https://github.com/FelixWuu/git_test
     * [new branch]      main       -> tmp
    
  2. 对比下差异

    $ 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
    -
    
  3. 合并,一般使用 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.
    

    存在冲突,解决一下冲突,该保留的保留,该删除的删除

  4. 收尾工作,比如推送分支到远端或者继续开发,或者删除不想保留的tmp分支

也就是说,fetch 的流程如下

x3FQ4e.png

其实 pull 的本质就是 fetch + merge,拉取,然后合并,如果没有冲突,就会顺利合并;如果存在冲突,则会告诉我们有冲突。