Git 高阶操作

上一章节我们了解了 Git 的基本流程和常见命令后,这一章节我们来继续介绍 Git 的一些场景和高阶操作,希望能够帮助大家进一步了解 Git 的使用方法。

分支管理规范

通常情况下,每个成员各自一条用户分支(除非项目特大,需要更多分支),还可能需要有 develop/dev(开发),test(测试),release(预发布),master/main(线上发布)分支各一条。除此之外,一些情况下,还可能有一些其他分支。

  • develop/dev开发分支,开发人员每天需要拉取和提交最新代码的分支;
  • test测试分支,开发人员开发完毕并完成代码自测后,用于发布到测试环境的分支;
  • release预发布分支,测试环境测试通过后,将测试分支的代码发布到预发环境的分支;(需要看实际是否需要)
  • master/main线上分支,预发环境测试通过后,运营/测试会将此分支代码发布到线上环境;
  • bug/hotfix修复分支,当线上版本出现 bug 时,会使用该分支进行 bug 修复;
  • feature新功能分支,当线上版本需要添加新功能时,可能会使用该分支,或者使用原有 dev 分支。

这些就是常用的分支,下面来介绍一些常规的项目开发流程。

  • 开发者每天在自己用户分支开发后,拉取并更新开发分支
  • 开发完毕后,开发人员自测后,提交到测试分支,供测试人员测试;
  • 测试完毕后,将代码提交到预发布分支,并进行预发布环境测试;如果不存在该分支,则直接提交到线上分支,并打 tag 标签版本。
  • 当出现 bug 时,使用修复分支,进行修复,随后合并到线上分支

分支管理命令

branch

  • 功能:进行分支切换和管理。
  • 使用场景:当多用户开发时,每个用户需要在自己的分支上进行代码编写,此外代码合并人员需要将多个开发人员的代码在 dev 分支进行合并。
# 分支查看
# 查看本地分支
git branch | git branch -l
# 查看远程分支
git branch -r
# 查看所有分支(本地分支+远程分支)
git branch -a
# 查看所有分支并带上最新的提交信息
git branch -av
# 查看本地分支对应的远程分支
git branch -vv

# 新建分支
# 在别的分支下新建一个分支,新分支会复制当前分支的内容
# 注意:如果当前分支有修改,但是没有提交到仓库,此时修改的内容是不会被复制到新分支的
$ git branch branchname
# 切换分支(切换分支时,本地工作区,仓库都会相应切换到对应分支的内容)
$ git checkout branchname
# 创建一个 aaa 分支,并切换到该分支 (新建分支和切换分支的简写)
$ git checkout -b aaa
# 可以看做是基于 master 分支创建一个 aaa 分支,并切换到该分支
$ git checkout -b aaa master
# 删除本地分支,会阻止删除包含未合并更改的分支
$ git brnach -d branchname
# 强制删除一个本地分支,即使包含未合并更改的分支
$ git branch -D branchname

# 删除远程分支
# 推送一个空分支到远程分支,其实就相当于删除远程分支
$ git push origin :远程分支名
# 或者
$ git push origin --delete 远程分支名

# 修改当前分支名
$ git branch -m branchname

diff

  • 功能:查看文件差异。
  • 使用场景:大部分情况下,每个用户修改的文件是不存在冲突的,往往用于个人代码的修改和管理;但极少数情况可能会存在多用户修改单文件的情况。
# 查看工作区和暂存区单个文件的对比
git diff filename
# 查看工作区和暂存区所有文件的对比
git diff
# 查看工作区和暂存区所有文件的对比,并显示出所有有差异的文件列表
git diff --stat
# 注意:
# 1.你修改了某个文件,但是没有将最新版提交到暂存区,这时候会有对比的内容
# 2.你新建了一个文件,但是没有提交到暂存区,这时候 diff 是没有结果的


# 查看暂存区与上次提交到本地仓库的快照(即最新提交到本地仓库的快照)的对比
git diff --cached/--staged
# 查看工作区与上次提交到本地仓库的快照(即最新提交到本地仓库的快照)的对比
git diff branchname
# 查看工作区与 HEAD 指向(默认当前分支最新的提交)的对比
git diff HEAD


