Git作为一个版本管理系统(VCS)最基本的功能就是保证所有操作的追踪和回退。但是,很多初学者会被各种各样的命令,各种参数给弄蒙了,加上命令行提示基本都是英文,所以就跟瞎子一样,生怕一不小心把这弄丢了,一不小心把库弄坏了,pull不了,也push不了,不知道怎么办了。实际上git中任何操作,包括破坏性、删除性的操作都可以可以回退。本文中虫虫就带大家了解Git中各种撤消回退大法,等你熟悉了这些方法,可以让你放开手一心只搞代码,Git只是帮你插上翅膀,让你在代码库中自由的翱翔。
分支策略(工作流)
Git是一个去中心化的分布式版本控制系统,这意味着代码仓的版本控制不是统一的,各个客户端都是一个完整的仓库,然后互相通过Git服务器基于commits进行变更的交换。为了避免这种多来源的变更导致混乱,开发人员必须遵循各自开发组工作流程(如果没有的话,要制定一个),该流程取决于团队内部工作流程:如何撤消、如何更改某些变化?如何提交PR/MR?如何审核和合并分支?。
有三个典型的协作流程:Git flow,Github flow和 GitLab flow。利用可以在开发相同功能和无缝协作的情况下解决开发人员冲突和开发平衡。
Git flow
由Vincent Driessen提出,主要是建立两个长期分支Master和Devlop开发分支,同时又临时性的功能分支、补丁分支和预发布分支。版本衍变流程图如下:
GitHub flow
Github flow 是Github提出的相比较Git flow简配版,是 Github网站使用的工作流程。
该流程简练,主要为配合CI/CD(持续集成和发布),实现快速迭代。
Gitlab flow
Gitlab flow结合了上面两种流的优点,是gitlab产品使用的方法。Gitlab flow以master分支为基础,只有master接受的commit才可以合并得到其他分支
在该流程下,版本发布也是基于master分支来推进。
撤消本地的变化
在你没有执行push变化推送到远程库(Git服务器)之前,所有变化只会影响你自己,不会影响其他人。所以你可以随意处置这些变化,就算全部丢了,也可以通过clone重新复制一份,你所丢失的只不过是本地一些变化,包括未push的历史记录(已经commit未push),本地索引区staging(已经add未commit)和工作区的变化(未add),以及我们在之前的文章《让你提高工作效率的Git的技巧 》中提到的四种文件状态:已暂存 (Staged),已修改(Modified),未修改(Unmodified)以及未跟踪(Untracked)。
对于不同的阶段的变化,撤销的方法也各不相同:未分级的本地更改(在您提交之前)
未commit的工作区文件变化
对已经更改但还没有添加到暂存区的变化时,Git本身提供一种解决方案来撤销对某个文件的更改。假如我们我们编辑了一个文件vim <file>,没有添加的暂存区,文件应为unstaged文件(如果文件已创建,未跟踪Untracked)。可以通过git statu命令查看文件状态:
在该状态下,你有三种方法选择撤销这个更改:
放弃所有本地更改,但保存它们以便以后重复使用git stash 。
撤销本地变化(永久丢弃)git checkout -- <file> 。
永久丢弃对所有文件的所有本地更改 git reset --hard 。
快速保存本地更改(git stash)
你在开发正当中,接到一个紧急bug修复任务。由于你功能没完全写,你没法马上就commit,但你需要换到另一个分支,以完成紧急的修复。这时候你就需要祭出神器git stash来保存你时下的工作,切换到需要修复的分支,修复,commit,push。再用git stash pop回到你的工作状态,继续写你的代码。
git stash 还支持如下其他操作:
git stash save xxx :可以在保存时添加备注信息(类似于commit信息),这这样对于多个stash管理和识别将更加方便,设定备注信息后,可以在list中显示这个信息。
git stash list :列出所有以前暂储过的工作状态(支持多次的git stash暂存)。
git stash pop :用户回到上一个存储的工作状态并将其从stash存储列表中删除(类似于数组的pop的操作)。
git stash apply xxx : 回到指定的一个stach存储列表的工作状态,但将不会从stashed列表中删除。
commit之前分阶段的本地更改
假设已经添加了一些文件到暂存区,但是你不想在该该次commit中包含他们。但是又不想撤销对这些文件的修改,只是想将他们从暂存区移除。当然如果你想撤销这些变化的话,和上一部分提到那样可使用git reset --hard或者git stash。让我们回到我们示例仓库:
首先,用git status 看看目前的状态:
我要要从暂存去移除一些文件,比如我们移除four.txt:
git reset HEAD four.txt
结果,被移除的文件处于未跟踪状态:
如果不添加文件,则会从暂存去移除所有的文件,但是文件修改都保存,都位于工作区。
git rest
可以使用git stash 保存所有工作区文件变化,以及添加到暂存区的文件状态,这是工作区会回到上一次commit状态的文件状态。但是可以之后随时返回到该工作状态。
如果要丢弃所有文件变化,和暂存区的状态,则使用git reset --hard。结果和上一个方法类似。但是没有stash的暂存项,也无法在会到该工作状态。
commit的本地变更
commit提交后,版本控制系统就会正式记录该变化,以git对象的形式保存修改,进入历史存档,可以push到远程仓,并和其他人做版本交换。在未push之前,该的修改是仍未公开(无法与其他开发者交换)。所以,做撤消操作不会影响别人,我们可以随意操作。一旦代码push到远程仓,我们的做撤销操作就要格外注意了,尽量不要影响,不然整改团队都不能pull,push,别人会拿刀砍你,酿出血案的。
不修改历史(revert)
在实际的使用中,有可能有些预先的commit可能最终不是预期要push到远程仓的,或者是一个有bug的commit。这是我们可以可以简单地用git revert commit-id撤销这个commit。此命令会反转该commit中的增加的git对象,并删除commit,它不会修改git历史记录。
假设有以下顺序提交的A,B,C,D,E提交:A-B-C-D-E,现在我们想要撤销有问题代码的到B的commit。至于如何判别B是否bug问题,虫虫之前的文章中有提到过就是使用git bisect,这儿就不在详述,可以关注虫虫,浏览以前的文章。git bisect A..E
bisect会我们通过二分法reset到A到E之间的一个commit,我们做测试,然后判断代码是否正常,根据这个状态来迭代知道找到问题的commit B。
撤销B的commit引入的状态,我们使用:
git revert B-commit-id
如果仅仅是撤销部分文件或者目录,但是保存在暂存区,则用:
git checkout B-commit-id <file>
如果要撤销B的commit状态,并且暂存区移除则使用rest
git reset B-commit-i <file>
还有一个方法,我们也可以选用,那就是创建一个新分支,从有问题的地方开一个bug分支。比如A-B-C-D历史记录,现在发现C和D有问题。这是除了我们重置到B commit,并强推 F(这会导致与其他开发人员冲突)。这是新的历史揭露为A-B-F,大家都必须强制reset -f 才跟上你的push。另一个更可取得方法是,不改变当前的历史,从B开始新创建一个新的分支,并在该分支commit F。
git checkout B-commit-i
git checkout -b new-path-of-feature
git commit -a
修改commit历史(rebase)
还有一个常用的修改的命令就死git rebase。他也提供-i选项实现交互式的操作。在-i模式下,可以打开一个编辑器你在该编辑器中使用一些指令来操作commit历史:
reword commit信息还,编辑最近一次commit的消息。(可以用命令行git commit --amend)
edit 编辑提交内容(提交引入的修改)和消息
squash 将多个提交合并为一个提交,并且提供自定义或整合的commit消息
drop 删除commit
我们来举个实例。假想现在我们的仓库历史为all-hello-new,要删除hello。
rebase当前提交范围:
git rebase -i fbaf080184ed
命令打开你默认的编辑器,你编辑其中只需写下指令drop New,但你保留所有其他pick提交的默认内容。
保存并退出编辑会自动执行rebase。结果:
如果你想修改commit hello中引入的东西,类似的方法:
git rebase -i fbaf08018
命令打开编辑器,您可以在提交前编写edit new,但保留所有其他pick提交的默认内容。
保存并退出编辑器执行,进行编辑和提交更改:
git commit -a
反悔你的撤消
有时你撤销了一些修改,但是又发现这些修改还是有用的,又想反悔。通过命令git reflog我们可以追回所有已经分离的(git log不显示)commit-id。
要查看存储库历史记录并跟踪旧提交,可以使用以下命令:
git reflog show
输出显示存储库的历史记录。第一列为commit-id,其他列HEAD旁边的数字表示之前commit了多少次,可以当做下表来做引用该次的commit,在git命令(commit,rebase,merge,...)中当做参数代替commit-id使用,最后一列该记录的描述。
撤销远程仓中的变更
不修改commit历史撤消远程变更
这操作和修改本地提交的本地不修改历史撤销更改大致相同。它是撤消任何远程库上或者公共分支的commit的首选方法。对这种需求,最好的的方法是使用分支,分支使能够在新开发中引入现有修改(通过合并)和还可以提供了明确的commit时间序列和开发结构。
要撤销某些commit-id中引入的更改,我们可以通过revert简单地创建的commit中恢复commit-id(置换添加和删除),比如上图的需求删除B,我们可以通过revert
git revert B-commit-id
或创建一个新分支:
git checkout B-commit-id
git checkout -b new-path-of-feature
修改历史记录的撤消远程更改
当你想隐藏远程仓中有些敏感新的信息时候,则必须要用该方法(一般不要用,不然结果见第三部的图)。比如仓库中包含了token,密码,SSH私钥等。这样做的会让你失去了真正的commit 历史进程。还要注意的是,即便是修改了历史记录,commit被分离(detach了),依然可以通过commit-id访问(git没有执行自动清理分离commit之前)。 还有就是别人如果还没有同步该修改,他客户端里的信息也是完全的。
修改历史记录
确定好要修改的内容之后(历史记录的范围或范围旧提交),使用git rebase -i commit-id。然后,此命令将显示所有提交当前版本选择commit-id并允许修改,压缩,删除提交。
git rebase -i commit1-id..commit3-id
然后根据我们第三部分提到指令做修改。
修改后,通过git push -f强制推送到远程库生效(慎用慎用慎用!)。
筛选要删除的敏感信息文件(git filter-branch)
Git还允许从过去的提交中删除敏感信息。我们可以使用行git filter-branch,它允许我们对rebase历史记录做过滤。这个命令也是通过rebase修改历史记录,比如要删除某些历史记录历史文件一共使用:
git filter-branch --tree-filter 'rm filename' HEAD
注意git filter-branch命令在大型库上可会很慢。还有一些相对较快的,让我们筛选特定文件的第三方工具,比如BFG Repo-cleaner。
网友评论