前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版

Git操作清单 从撤销到rebase的日常命令梳理

首页2019-08-31 19:50:12VCS
Git版本控制

Git 的命令我一直是「用到再查」的状态,直到有一次把改了一整天的代码 git reset --hard 掉,才认真坐下来把这些命令的边界搞清楚。后来发现绝大多数慌张的时刻,起因都是同一个问题:不知道自己现在这次改动躺在哪个区域里,是工作区、暂存区,还是已经进了本地仓库。

搞清楚这四个区域之后,撤销操作就变成了一道选择题,选哪条命令看的是「我要退回到哪一层」。这篇按这个思路把日常会用到的 Git 命令过一遍,重点放在出错之后怎么救,以及 rebase 和 merge 这两条路各自适合什么场景。

如果你想看的是分支模型层面的约定,可以配合 Git Flow 工作流总结一起看,那篇讲的是团队怎么分支,这篇讲的是命令怎么用。

在本篇文章中,我们将从浅入深,和大家一起学习以下知识:

  • Git 的四个区域,以及每条命令在这四个区域之间怎么搬运数据
  • git add 之后反悔了,两种场景分别怎么退回去
  • commit 信息写错、漏提交文件、提交了不该提交的东西,分别怎么补救
  • git reset 和 git revert 到底差在哪,什么时候不能用 reset
  • git pull --rebase 和 git merge --no-ff 这对相反的策略各自解决什么问题
  • SSH 公钥的生成与配置
  • git stash 暂存现场的用法
  • .gitignore 改了不生效、克隆时文件名过长这类杂症
  • 怎么把 fork 出来的仓库同步上游更新

# 一、必备知识点

先把地图铺开。

Git 工作区、暂存区、本地仓库与远程仓库的关系

Git 常用命令在四个区域之间的流转

这两张图说的是同一件事,四个区域各自是什么:

  • Remote:远程主仓库
  • Repository/History:本地仓库
  • Stage/Index:Git 追踪树,也就是暂存区
  • workspace:本地工作区,即你编辑器里看到的代码

代码从左到右走一遍就是完整的提交流程:在工作区改完,git add 送进暂存区,git commit 落到本地仓库,git push 推到远程。

反过来,所有的「撤销」都是在问同一个问题:我要把改动从哪一层退回到哪一层。想通这一点,下面的命令就不用死记了。

# 二、git add 提交到暂存区,出错怎么办

一般的代码提交流程是这样:工作区改动,git status 查看状态,git add . 把所有修改加入暂存区,git commit -m "提交描述" 提交到本地仓库,git push 更新到远程仓库。

在这条链路上反悔,分两种情况。

场景 1

改乱了工作区某个文件,还没 add,想直接丢弃修改:

# 丢弃工作区的修改
git checkout -- <文件名>
@前端进阶之旅: 代码已经复制到剪贴板

这条命令是用暂存区(或者最近一次 commit)的版本覆盖工作区文件,被覆盖的内容 Git 从来没记录过,找不回来。所以执行之前一定要确认这些改动真的不要了。

场景 2

不但改乱了内容,还 git add 进了暂存区。这时候分两步:先用 git reset HEAD <文件名> 把它从暂存区退回工作区,就回到了场景 1;然后按场景 1 处理。

这两条命令都用了 checkout 和 reset 这种一词多义的老接口,Git 2.23 之后提供了语义更清楚的替代写法:git restore <文件名> 对应场景 1,git restore --staged <文件名> 对应场景 2 的第一步。老命令仍然可用,新项目建议直接上新的,少一次「这个 checkout 到底在干嘛」的思考。

# 三、git commit 提交到本地仓库,出错怎么办

# 3.1 提交信息写错了

改最近一次 commit 的信息:

git commit --amend -m "新提交消息"
@前端进阶之旅: 代码已经复制到剪贴板

--amend 是把这次提交重写一遍,生成的是一个全新的 commit 对象,哈希会变。只在还没 push 的时候用它,已经推到远程再 amend,别人拉下来就会冲突。