# 查看两个本地分支中某一个文件的对比
git diff branchname..branchname filename
# 查看两个本地分支所有的对比
git diff branchname..branchname
# 查看远程分支和本地分支的对比
git diff origin/branchname..branchname
# 查看远程分支和远程分支的对比
git diff origin/branchname..origin/branchname


# 查看两个 commit 的对比
git diff commit1..commit2

stash

  • 功能:对未提交的修改(不包括新增)进行暂存,方便后期恢复(要注意如果暂存区没有该文件,则无法对其进行堆栈暂存)
  • 使用场景:当产品出现 bug 时,一般情况下直接开辟新的 bug 分支进行修复,随后将其合并至 master 分支即可,但当此时本地在进行功能开发时(并且一半会儿无法完成,或者 bug 情况紧急),代码不能直接提交仓库,就可以使用 stash 命令,将工作区内容 “暂存”,等修复 bug 后,再 stash pop 进行恢复即可。
# 将所有未提交的修改(提交到暂存区)保存至堆栈中
git stash
# 给本次存储加个备注,以防时间久了忘了
git stash save "存储"
# 存储未追踪的文件
git stash -u
# 查看存储记录
git stash list

# 在 Windows 上和 PowerShell 中,需要加双引号
# 恢复工作区,stash 记录并不删除
git stash apply "stash@{index}"
# 恢复,同时删除 stash 记录
git stash pop "stash@{index}"
# 直接删除 stash 记录
git stash drop "stash@{index}"
# 删除所有存储的进度
git stash clear
# 查看当前记录中修改了哪些文件
git stash show "stash@{index}"
# 查看当前记录中修改了哪些文件的内容
git stash show -p "stash@{index}"

merge / rebase

  • 功能:分支、代码的合并。
  • 使用场景:对不同阶段时的分支进行合并。

这里简单介绍一下三种合并方式,后面会对集中合并方式进行详解。

  • merge(默认 --fast-forward):快进式合并,指针移动造成合并假象
  • merge --no-ff:对比不同版本差异,创建新的 commit,并实现真真合并。
  • merge --squash:把所有的改动合并,然后放在本地文件,需要你再次手动执行 git commit 操作
  • rebase:将要合并分支的历史 commit 合并成一条线,保持 commit-tree 的线性
# 默认 fast-forward ,HEAD 指针直接指向被合并的分支
git merge

# 禁止快进式合并
git merge --no-ff

# 压缩式合并
git merge --squash

# rebase 线性合并
git rebase

tag

  • 功能:给仓库提交版本进行标识命名。
  • 使用场景:主要用于发布版本命令的管理。
# 默认在 HEAD 上创建一个标签
git tag v1.0
# 指定一个 commit id 创建一个标签
git tag v0.9 f52c633
# 创建带有说明的标签,用 -a 指定标签名,-m 指定说明文字
git tag -a v0.1 -m "version 0.1 released"

# 查看所有标签
# 注意:标签不是按时间顺序列出,而是按字母排序的。
git tag
# 查看单个标签具体信息
git show <tagname>

# 推送标签
# 推送一个本地标签
git push origin <tagname>
# 推送全部未推送过的本地标签
git push origin --tags

# 删除本地标签
# 因为创建的标签都只存储在本地,不会自动推送到远程。
# 所以,打错的标签可以在本地安全删除。
git tag -d v0.1
# 删除一个远程标签(先删除本地 tag ,然后再删除远程 tag)
git push origin :refs/tags/<tagname>

版本管理命令

checkout

  • 功能:不同版本和分支的切换。
  • 使用场景:主要用于版本的回退、历史代码查看以及测试 bug。当 checkout 某一次的提交时,你可以进行编辑、编译、运行都可以,他并不会影响项目当前状态,当你想要回到项目最新状态,只需 git checkout branchname 即可回到项目状态。这里注意,「在开发的正常阶段,HEAD 一般指向 master 或是其他的本地分支,但当你使用 git checkout <commit id> 切换到指定的某一次提交的时候,HEAD 就不再指向一个分支了——它直接指向一个提交,HEAD 就会处于 detached 状态(游离状态)」。
