Skip to content

Git 分支操作

Git 各区域之间的流动关系

git 工作流程中,文件在各个区域之间的流动关系:

通过上面几条命令,可以在 git 的 3 个区域之间对文件进行流动操作,也可以通过以下命令,进入命令进入交互模式。

  • git reset -p
  • git checkout -p
  • git add -p

git add

将文件放入暂存区

git add <...files>

  • git add README.md:将README.md文件放入暂存区
  • git add .:将当前目录下的文件放入暂存区
  • git add -A 等同 git add --all:将项目根目录下所有目录和文件放入暂存区

git rm

从暂存区删除文件

  • git rm <...files>: 删除文件,并添加到暂存区,等于 rm <file> + git add . 操作
  • git rm --cached [files]: 只从暂存区删除文件,工作区保留

git mv

重命名文件,并添加到暂存区

  • git mv <filename> <filename_new>:重命名文件,并添加到暂存区,等于 mv <filename> <filename_new> 外加 git add . 操作

git commit

git commit暂存区 内容生成 commit快照 ,并根据文件内容生成hash值,然后提交到历史区

提交到历史区

  • git commit -m "commit message"
  • git commit -am "commit message": 等于 git add -Agit commit -m 结合
  • git commit --amend:放弃当前提交,将当前 commit 对象的快照内容和暂存区内的新内容重新进行一次 commit 提交,并附上新的 commit message 生成新的 commit 对象。

git commit --amend

只能修改当前提交,如果以前某个提交出现了错误,可以先修正错误并提交到暂存区生成新的 commit 提交,然后将当前提交使用 git rebase -i <commit-hash> 修正到指定的提交上,比如 git rebase -i HEAD^3 将当前提交覆写到 HAED 指针之前的第 3 个父节点上,又或者使用 git reset 命令恢复到 HEAD^3 重新来过。

git reset

恢复到暂存区/工作区

  • git reset <commit-hash>:主要是移动指针,并将指针引用的 commit 对象快照恢复到暂存区,所以它主要的功能也是修订暂存区,并不会影响到工作区。
  • git reset <commit-hash> --hard:不仅将 HEAD 指针移动到指定的 commit,还会抛弃被撤销的 commit 提交,连工作区也被充值到对应的 commit。

移动指针

git reset

git reset HEAD

  • git reset 等同于 git reset HEAD
  • HEAD指针 移动到 HEAD指针 , 即不移动。
  • --mixed默认参数选项 ,将 commit 对象快照恢复到暂存区,改动暂存区内容为快照内容,暂存区和历史区指向当前 commit 对象,但是工作区内容保持不变,不会影响用户工作区的工作内容。
  • --soft 参数选项,将 HEAD 指针指向 commit 对象,不改动暂存区和工作区,即暂存区和工作区还是原 HEAD 指针指向的 commit 对象,这会导致暂存区工作区相同,但和历史区不同 --soft 等于 --minxed 之后再 git add 一次。
  • --hard 参数选项,将暂存区和工作区都强制恢复到 HEAD 指向的 commit 快照。

git reset <commit-hash>

git reset HEAD~3

  • 将 HEAD 指针移动到指定 commit 对象,如果 HEAD 指针指向分支,则直接将分支指向 commit 对象
  • --mixed默认参数选项 ,git reset HEAD~3 将 HEAD 指针移动到 b325c 快照, 改动暂存区但不改动工作区 ,所以工作区的内容还是 c10b9,da985,ed489 提交的内容,但是暂存区已经重置到 b325c 快照的内容了,所以 git status 可以看见这 3 个提交新增和修改的内容,需要手动添加到暂存区,或者放弃修改
  • --soft 参数选项,git reset HEAD~3 将 HEAD 指针指向 b325c,不改动工作区 ,所以工作区的内容还是 c10b9,da985,ed489 提交的内容, 既改动暂存区也不改动暂存区 ,暂存区还是保留着 b325c 时的内容,但是 --soft 相比于 --mixed 多了一个 git add 步骤,导致 git add 后的暂存区和使用命令之前时的暂存区是一致的,所以看起来 没有改动暂存区
  • --hard 参数选项,git reset HEAD~3 将 HEAD 指针指向 b325c,改动工作区和暂存区 都恢复到 b325c 快照