# 3.2 漏提交了文件

commit 完发现有个文件忘了带上,有两种解决方案。

方案一,再提交一次:

git commit -m "提交消息"
@前端进阶之旅: 代码已经复制到剪贴板

代价是 Git 上会出现两次 commit,历史里多一条没什么信息量的记录。

方案二,把遗漏的文件补到之前那次 commit 上:

git add missed-file    # missed-file 为遗漏提交的文件
git commit --amend --no-edit
@前端进阶之旅: 代码已经复制到剪贴板

--no-edit 表示提交消息保持不变,不弹编辑器。最终在 Git 上仅为一次提交,历史干净。

小改动我基本都走方案二,但同样受前面那条限制,push 之前用。

# 3.3 提交了错误文件,想回退版本

这里有两条路,reset 和 revert,它们的差别是这一节的重点。

git reset 删除指定的 commit

# 修改版本库,修改暂存区,修改工作区

# 把暂存区的修改撤销掉(unstage),重新放回工作区
git reset HEAD <文件名>

# 版本回退,回退到特定的 commit_id 版本
# 可以通过 git log 查看提交历史,确定要回退到哪个版本(commit 之后的即为 ID)
git reset --hard commit_id

# 将版本库回退 1 个版本
# 不仅把本地版本库的头指针重置到指定版本,也会重置暂存区,并把工作区代码一起回退
git reset --hard HEAD~1

# 修改版本库,保留暂存区,保留工作区
# 软回退 1 个版本,头指针重置到指定版本,且把这次提交之后的所有变更移动到暂存区
git reset --soft HEAD~1
@前端进阶之旅: 代码已经复制到剪贴板

三个参数的区别记住一句话就行:--soft 只动版本库,--mixed(默认值)动版本库和暂存区,--hard 三个区域全动。

--hard 是这份清单里最危险的一条,它会直接抹掉工作区的未提交改动。这个我踩过,当时以为只是回退提交记录,结果一天的活没了。真的执行错了也别急着放弃,git reflog 里还留着 HEAD 的移动记录,能找回刚才那个 commit 的哈希再 reset 回去,前提是那些改动至少 commit 过一次。没 commit 过的工作区内容,谁也救不了。

git revert 撤销某次操作

这次操作之前和之后的 commit 和 history 都会保留,撤销动作本身也会作为一次新的提交记录下来。

# 撤销前一次 commit
git revert HEAD

# 撤销前前一次 commit
git revert HEAD^

# 撤销指定的版本(比如 fa042ce57ebbe5bb9c8db709f719cec2c58ee7ff)
# 撤销也会作为一次提交进行保存
git revert commit
@前端进阶之旅: 代码已经复制到剪贴板

git revert 是提交一个新的版本,把需要 revert 的那个版本的内容反向修改回去,版本号继续递增,不影响之前提交的内容。

git revert 和 git reset 的区别

  • git revert 是用一次新的 commit 来回滚之前的 commit,git reset 是直接删除指定的 commit
  • 单看回滚这一步,两者效果差不多。区别出现在日后 merge 老分支的时候。git revert 是用一次逆向的 commit 中和之前的提交,日后合并老 branch 时,这部分改变不会再次出现;而 git reset 是直接把某些 commit 在某个 branch 上删掉,再和老 branch 合并时,这些被回滚的 commit 还会被引入
  • git reset 是把 HEAD 往后移,git revert 是 HEAD 继续往前走,只是新 commit 的内容和要 revert 的内容正好相反,能够抵消掉被 revert 的部分

实际用的时候有一条简单的判断:改动已经 push 到共享分支上了,就只能用 revert。因为 reset 会改写历史,你本地删掉的那些 commit 在别人的仓库里还在,下次同步会互相打架。只在自己本地没推出去的分支上,才可以放心 reset。

# 四、常用命令

# 4.1 初始开发的 git 操作流程