# 恢复暂存区的指定文件到工作区
git checkout <filename>
# 恢复暂存区的所有文件到工作区
git checkout .

# 回滚到最近的一次提交
# 如果修改某些文件后,没有提交到暂存区,回滚到上一次提交;如果提交了,那就是回滚这次提交(表现为没有变化)
git checkout HEAD
git checkout HEAD -- filename
# 回滚到最近一次提交的上一个版本
git checkout HEAD^

# 切换分支,在这里也可以看做是回到项目「当前」状态的方式
git checkout <当前你正在使用的分支>
# 切换到某个指定的 commit 版本
git checkout <commit_id>
# 切换指定 tag
git checkout <tag>

reset

  • 功能:将当前分支重设到某一版本。
  • 使用场景:通常用于本地修改,常常用于移除提交的快照(因为 reset 回滚到先前版本后,该版本后的新版本在下一次提交时会被删除)。注意,一旦回退到一个旧版本后,此时再用 git log 查看提交记录,会发现之前的新版本记录没有了,此时想要恢复到原来的新版本,可以使用 git reflog 来查看原来的 id,并恢复到那个新版本。
# 从暂存区撤销特定文件,但不改变工作区。它会取消这个文件的暂存,而不覆盖任何更改
git reset <fileName>

# 重置暂存区最近的一次提交,但工作区的文件不变
git reset | git reset HEAD (默认)
# 重置暂存区与工作区,回退到最近一次提交的版本内容
git reset --hard
# 重置暂存区与工作区,回退到最近一次提交的上一个版本
git reset --hard HEAD^

# reset 三种方式 --mixed(默认) --hard --soft
# 将当前分支的指针指向为指定 commit(该提交之后的提交都会被移除),同时重置暂存区,但工作区不变
git reset --mixed <commit> | git reset <commit>
# 将当前分支的指针指向为指定 commit(该提交之后的提交都会被移除),但保持暂存区和工作区不变
git reset --soft <commit>
# 将当前分支的指针指向为指定 commit(该提交之后的提交都会被移除),同时重置暂存区、工作区
git reset --hard <commit>

revert

  • 功能:撤销当前分支的某一版本。
  • 使用场景:通常适用于线上公共仓库,它不是移除提交快照,而是在提交记录上面添加一个新提交,在追踪和修改 bug 时很有帮助,更加安全,可记录。每次提交,你必须假设其他开发者会依赖于它。移除一个其他团队成员在上面继续开发的提交在协作时会引发严重的问题。当他们试着和你的仓库同步时,他们会发现项目历史的一部分突然消失了。一旦你在重设 reset 之后又增加了新的提交,Git 会认为你的本地历史已经和 origin/master 分叉了,同步你的仓库时的合并提交(merge commit)会使你的同事困惑。
# 生成一个撤销最近的一次提交的新提交
git revert HEAD
# 生成一个撤销最近一次提交的上一次提交的新提交
git revert HEAD^
# 生成一个撤销最近一次提交的上两次提交的新提交
git revert HEAD^^
# 生成一个撤销最近一次提交的上n次提交的新提交
git revert HEAD~num
# 生成一个撤销指定提交版本的新提交
git revert <commit_id>
# 生成一个撤销指定提交版本的新提交,执行时不打开默认编辑器,直接使用 Git 自动生成的提交信息
git revert <commit_id> --no-edit

三者区别

  • checkout:本地、远程均可使用,作用于 commit 和暂存区。用于代码的历史查看和 bug 测试等。
  • reset:本地使用,作用于 commit 和暂存区。用于版本和文件回滚,在此之后的修改都会退回到暂存区。
  • revert:远程使用,作用于 commit。用于安全撤销公共提交,主要通过创建新的 commit 来撤销旧的 commit 的修改。