Git作为一款分布式版本控制系统,是现代软件开发中不可缺少的工具之一,更是现在开发工程师必备的技能。可大多数工程师还是只会最基本的保存、拉取、推送,遇到一些commit管理的问题就束手无策,或者用一些不优雅的方式解决。
本文分享我在开发工作中实践过的6大高效命令,这些都能够大大提高工作效率,还能解决不少疑难场景。
作者:吴冬林| 前端开发工程师
一、6大高效命令总结
1、git reset:用于撤销或回退Git仓库中的更改。相当于后悔药,给你重新改过的机会。
2、git revert:用于撤销已经提交的一个或多个提交,同时保持这些撤销操作的历史记录。
**3、git rebase: **允许你将一系列提交从一个分支移到另一个分支上,以此来重组提交历史,使历史更加清晰和线性化。
4、git cherry-pick: 用于将一个或多个提交从一个分支复制并应用到另一个分支上。
5、git stash: 允许你暂时将未完成或未提交的工作存储起来,从而使工作目录恢复到一个干净的状态,便于切换分支等其他工作。
**6、git reflog: **用于记录你的仓库中的每一次分支引用变动历史,包括提交、切换分支、重置、合并等操作,便于后续查阅。
二、reset
认识:
git reset主要用于撤销或回退Git仓库中的更改。相当于后悔药,给你重新改过的机会。对于已经错误提交的场景,都可以再次修改并重新提交,保持干净的 commit 记录。
作用:
通过git reset,开发者可以灵活地管理他们的提交历史和工作目录状态,以适应不同的开发需求。
语法:
git reset [选项][目标]
[选项] :可以是 --soft、--mixed(默认)、或 --hard,用来指定重置的深度。
[目标] :可以是提交的SHA引用(比如 HEAD^ 表示上一次提交,HEAD~2 表示倒数第二次提交,或者具体的提交哈希值),分支名,或者其他Git引用。
使用场景:
1、取消暂存的更改:当你不小心使用 git add 添加了不想提交的文件,可以用 git reset 取消暂存。
2、撤销本地提交:如果你提交了一个错误的或不完整的提交,可以使用 git reset 回滚到之前的提交状态。
3、解决合并冲突:在合并冲突后,可能需要使用 git reset 来撤销合并并重新开始。
4、清理工作目录:在开发过程中,使用--hard选项可以快速清除所有未提交的改动,恢复到指定状态
使用方法:
1、git reset或者 git reset:取消暂存文件
2、git reset --soft HEAD^:软重置(撤销提交,只移动HEAD,保留所有更改)
3、git reset HEAD^ 或者 git reset --mixed HEAD^ :混合重置(默认,撤销提交,移动HEAD并取消暂存,保留工作目录更改)
4、git reset --hard HEAD^:硬重置(撤销提交,移动HEAD,取消暂存,重置工作目录,谨慎使用)
注意:使用 --hard 选项时要特别小心,因为它会永久丢失所有未提交的更改。
使用示例:
1、git reset --soft HEAD~2:回退最近2次提交到暂存区
最近的提交历史如下:
执行完git reset --soft HEAD~2后,可以看到提交历史已经回退到了"reset1",并且提交记录保留在暂存区;
2、git reset --soft [commit hash]:软重置到具体的提交hash;
最近的提交历史如下:
软回退到指定commit hash "4ad5ce3",如图:
并且提交内容保留在暂存区,如图:
**3.git reset --hard [commit hash] : **硬重置到具体的提交hash; 将从commit hash(不包括此hash)之后的提交丢弃;
最近的提交历史如下:
硬重置到指定commit hash "fcbfe80",如图:
并且暂存区的内容被丢弃,工作目录被重置,如图:
注意点:
1、以上说的是还未 push 的commit,对于已经 push 的 commit,也可以使用该命令,不过再次 push 时,由于远程分支和本地分支有差异,需要强制推送 git push -f 来覆盖被 reset 的 commit。
2、在 reset --soft 指定 commit 号时,会将该 commit 到最近一次 commit 的所有修改内容全部恢复,而不是只针对该 commit。
三、revert
认识:
git revert 用于撤销已经提交的一个或多个提交,同时保持这些撤销操作的历史记录。
作用:
● 撤销变更:撤销指定提交的所有更改,包括文件的添加、删除或修改。
●保留历史:在撤销的同时,生成一个新的提交,清晰记录了撤销操作,保持了提交历史的连续性和完整性。
●非破坏性操作:不会丢失任何工作树中的未提交更改,安全适用于团队协作环境。
语法:
git revert [选项]<提交>
其中,<提交>可以是具体的提交哈希值(commit hash)、分支名、标签名,或者HEAD的相对位置(如 HEAD^ 表示上一次提交,HEAD~2 表示倒数第二次提交)。
常见的选项包括:
-n 或--no-commit:执行操作但不自动提交,给用户机会手动编辑提交信息或添加更多更改。
-m<parent-number>:当撤销一个合并提交时,可以选择要基于哪一个父提交来创建撤销提交。
使用场景:
有一天测试突然跟你说,你开发上线的功能有问题,需要马上撤回,否则会影响到系统使用。如果使用reset 回退,那么该分支上其他同事提交的代码也会被撤回。
如果只想撤回自己的提交,保留其他同事的提交记录,git revert 就可以解决这个问题;
使用方法:
1、git revert HEAD:撤销上一次提交,并自动创建一个新的提交来记录撤销操作;
2、git revert [commit hash]:撤销指定的commit hash,并自动创建一个新的提交来记录撤销操作;
3、git revert HEAD~2:撤销指定的提交,例如撤销前三次提交中的第二次提交;
4、git revert HEAD~2..HEAD:撤销多个提交,例如撤销从第二次到最后一次的所有提交;
5、git revert -m 1 HEAD:指定并保留主线分支代码,撤销另一条分支代码。-m 后面要跟一个 parent number 标识出"主线",一般使用 1 保留主分支代码。
使用示例:
1、git revert [commit hash]:撤销指定的commit hash,并自动创建一个新的提交来记录撤销操作;
最近的提交历史如下:
使用 git revert 回退指定commit hash "ec2c9d9",如图:
由于“revert2”提交记录位于“revert1”和“revert3”之间,存在冲突,保留双方更改即可;
效果如下:“revert2”提交记录被撤回,并且重新生成了一条新的提交记录,自动备注为Revert "revert2"
注意点:
1、撤销合并提交:如果要撤销的是一个合并提交,git revert默认会尝试撤销整个合并操作,这可能导致大量冲突。此时,可能需要指定-m选项来选择合并的基础分支。
2、冲突处理:在撤销过程中遇到冲突时,Git会停止并让用户手动解决冲突,解决后需手动执行git add和git commit来完成撤销。
3、影响共享历史:在公共分支上使用git revert是安全的,因为它不会重写历史,减少了与他人工作的冲突风险。但在执行后,应尽快推送到远程仓库,以避免他人基于被撤销的提交进行工作。
扩展:
git revert 和 git reset区别:
1、回滚和删除commit:git revert是用一次新的commit来回滚之前的commit,git reset是直接删除指定的commit。
2、对HEAD操作顺序不同:git reset 是把HEAD向后移动了一下,而git revert是HEAD继续前进,只是新的commit的内容和要revert的内容正好相反,能够抵消要被revert的内容。
3、安全性:相较于 git reset 命令,git revert 命令更加安全,因为它不会删除任何提交,而是创建新的提交。
总结
如果你需要撤销一个公开的提交,并希望保持项目历史的清晰,应该使用 git revert。
对于本地开发过程中想要尝试不同的方向或撤销错误的提交,尤其是在没有将更改推送到公共仓库的情况下,git reset 是更灵活的选择,但需谨慎使用--hard 参数,以防数据丢失。
四、rebase
认识:
git rebase 是一个高级的Git命令,它允许你将一系列提交从一个分支移到另一个分支上,以此来重组提交历史,使历史更加清晰和线性化。
作用:
● 简化历史:通过将一系列提交合并为一个或几个更有意义的提交,从而使得提交历史更加整洁。
●解决冲突:在变基过程中,如果有冲突,Git会暂停变基过程,允许你解决冲突后再继续。
●同步分支:将本地分支的更改基于远程分支的最新状态进行重新应用,有助于保持分支与上游分支的同步。
●避免合并提交:相比于git merge,使用git rebase可以生成一个干净的、没有合并提交的历史记录
语法:
git rebase [选项]<upstream>
<upstream>:指定了你想要变基到的目标分支。
常见的[选项]包括:
--onto 用于指定新的基底
--interactive 或 -i 用于交互式变基
--abort 用于终止一个已经开始的变基操作
--continue 用于继续一个已经开始的变基操作
使用场景:
●在将特性分支的更改合并到主分支之前,希望保持主分支的提交历史干净。
●当你想在推送前整理个人的提交历史,使其更易于阅读和理解。
●需要将本地的多次提交整合后推送,以符合团队的提交规范。
使用方法:
1、git rebase main:将当前分支基于main 分支最新提交进行变基;
理解:在当前分支上(如 git rebase分支),通过执行git rebase main,将当前分支最新提交内容追加到main分支最新提交记录的后面;只是git rebase分支提交记录顺序发生了变化,main分支提交记录并没有发生变化;
假设你在 feature 分支上工作,想要将这些更改基于 main 分支的最新状态进行变基
gitcheckout feature
git rebase main
2、git rebase -i HEAD~3:交互式 rebase(修改提交历史);
使用示例:
1、git rebase git-test:将当前分支git-rebase变基到 git-test 分支上
如图,git-rebase分支为从git test分支checkout出的子分支;
git test分支日志如下:
最新的三条提交记录为git cherry-pick 3、git cherry-pick 2、git cherry-pick 1
git rebase分支日志如下:
新增了git rebase3、git rebase2、git rebase1三条提交记录;
注意:将git-rebase变基到 git-test 分支上,git-test分支需要有新的提交内容;
如图,继续在git-test分支新增内容并提交,如new git test 2 、new git test 1;
现在git test分支在上次拉取git rebase分支之后有了新的提交;切换到git rebase 分支,执行git rebase git-test,查看合并历史;
效果如下:
可以看出,在变基过程中,发生了冲突,执行git add.,继续git rebase --continue,最后git push -f 强制推送到远端。
结论:git rebase分支上提交记录被追加到了git test分支最新提交记录(即 new git test 2)的后面,并且呈现了线性化的提交记录;
2、git rebase -i HEAD~3:指定了对当前分支的最近3次提交进行操作。
实现步骤:
(1)切换到你想重新编辑的分支:
gitcheckout git-rebase
(2)交互式 rebase 到你想开始的点(例如,重新编辑最近的三个提交):
gitrebase -i HEAD~3
这会打开一个文本编辑器,其中列出了最近的三个提交。你可以修改它们(例如,重新排序、合并、编辑或删除)。
(3)保存并关闭(:wq)编辑器后,Git 会根据你的指示重新应用提交。如果有冲突,你需要解决它们。
注意点:
1、不要在已推送的分支上使用 rebase,除非你是该分支唯一的贡献者,或者所有协作者都明白变基的后果。
2、不能在一个共享的分支上进行git rebase操作。变基会改变历史,这可能会影响与你共享代码的其他开发者。
3、使用 git rebase -i 进行交互式变基时,可以更精细地控制哪些提交被应用、修改或跳过。
4、在团队协作环境中,务必与团队成员沟通后再决定是否使用 git rebase,以避免造成他人工作树的混乱。
扩展:
rebase与merge的区别:
1、合并历史记录的不同:
● merge: 当你使用git merge命令时,它会创建一个新的合并提交(merge commit),这个提交有两个父提交——一个是当前分支(HEAD)的最后一个提交,另一个是要合并进来的分支的最后一个提交。这样保留了两个分支的完整历史,生成了一个包含所有更改的新提交。
● rebase: 而git rebase则是将你的当前分支的提交“重新播放”(reapply)到另一个分支的末尾。这个过程会修改提交历史,使得看起来好像你的更改直接基于目标分支的最新提交进行的。rebase会把你的提交一个个地取下、暂存,然后应用到目标分支的最新提交之上,从而产生一个线性的提交历史。
2、适用场景:
● merge适合团队协作场景,尤其是当多人同时在一个特性上工作,需要保留每个人的提交历史以便追踪和审查时。
● rebase更适合当你在个人分支上工作,希望保持提交历史整洁,或者当你需要将你的分支更新到最新的主分支状态,但又不想引入不必要的合并提交时。然而,使用rebase时要小心,因为它会重写历史,如果已经推送的分支被rebase,会导致与他人仓库的历史不一致。
五、cherry-pick
认识:
git cherry-pick 用于将一个或多个提交从一个分支复制并应用到当前分支上。这个操作不会改变原始提交所在的分支,而是创建了该提交的一个新副本。
作用:
● 灵活移植提交:允许用户选择性地将某个分支中的特定提交应用到另一个分支,而不必合并整个分支。
●修复错误或添加特性:当需要在不同的发布分支中应用同一个修复或功能时非常有用,可以避免重复劳动。
●维护分支同步:在不合并整个历史的情况下,将关键提交同步到维护分支中。
语法:
git cherry-pick <commit>
其中<commit>是你要应用的提交的哈希值(或简短哈希)、分支名上的提交位置等引用。
使用场景:
●当你需要将一个特性或修复从开发分支快速迁移到稳定分支时。
●在进行紧急修复时,直接将修复提交应用到多个分支上。
●合并特定的更改,而不是整个分支的内容,以保持历史的清晰度。
使用方法:
1、git cherry-pick:应用单个指定的提交到当前分支
假设你想要从 feature 分支挑取最近的一个提交 A 到 main 分支上:
git checkout main
git cherry-pick <commit_A_hash>
2、git cherry-pick<commit1>^..<commit2>:多个连续提交;
说明:上面的命令将 commit1 到 commit2 这个区间的 commit 都应用到当前分支(包含commit1、commit2);
注意:commit1 是最早的提交。
3、git cherry-pick<commit1><commit3>:多个特定的提交;对于非连续的多个提交,你可以分别列出每个提交的哈希值。
说明:这样,Git会依次尝试应用你列出的所有提交。
在执行cherry-pick过程中可能会遇到冲突,以下三个选项用于处理发生冲突之后的情况:
1、git cherry-pick --continue:用于继续cherry-pick过程。这会完成剩余的cherry-pick操作,将解决冲突后的提交应用到当前分支。
2、git cherry-pick --abort :撤销已经开始的cherry-pick,恢复你的工作目录和索引到cherry-pick操作开始前的状态,就像该操作从未发生过一样。
3、git cherry-pick --skip:用于跳过引起冲突的提交,直接移动到下一个(如果有的话)指定的cherry-pick操作。这在你批量cherry-pick多个提交时特别有用,允许你跳过有问题的提交,继续处理列表中的其余提交。
使用示例:
git cherry-pick<commit1>^..<commit2>:多个连续提交
如图,在git-cherry-pick分支上最近有三次提交记录,对应的hash分别为:c53d6cd、c618ab8、99d93b8
要求:将git-cherry-pick分支上最近的三次提交记录,复制到git-test分支;
切换到git-test分支,操作如下:
可以看到在git-test分支上,三次提交记录已经被成功复制过来了;
注意点:
1、冲突处理:如同合并操作,cherry-pick也可能导致冲突,需要手动解决。
2、更改历史:虽然cherry-pick创建的是原提交的新副本,但这也改变了应用分支的历史,可能需要与团队成员同步信息。
3、并行 cherry-pick:对一系列连续提交执行 cherry-pick 时,如果其中一个失败,可以使用 git cherry-pick --abort 来取消整个操作,或用 git cherry-pick --skip 跳过当前失败的提交继续下一个。
4、保留提交信息:cherry-picked的提交在新分支上会有新的提交哈希,但作者、日期、提交消息等元数据通常会被保留。
六、stash
认识:
git stash允许你暂时将未完成或未提交的工作存储起来,从而使工作目录恢复到一个干净的状态。
作用:
●保存工作状态:保存当前未提交的改动(包括未跟踪的文件),以便之后恢复。
●清理工作区:在不提交也不丢失更改的情况下,清理工作目录,便于切换分支或拉取远程代码。
●灵活管理上下文:方便在多个任务间切换,每个stash都保存了一个独立的工作上下文。
语法:
基础语法有几种形式,最常用的是:
git stash
git stash save [描述]
使用场景:
1、紧急切换分支:当你正在开发一个特性,突然需要紧急修复另一个分支的问题,可以先stash当前工作,切换到另一个分支完成修复后再回来。
2、代码审查或合并前准备:在提交代码进行审查或合并之前,若工作区不干净,可以先stash更改,保证合并或审查过程不受干扰。
3、实验性更改:当你想尝试一些可能不保留的实验性代码时,stash可以用来安全地保存当前状态,以便随时恢复。
使用方法:
- git stash list:列出所有stash的快照。
- git stash apply [stash@{编号}]:应用某个stash的更改到工作目录,但不删除stash。
- git stash pop:应用最近一次stash的更改并删除该stash。
- git stash drop [stash@{编号}]:删除指定的stash快照。
- git stash clear:删除所有stash快照。
- git stash save "stash temp1":保存当前工作区的所有更改,并附带描述。
使用示例:
1、新增stash、应用最近一次stash的更改并删除该stash、删除指定的stash快照。
注意点:
1、未跟踪文件:默认情况下,git stash只会保存已经被Git跟踪的文件的更改。如果需要保存未被跟踪的文件,需要加上--all选项。
2、stash冲突:在应用stash时,如果当前工作区也有未提交的更改,可能会产生冲突,需要手动解决。
3、stash丢失:一旦删除了一个stash,除非你有其他备份,否则无法恢复。使用git stash list定期检查stash列表,谨慎使用git stash clear。
4、stash命名:虽然stash默认只用时间戳标记,但可以通过git stash save "描述"来为stash添加有意义的描述,便于日后识别。
七、reflog
认识:
git reflog 是Git中一个非常有用的命令,它显示了HEAD(当前分支的指针)以及其他引用的移动记录。简而言之,reflog记录了你的仓库中的每一次分支引用变动历史,包括提交、切换分支、重置、合并等操作。即使某些操作导致了提交被删除或丢失,git reflog也能帮助你找回这些丢失的提交。
作用:
● 恢复丢失的提交:当通过git reset、git rebase等操作意外丢失了提交,git reflog能帮助你找到这些提交的SHA值,进而恢复。
●追踪历史变动:查看HEAD或任何引用的历史变动轨迹,有助于理解仓库的变动历史。
●辅助调试:在解决版本控制问题时,提供了一条追溯和诊断的途径。
语法:
git reflog [引用]
如果不指定引用,默认查看HEAD的reflog。也可以指定其他引用,如分支名、标签名等。
使用场景:
1、误删提交恢复:不小心执行了git reset --hard或git push --force,导致提交丢失。
2、探索历史:想了解仓库中的历史变动,特别是HEAD指针的移动情况。
3、找回分支点:忘记之前在哪个提交上创建了分支,可以通过reflog查看分支创建前的提交。
使用方法:
1、git reflog:查看HEAD的reflog
2、git reflog master:查看特定分支的reflog(例如master分支)
使用示例:
1、git reflog:查看HEAD的reflog
如图,在git-test分支上,可以看到该分支上的所有的操作记录,包括reset、commit、cherry-pick、checkout等;
2、git reflog [branch]:查看特定分支的reflog(例如git-cherry-pick分支)
如图,通过 git reflog git-cherry-pick,可查看git-cherry-pick分支具体的操作记录;
注意点:
1、有效期:reflog的记录是有期限的,默认情况下,本地仓库的reflog记录大约保存30天到90天,之后旧的记录会被自动删除。可以通过配置gc.reflogExpire和gc.reflogExpireUnreachable来调整。
2、本地特性:reflog是本地仓库特有的,不会随着push或pull操作传播到远程仓库。因此,它不能帮助恢复远程仓库中丢失的提交。
3、恢复策略:找到丢失提交的SHA值后,可以使用git cherry-pick或git reset等命令来恢复提交。注意,操作前最好备份当前工作状态,以防万一。
以上就是本期的全部内容~
版权声明:本文由神州数码云基地团队整理撰写,若转载请注明出处。
公众号搜索神州数码云基地,了解更多技术干货。
版权归原作者 神州数码云基地 所有, 如有侵权,请联系我们删除。