新进项目时基本就是这一串:

  • 克隆最新主分支项目代码 git clone 地址
  • 创建本地分支 git branch 分支名
  • 查看本地分支 git branch
  • 查看远程分支 git branch -a
  • 切换分支 git checkout 分支名(一般修改未提交则无法切换,大小写问题经常会有,可强制切换 git checkout 分支名 -f,非必须慎用)
  • 将本地分支推送到远程分支 git push <远程仓库> <本地分支>:<远程分支>

强制切换那条要格外小心,-f 会丢掉未提交的改动。有改动又想切分支,正确做法是先 git stash 收起来,后面第七节会讲。

# 4.2 git fetch

把某个远程主机的更新全部或按分支取回本地,此时只更新了 Repository,取回的代码对你本地的开发代码没有影响。想彻底更新还需要手动合并,或者直接用 git pull。

平时想看看远程有什么新东西又不想动自己的代码,fetch 是最安全的选择。

# 4.3 git pull

拉取远程主机某分支的更新,再与本地的指定分支合并,相当于 fetch 加上了合并分支的操作。

# 4.4 git push

将本地分支的更新推送到远程主机,命令格式与 git pull 相似。

# 4.5 分支操作

这一段是纯清单,用到查一下就行:

  • 下载指定分支:git clone -b <分支名> <仓库地址>
  • 拉取远程新分支:git checkout -b serverfix origin/serverfix
  • 合并本地分支:git merge hotfix(将 hotfix 分支合并到当前分支)
  • 合并远程分支:git merge origin/serverfix
  • 删除本地分支:git branch -d hotfix
  • 删除远程分支:git push origin --delete serverfix
  • 上传新命名的本地分支:git push origin newName
  • 创建新分支:git branch branchName
  • 切换到新分支:git checkout branchName
  • 创建并切换分支:git checkout -b branchName(相当于以上两条命令的合并)
  • 查看本地分支:git branch
  • 查看远程仓库所有分支:git branch -a
  • 本地分支重命名:git branch -m oldName newName
  • 把修改后的本地分支与远程分支关联:git branch --set-upstream-to origin/newName

删除本地分支时 -d 会检查这个分支是否已经合并过,没合并会拒绝删除,这是一道保护。确实要删没合并的分支才用 -D。

同样地,Git 2.23 之后 git switch <分支名> 和 git switch -c <新分支> 是更清楚的写法,把「切分支」从 checkout 里独立了出来。

# 五、优化操作

# 5.1 拉取代码用 pull --rebase

团队协作时,假设你和同伴在本地分别有各自的新提交,而同伴先于你 push 了代码到远程分支,所以你必须先执行 git pull 获取同伴的提交,然后才能 push 自己的提交。

按照 Git 的默认策略,如果远程分支和本地分支之间的提交线图有分叉(即不是 fast-forwarded),Git 会执行一次 merge 操作,因此产生一次没意义的提交记录,把线图搞得很乱。

其实在 pull 的时候加上 --rebase 就能很好地解决这个问题。加上这个参数的作用是,提交线图有分叉的话,Git 会用 rebase 策略代替默认的 merge 策略。

假设提交线图在执行 pull 前是这样的:

fe
  • 一、必备知识点
  • 二、git add 提交到暂存区,出错怎么办
  • 三、git commit 提交到本地仓库,出错怎么办
    • 3.1 提交信息写错了
    • 3.2 漏提交了文件
    • 3.3 提交了错误文件,想回退版本
  • 四、常用命令
    • 4.1 初始开发的 git 操作流程
    • 4.2 git fetch
    • 4.3 git pull
    • 4.4 git push
    • 4.5 分支操作
  • 五、优化操作
    • 5.1 拉取代码用 pull --rebase
    • 5.2 合代码用 merge --no-ff
  • 六、SSH 配置
  • 七、暂存
  • 八、克隆时提示文件名过长
  • 九、邮箱和用户名
  • 十、.gitignore 更新后不生效
  • 十一、同步 GitHub fork 出来的分支
  • 总结
  • 参考

← React children 详解,Children 工具方法与 cloneElement 实践Taro中使用ECharts小结 小程序与H5双端图表组件封装 →