git reset -- <...files>

git reset -- README.md

  • git reset -- README.md 等同 git reset HEAD -- README.md 将当前历史区中的 README.md 文件恢复到暂存区,也就是取消暂存区中的 README.md 文件的改动提交。
  • -- 表示命令参数的结束,后续的参数都是文件名称,将 HEAD指针 移动到 HEAD指针 ,即不移动
  • --soft 参数选项
  • --mixed (默认)参数选项,将 HEAD指向的 commit对象 快照恢复到暂存区
  • --hard 参数选项,将 HEAD 指向的 commit对象 快照既恢复到暂存区,也恢复工作区

提示

git reset -- <...files> 这一操作某种程度上等于撤销了在上一个 commit 提交之后,本次准备 commit 提交之前,对于 README.md 文件的 git add 添加到暂存区的操作。

git checkout

提示

git checkout 是 git 早期的命令,功能比较多,但也相对危险,所以 git 在 2.23 版本之后增加了 git switch,git restore 命令

git checkout <commit-hash | branch> -- <...files>暂存/索引区 的文件回撤到 工作区 ,即放弃当前工作区所有修改,回撤到上次 提交(add) 时的状态。

检出文件

  • git checkout -- README.md暂存区README.md 文件回撤到工作区,抛弃当前工作区 README.md 文件修改。
  • git checkout -- .暂存区所有文件 回撤到工作区,抛弃当前工作区 所有文件 的修改。
  • git checkout HEAD -- README.md 这一点和 git reset 有很大不同,从 历史区 中,将 HEAD 指针指向的 commit对象 对应的 README.md 文件回撤到工作区,同时也恢复到暂存区,这一操作会抛弃 工作区暂存区 对于 README.md 文件的修改。
  • git checkout HEAD~n -- README.md 检出 HEAD 指引引用的 commit对象 的第 n 个父节点的 README.md 文件到暂存区和工作区
  • git checkout HEAD~ -- README.md 等同 git checkout HEAD~1 -- README.md ,检出 HEAD 指引引用的 commit对象 的第 1 个父节点的 README.md 文件到暂存区和工作区
  • git checkout <branch> -- README.md 从指定的分支检出 README.md 文件到暂存区和工作区
  • git checkout --orphan <branch> 会创建一个没有任何 commit 记录的分支,直到有新的 commit 提交记录后,这个分支才会真正的存在

git checkout <branch>HEAD指针引用到分支上,并将该分支内容检出到暂存区和工作区,切换分支之前记得使用git stash保存当前文件的修改。

git checkout <commit-hash>HEAD指针 引用到 commit对象 上,这会产生一个游离的 HEAD指针 ,即 detached HEAD。

如果在游离的指针上提交了一个 commit对象 ,当你切换到其它 commit对象分支tag标签 后,这个新提交的 commit对象难以找回

提示

可以使用 git reflog 找到历史提交记录

所以在提交了新的提交之后,记得使用 git checkout -b <branch> <commit-hash> ,或者 git branch <branch> <commit-hash> 新建一个分支指向这个危险的 commit对象

切换分支(检出分支)

sh
# 新建分支,分支指针指向 HEAD,并切换到分支
git checkout -b newbranch
# 等同
git checkout -b newbranch HEAD
# 等同
git branch newbranch HEAD && git switch newbranch
# 等同
git branch newbranch HEAD && git checkout newbranch
# 等同
git switch -c newbranch

git diff

git diff

  • git diff 将工作区内容和暂存区进行 diff 比较
  • git diff <branch> 将工作区内容和分支引用进行比较
  • git diff HEAD 将工作区内容和 HEAD 引用的 commit对象 进行比较
  • git diff <commit-hash> 将工作区内容和 commit对象 进行比较
  • git diff --cachedHEAD 引用的 commit对象 和暂存区进行比较
  • git diff <commit-hash> <commit-hash> 将两个 commit对象 进行比较

分支合并

git merge

Git 合并分支有多种情况,下面将一一介绍。

fast-forward(快速合并)

默认情况下,git 会按照 fast-forward 快速合并模式进行合并,直接将 stable 分支指向最新的提交,这样整个提交记录就只有一棵树。

sh
git merge main

