2.5W字!8个场景问题!带你了解最实用的 git 操作!!!
关注更多AI编程资讯请去AI Coding专区:https://juejin.cn/aicoding
?网上稍微搜一搜,每一篇 git 文章都在教你怎么 git add,git push,少数文章会给你说 git cherry-pick,git stash,但是很少有文章会给你说 git rebase。
从刚工作时起,你的 leader 前辈们都在告诉你,git rebase 很危险,平时就用 git merge 就行了,所以你工作了三五年,还没怎么用过 git rebase,有时候遇到 git 问题,不是删分支就是在删分支的路上,我猜你还会把需求代码先一点一点复制粘贴下来,然后删除旧分支再建新分支,再改回去,对吧?
stop!!!
诶棒油,你的头顶长了什么东西,我眼睛里没有~
那么今天,我们 git rebase 要讲,git cherry-pick、git stash 也要讲,而且要讲一些可能大家不知道的,实用的 git 知识和操作。
带着问题找答案,先来看看下面八个问题,你是否能解决。如果你都能解决,那么恭喜你,你的 git 已然化境:
(前四个问题只能算是开胃小菜)
「新需求忘记开新分支,提交到其它分支了,怎么办?」「新开发了一个功能,合并 master 了,产品过了一段时间说不要了,怎么办?(不可以殴打产品)」「功能开发了一半,需要拉一下 master,但是你又不想 commit 代码怎么办?」「刚才提交了一个 commit,发现 message 写得有问题,怎么办?」「需求开发提交了几个 commit,提交 master 时领导 review 后,说你第一笔 commit 代码有问题,让你改一下,怎么办?」「刚才提交了很多个 commit,发现最初的 commit meesage 写得有问题,怎么办?」「一个大需求开发了一个月,每天拉 master 代码合并到本地分支,发现这个需求自己提了十多个 commit,需求需要发布 merge 到 master 了,领导让你把十多个 commit 合并成一个,方便 code review,怎么办?」「昨天提交了一个 commit 到 master 了,今天发现 commit message 写错了,但是 master 上别人的提交已经有几十上百个了,领导说你 message 写错了,改一下,怎么办?」不能完全解答的朋友就请继续往下看。
为了方便演示,我现在新建一个 git-demo 的项目,新增 a.js、b.js、c.js、d.js 四个演示文件,每个文件一句代码,每个文件一次 commit 共四个 commit:
问题1:新需求忘记开新分支,提交到其它分支了,怎么办?我当初犯过最严重的错误就是,同时开发两个需求,忙来忙去,两个需求的 commit 搞岔了,两个分支都各有另一个需求的 commit,当时带我的同事都懵了。
然后在他一顿猛如虎的操作下,才理清了我的 commit。
其实答案很简单,就是 cherry-pick,这倒不是什么新鲜知识,很多有点工作经验的都知道,那为什么我们还讲,一是照顾新同学,二是这个命令对我们后续的题目解答很重要。
git cherry-pcik 的作用,就是将任意分支的 commit 挑拣到当前分支,但是这个挑拣不会删除原分支的 commit。
使用方式,就是紧跟着我们某个 commit 的 hash 值,可以跟多个,每个 hash 使用空格分割:
git cherry-pick 123abc 456def 789ghi
不妨在演示项目中新建一个分支 test:
我们在 test 分支新增一个 commit:
然后我们复制 e.js 的 hash,切回 master 使用 cherry-pick 将其应用到 master 上:
而 test 分支的 e.js commit 不会受到影响:
这就是 cherry-pick 最简单的应用。
不过,你聪明的小脑袋瓜可能会问,cherry-pick 任意分支?那挑拣的 commit 如果是在本分支呢?
诶,问得好,我们不妨试试。
先将刚才的 e.js 从 master 撤回。然后将 e.js、d.js 做 cherry-pick,这里的 e.js 是 test 分支的,d.js 是 master 分支自己的,我们在 master 分支做 cherry-pick 操作:
注意看红框里的内容,第一个框里说的意思,就是上一个 cherry-pick 是空的,可能是解决冲突产生的,你可以直接执行git commit --allow-empty将没有任何内容更改的 commit 提交到当前分支。
假设我们直接执行git commit --allow-empty,会弹出 vim 编辑器让我们编辑该 commit 的信息:
我们不做任何操作,直接按esc键,然后输入:wq并按回车键退出:
可以看到,d.js 有两次 commit:
回到前面,除了git commit --allow-empty,git 还提示我们有其它几个命令可以选择:
git cherry-pick --skipgit cherry-pick --continuegit cherry-pick --abortgit cherry-pick --skip的意思就是跳过,d.js 本身对比 master 自身来说没有任何修改,是空的,直接跳过,仅 cherry-pick e.js。
git cherry-pick --continue的意思是说,当你使用 git cherry-pick 遇到冲突,解决冲突并把修改添加到暂存区(使用 git add)之后,就可以使用这个命令,让 git 继续执行 cherry-pick 操作。如果你没解决冲突,一直执行这个命令,是不会有任何有意义的效果的。
git cherry-pick --abort则是放弃此次 cherry-pick,只要放弃了,所有内容都不会 pick,包括 e.js 也不会被 pick 过去,等于直接放弃本次 pick 操作。
综上,如果你在 cherry-pick 时不小心 pick 了本分支的 commit,且是空白内容没有实质性内容冲突,最好执行git cherry-pick --skip。但是实际上,那么多个 commit,它只会提示你The previous cherry-pick is now empty, possibly due to conflict resolution.,你并不知道具体是哪个,直接跳过也不见得是正确的,也可以先 pick 过来,再决定取舍。
另外,cherry-pick 的 commit 理想状态下是没有冲突的,但是很多时候会有冲突,必须解决冲突了才能继续 cherry-pick。
具体点说,冲突解决完,需要继续git add 文件1 文件2 ...,然后修改 commit message。
这两步做完,还需要继续执行:
git cherry-pick --continue
这样呢,一次 cherry-pick 的过程才算结束。
再多说一句,git cherry-pick 本就应该用于挑拣其它分支的 commit,所以用的时候不要挑拣本分支 commit。
问题2:新开发了一个功能,合并 master 了,产品过了一段时间说不要了,怎么办?这个问题也不算太难,直接 git revert。
使用方式,也是跟 hash 值:
git revert 123abc 456def
当然,上面的用法是针对不连续的 commit 来说的,如果你是连续的多个 commit 一起撤回,可以这么用:
git revert start_commit(不包含)..end_commit(包含),例如:
git revert abcdef123..7890abcd
问题3:功能开发了一半,需要拉一下 master,但是你又不想 commit 代码怎么办?git stash。
又是一个高频使用的命令,重要性不言而喻。场景很多,比如,我在当前 bug_fix 分支,在修复一个 bug,突然来了一个优先级更高的 bug,那已经写的代码不能直接 commit 吧?你当然可以说建一个新分支呗,一个 bug 一个分支,这也是一个解决方案,不过各家公司有各家公司的要求,具体问题具体分析。不切换分支的情况下,就可以 git stash。
修复完这个紧急 bug 后,我们需要继续修复前一个 bug,就可以执行git stash apply将之前暂存的代码恢复,继续开发。
不过,这里需要注意,git stash apply是应用最近一次 stash 的代码,如果你存了很多个,就必须指定。
我们可以通过git stash list命令查看所有的 stash:
stash@{0}: On main: stash1
stash@{1}: On feature: stash2
stash@{2}: On feature: stash3
stash@{3}: On main: stash4
越上面的越新,如果我们要应用某个旧的,指定一下即可:
git stash apply stash@{3}
git stash 和 git commit 一样也可以设置 message:
git stash -m'stash: 这是一个 stash'
篇幅所限,我们用表格汇总下 stash 命令:
命令作用示例git stash或git stash push将当前工作目录和暂存区的修改保存到栈中git stash push -m "保存修改"git stash list查看当前保存的所有 stashgit stash listgit stash apply应用最近一次保存的 stash,应用后 stash 仍保留在栈中git stash applygit stash apply stash编号应用指定的 stashgit stash apply stash@{1}git stash pop应用最近一次保存的 stash,并将其从栈中删除git stash popgit stash pop stash编号应用指定的 stash 并将其从栈中删除git stash pop stash@{1}git stash drop删除最近一次保存的 stashgit stash dropgit stash drop stash编号删除指定的 stashgit stash drop stash@{1}git stash clear删除栈中所有的 stashgit stash cleargit stash show查看最近一次 stash 的差异git stash showgit stash show stash编号查看指定 stash 的差异git stash show stash@{1}git stash show -p stash编号查看指定 stash 的详细差异内容git stash show -p stash@{1}问题4:刚才提交了一个 commit,发现 message 写得有问题,怎么办?git commit --amend。
当我们执行完命令后,会打开 vim 编辑器:
vim 编辑器的使用其实很简单,我们输入 i,底部会提示我们进入编辑模式:
我们使用箭头移动光标位置,输入新的 message:
修改完成,按esc键,再输入:wq回车,操作完成。
问题5:需求开发提交了几个 commit,提交 master 领导 review 后,说你第一笔 commit 代码有问题,让你改一下,怎么办?这里的意思很简单,当我们辛苦开发了一阵子需求,提了好几个 commit 后,发现某一笔(非最新一笔)的代码有问题,需要修改,一般人的做法,就是库库一阵改了,再提个新的 commit 呗。
这其实也可行,但是还是那句话,不同的公司有不同的规范,commit 的管理尺度各不相同,所以不用太较真场景和问题的解决方法。
假设我们现在不新增 commit,就在原 commit 的基础上修改,这就需要用到 rebase。我们不讲深奥的理论,就只讲实操和现象,你先用起来再说。
现在,我们将 b.js 的const a = 1的内容修改一下,且不新增 commit。
我们需要执行命令git rebase -i hash。
这里有个关键点,rebase -i 操作,后面跟的这个 hash 是一个开区间,也就是不包含在内的意思,假如,我要修改 b.js,hash 就是 a.js 的 hash,假如我要修改 c.js,hash 就至少得是 b.js 的 hash。
Talk is less,show me the code.
直接看结果,假设是 a.js 的 hash:
git rebase -i 081b0c26a4d7deb04ed1625b2a84f31f24d5fbe8
那么我们就可以编辑 b、c、d 三个提交。
假设是 b.js 的 hash:
git rebase -i b8cd836ef539457228a86fca8ddeb3ec2b52017e
那么我们就可以编辑 c、d 两个提交。
其实就是在这个开区间的 hash 范围内,所有的 commit 都可以被操作。
现在,我们就修改一下 b.js 的内容,不新增 commit:
这里绿色区域的文字,默认进入是 pick,当我们要操作某个 commit 时,将其替换为对应的操作,例如:
edit 的意思就是编辑该 commit。务必记得,在 vim 编辑器里,需要先键入i才能进入编辑模式。然后我们继续esc+:wq退出:
到了这一步,我们相当于穿越时空,来到了 b.js 提交的那个时空,我们在当前可以任意修改文件,任意操作代码,然后执行 commit,或者使用git commit --amend修改当前 b.js 的 message。
我们将 b.js 代码内容修改一下:
修改完代码,务必将修改后的代码暂存:
暂存完,这一步我们需要修改 commit message,如果我们不修改 commit message,我们直接git rebase --continue,系统还是会弹出 vim 编辑器让我们修改,不修改的话我们直接esc+:wq退出:
仔细观察红框中的内容,首先是 commit 没有什么变化,其次,master 分支名旁边的表示 rebase 进程的提示没有了。
那么,我们针对历史 commit 代码的修改就结束了。
好了,这时候聪明的你又会问,除了 edit,还有其它的操作吗?
有的,有的兄弟,这样的命令一共有 11 种:
pick 就是默认的,reword 是只修改 commit message 不修改代码,squash 是将多个 commit 合并为一个。
squash 这里比较重要,我们继续来举个例子,假设我一共 4 个 commit,那么到底什么场景下我会需要把它合并成一个呢?
首先,开发时间长,每天都可能要处理不同需求和 bug,有时就只能先 commit,然后去做其它事情。这样就会产生多个 commit,当我们开发完了,我们需要提代码给 leader review,你一个需求不能整个五六个、七八个 commit 给他看吧,这时候就需要合并 commit。
其次,假设我现在修改 bug,先写了一版,推上去发现有问题,我需要继续修改,此时我当然可以使用上面的 git rebase 的 edit 方法,不过你也可以再新增一个 commit,然后合并 commit,这也是一种方案。
OK,废话不多说。
假设我现在需要把 b.js、c.js 的 commit 合并为一个:
此时是 a.js 的 hash,才能选择 b.js 与 c.js。
git rebase -i 081b0c26a4d7deb04ed1625b2a84f31f24d5fbe8
(s 是 squash 的缩写)
但是你发现,为什么第一个 s 是红色的?
其实是因为,在 squash 操作中,第一个 commit 不允许被 squash,第一个默认就是 pick。
假设合并 b & c,需要这样:
假设合并 c & d,需要这样:
假设合并 b & c & d,需要这样:
那么继续esc+:wq退出,如果有冲突会按照冲突流程处理,前面我们已经处理过。
可能看到这里,你又有疑问,如果git rebase -i hash的 hash 是开区间,那我就是要编辑或者合并 a.js 的 commit 怎么办?
可以使用参数 --root 解决:
git rebase -i --root
必须这样执行,不需要加 hash。
这样又引出了新的问题,就是,我操作完了,发现搞错了,我怎么反悔?得回到 squash 前的状态啊!!
这里就涉及到 git 的 back 操作,想必你工作中也用到过,前面的内容中我们也提到了 revert。
那这里的回退,我们卖个关子,放在后面讲。
现在以表格整理一下这些命令,其余命令大家可以实操试试,毕竟实践出真知。
简称英文全称解释ppick按原样应用指定的提交,让提交按原有的顺序和内容应用到变基后的分支上。rreword应用该提交,但会暂停以允许修改提交信息,可用于完善之前提交时写得不够清晰或有错误的提交信息。eedit应用该提交,但会暂停以便修改提交内容,你能对此次提交所做的更改进行调整,之后使用git commit --amend更新提交。ssquash将该提交与前一个提交合并,并且可以编辑合并后的提交信息,有助于把多个相关的小提交合并成一个更有意义的大提交。ffixup类似于squash,将该提交合并到前一个提交,但会丢弃当前提交的提交信息,只保留前一个提交的信息。xexec在处理到该提交时执行一个 shell 命令,允许在变基过程中插入自定义操作,如运行测试脚本等。ddrop移除该提交,即不将此提交应用到变基后的分支中,可用于去除不必要的提交。bbreak在该提交处暂停变基,让你可以手动检查状态或执行额外操作,之后使用git rebase --continue继续变基。llabel在当前位置创建一个新的标签,类似于git label命令,方便后续引用该位置。treset返回到指定的标签位置,撤销自该标签之后的所有变基操作。mmerge引入指定的标签或提交,将其与当前分支合并,就像执行了一次git merge操作。问题6:刚才提交了很多个 commit,发现最初的 commit meesage 写得有问题,怎么办?通过前面的学习案例,后续的问题我们直接给出参考答案,减少演示带来的阅读负担。
第一个你应该想到的思路,就是 git rebase -i,并执行 reword 或者 edit 命令。
第二个思路,就是直接 revert 该 commit,然后重新 commit 时修改 message。
问题7:一个大需求开发了一个月,每天拉 master 代码合并到本地分支,发现这个需求自己提了十多个 commit,需求需要发布 merge 到 master 了,领导让你把十多个 commit 合并成一个,方便 code review,怎么办?git rebase -i,执行 squash 操作。
当然了,这是一个理想状态,你的需求的 commit 是连续的,只有你自己的 commit。
可现实开发中,一个大需求绵延一个月,我们每天还要更新 master 的代码,我们自己的 commit 可能散落在各个时间点上,不能直接 squash,不然你就会面对几百个上千个 commit,无异于大海捞针。
最正确的做法,是我先将开发完成的分支 push 到 remote 仓库,然后我新建一个 merge request,不要直接确认,我们只是需要其 diff 出分支上的更改。因为此时 gitlab 等托管平台已经会自动 diff 出本分支的 commit,这样你不用再去翻找一月前的那些 commit 了。
然后,你新建一个分支,将原来分支上所有的 commit (这就是为什么要在原分支新建 merge request,方便你复制 commit hash)使用 cherry-pick 转移到新分支上来。
接着使用 squash 操作将其合并为一个 commit,再推送新分支到 remote,发起真正的 merge request。
怎么样!!这个思路是不是很 sao ~。
问题8:昨天提交了一个 commit 到 master 了,今天发现 commit message 写错了,但是 master 上别人的提交已经有几十上百个了,领导说你 message 写错了,改一下,怎么办?你可能会说,git rebase -i,执行 reword 或者 edit。
是的,这不算错。
但是这和第 6 题不同,这是已经在 master 上的修改了,不是本地的 commit。
如果我们执行 rebase 操作,因为涉及到变基,代码 push 到托管平台后,会 diff 出你的变更,它会把该 commit 后的所有 commit 都识别为你的变更,你会发现你这个分支提上去 merge 到 master 时会有 N 个 commit,那些 commit 都是你同事的,且已经是在 master 上了。
理论上你直接合并也没有问题,因为你本身没有修改过任何东西,哪怕合上去起了冲突,冲突解决完就行了。但是你的领导看着你的这个操作,明明只是修改了一个 commit message,但是却多出来 N 个已经在 master 的 commit,他看到肯定会懵逼,几乎不会同意你的 merge request。
所以,最好的解决方案是就是:
将错就错~?
哈哈,大概率现实是这样的,你的 leader 会和你说,错就错了,一个 commit message 而已。
这其实没毛病,但是如果管理严格一点的公司呢?
或者说,这个问题难道真的没法解决吗?
其实方案我们在第 6 题也提过。
解决办法就是先对其 revert,然后重新 commit,这是最稳妥的解决办法。
对,思路就是这么简单,不要拘泥于 git rebase 强大的历史 commit 能力,最简单朴素的方式或许是最安全的。
一点点后续:我想反悔,怎么办?假如我们 git rebase 出错了怎么办?
假如你还没执行完,你的分支显示是这样的:master|REBASE,那么你可以直接执行git rebase --abort。这会直接放弃本次 rebase。
其它的情况,例如 cherry-pick 也是同理。
但是,假如你的分支变成了((0903e23230...))或者其它你看不懂的样子,并且 commit 也不见了一些,别慌!!
git 其实为我们保存了所有的历史记录。
我们使用 git reflog 可以查看:
想退出此模式的话直接q或者wq。
假设现在我想回到下面红框时候的 commit 状态:
此时,我只需要先复制它的 hash —— c7d7bbb,然后 reset:
git reset --hard c7d7bbb
就能回到最初的时候了!~
不过使用--hard参数会丢失暂存区的更改和影响工作目录,如果你不是很明确地想要回退到某个历史记录上,建议使用git reset --soft。下面也列一下 reset 的参数:
参数作用描述--soft仅移动分支指针到指定提交,暂存区和工作目录的内容保持不变,不改变文件的修改状态,暂存的内容依然保留。--mixed默认参数,移动分支指针到指定提交,同时重置暂存区,使其与指定提交时的状态一致,但工作目录中的文件内容不会被修改。--hard移动分支指针到指定提交,并将暂存区和工作目录的内容都重置为指定提交时的状态,会覆盖未提交的修改,使用需谨慎。--merge用于处理合并冲突后,将分支指针重置回合并前的状态,同时保留工作目录和暂存区中已解决的冲突内容。--keep尝试将分支指针移动到指定提交,同时保留工作目录中的修改。若工作目录中的修改与指定提交存在冲突,重置操作会失败。--abort终止一个正在进行的--merge或--keep重置操作。给个三连吧!!!!!!!!!!!!
往期推荐爆肝两个月,我用flutter开发了一款免费音乐app80+????102+??
搭建一个快速开发油猴脚本的前端工程24+????42+??
金九银十招聘季,IT 打工人,该怎么识别烂公司好公司?70+????80+??
为什么就这个文件的 ESLint 检查失效了?
学会 TypeScript 体操,轻松看懂开源项目代码
别人休息我努力,悄悄写个 cli 工具,必须提升效率,skr~60+????110+??
一文掌握 eslint,再也不怕项目报错20+????30+??
开发一个 npm 库应该做哪些工程配置?40+????50+??
分享我在前端学习与开发中用到的神仙网站和工具40+????110+??
uniapp 踩坑记录(二)130+????150+??
闲来无事,摸鱼时让 chatgpt 帮忙,写了一个 console 样式增强库并发布 npm100+????110+??
uniapp 初体验踩坑记录30+????60+??
两小时学会 JS 正则表达式,终身不忘50+????
【一年前端必知必会】如何写出简洁清晰的代码50+????
【一年前端必知必会】了解 Blob,ArrayBuffer,Base6440+????90+??
点击关注公众号,“技术干货” 及时达!
阅读原文
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线