当前 HEAD 指针 指向 ed489,main 分支指向 HEAD,stable 分支指向 a47c3,main 分支和 stable 分支在同一棵树上,这时候 stable 分支合并 main 分支就是 fast-forwad 模式,只需要在合并时将 stable 分支的指针引用更新到 main 分支的指针引用(HEAD 指针)就行了。

squash

提示

这是一张动图,可以将鼠标放上去。

sh
git merge --squash master

当前处在 feature 分支上,--squash 会将 feature 分支上的 commit 提交合并为 1 个新的提交,然后按照 fast-forward 快速合并模式合并到 master 分支上。

non-fast-forward(普通模式)

sh
git merge --no-ff -m "message" master

non-fast-forward 模式并不会因为当前分支和 master 分支在同一棵树上,就直接将 master 分支或者当前分支的指针移动到二者之间最新的提交,而是会强行分个叉,然后重新提交一个 commit 对象,也就是 3-way merge。

3-way merge(三方合并)

sh
git merge other

当前处在 main 分支上行,other 分支和 main 分支并不在一棵树上,所以合并 other 分支时,进行的是 3 方合并,会生成一个新的提交记录 commit 对象,并且将分支指针指向新的提交记录。

git cherry-pick

cherry-pick 会选择一个 commit 对象作为新的提交内容进行一次提交, 本质和 git commit --amend 有些类似,amend 操作会将当前的 commit 对象重新提交一次,并修改提交信息, 生成新的 SHA-1 值,而 cherry-pick 操作将选中的 commit 对象在当前 HEAD 上重新提交一次且生成新的 SHA-1 值。

git rebase

变基也是合并的一种,两个分支的合并可能不是处在一棵树上,所以并不是线性才做, 但是 rebase 会将不同树上的节点合并到同一棵树上去

当前处在 topic 分支,使用变基操作,将 topic 分支挂到 mian 分支所在的树上,和 cherrr-pick 一样会将节点(commit 对象)的内容重新提交生成新的引用,而不是直接移植引用,因为同 SHA1 哈希值的父 commit 对象只有一个。

sh
git rebase main
# 添加到暂存区
git add .
# 会将暂存区再次commit,和 git commit --amend类似
git rebase --continue

如果 rebase 过程中出现错误,可以取消 rebase

sh
git rebase --abort

提示

  • 如果有主分支 main/master 的权限,则可以直接 git merge main 分支,将 main 分支的引用也更新到当前提交,然后推送到远程仓库。
  • 如果没有 main 分支的权限,则需要将变基后的 topic 分支推送到远程仓库,然后进行 pull request 操作。
  • 仓库主人收到 pull request 仓库权限后,可以进行 squash and merge 操作,将 topic 分支的所有提交记录合并到一个 commit 中。
  • 之后就会删除远程 topic 分支,并且本地也会删除 topic 分支,切换到 main 分支上重新拉取最新的代码。

参考视频

git rebase --onto

从 topic 分支,可以直接变基到 main 分支,但并不希望 topic 上所有 commit 提交都被变基,可以使用 --onto 参数。

sh
git rebase --onto main 169a6

这条命令表示从 169a6 的下一个节点开始,到 topic 分支上最新提交,重新变基到 main 分支上,不仅如此,还会将指针也一起挪动

git rebase -i 交互式 rebase

可以指定变基的起点,从起点向前递归父 commit

sh
# 修改当前commit
git rebase -i master
# 修改master开始往前共3个commit
git rebase -i master~3

HEAD^1 和 HEAD~1

HEAD^1

  • ^1 表示当前提交的第一个父提交。通常,一个提交只有一个父提交,(除非是合并提交(Merge Commit),合并提交会有多个父提交)。
  • 你可以使用这种语法来引用当前提交的第一个父提交。例如,如果当前提交是一个合并提交,那么 HEAD^1 就是合并的第一个父提交。

HEAD~1

  • ~1 表示当前提交的父提交。它是 Git 中用来指代父提交的一种简化写法。
  • 这种语法更加通用,因为无论是普通提交还是合并提交,都可以使用这种语法来引用它们的父提交。

所以,HEAD^1HEAD~1 在大多数情况下是等价的,它们都表示当前提交的父提交。但是在涉及到合并提交时,HEAD^1 可以更加明确地指定第一个父提交。

参考