git 后悔药

git 后悔药

前言

lilac 按:

错事常常有,后悔药却难寻。你是不是经常因为做错事而搜寻不到后悔药而苦恼万分呢?生活中很多事情确实无后悔药可吃,可是在git的世界里,只要用心思考,还是能找到不少好用的后悔药的。

如下几副后悔药为lilac思考总结的成果,如有不当,还请大佬们留言探讨,lilac先行谢过~

1744622-65656c7dbe6b9b93.jpeg
gitrepo-1.jpeg
  • Workspace:工作区
  • Index / Stage:暂存区
  • Repository:仓库区(或本地仓库)
  • Remote:远程仓库

git 中存在工作区,暂存区,本地仓库和远程仓库几个概念,上图比较直观地说明了这几个区之间的操作(图片来源于网络)。平时通过git进行代码管理时,一般也是将改动在这几个区之间进行变换。

P.S. 划重点啦!!!

在 Git 中任何 已提交的 东西几乎总是可以恢复的,甚至那些被删除的分支中的提交或使用 --amend 选项覆盖的提交或使用reset --hard命令重置后的提交等都是可以恢复的。 然而,任何你未提交的东西丢失后很可能再也找不到了。

因此在使用git时,比较保险的做法是多采用commit命令提交代码,即便你有代码洁癖,想维持提交记录的清晰可读性,也可以多次提交,反正有办法可以从历史提交记录中去掉某个指定的提交,也可以将多个提交合并为一个提交。

接下来分情况讨论下git的各种后悔药吧,下文的分析脉络如下面的脑图所示。

备注:所有没有被git托管的文件如果没有自己手动进行备份的话,是不存在后悔药的,故不做讨论。

1744622-01dc6f851a539f80.png
Git后悔药.png

约定: 后文中英文方括号[]中的内容表示可选内容,当可选内容为commitID时,若不填,则表示采用HEAD所指向的提交。

示例

下面通过一个具体的实例演示各种后悔药的服用方法。

入职几天后,Jack 对组内工作已略微熟悉,找 mentor 接手了一个小任务,想要好好表现一番。

他首先加入了项目组的中央仓库,并拉取了组内前辈们的代码,然后便开始了边听歌,边写代码实现功能的节奏。

情况一 撤销工作区的修改

写到一半,突然想到有个文件的代码修改考虑不周,需要推翻重写。可是这份代码是在已有文件上进行修改的,不能删除重来,并且修改量还挺大的,一行一行修改回来的话,实在是有点傻。Jack 开始烦恼了。。。

此时可以服用第一方后悔药:撤销工作区的修改

在某个 git 仓库中,修改了文件 file 后,发现此次修改不对,想要放弃此次修改,或者想要将 file 恢复到某个指定的状态,此时可以用 git checkout 命令

git checkout [commitID] [file1][file2]...

如:
git checkout file1          # 将文件 file1 恢复到 HEAD 所指向的提交的状态,即上一次提交的时候的状态
git checkout commit file1       # 将文件 file1 恢复到 commitID 所指向的提交的状态

采用 git checkout 命令将吴修改的文件恢复到修改前的状态后,Jack 又可以愉快地开始写代码了。。。

情况二 撤销暂存区的修改

写着写着,午饭时间到了,同事们叫 Jack 一起去吃午饭。Jack 心想着,还是先提交一下,备份下代码吧,万一吃完饭回来,手欠将好不容易写好的代码给弄没了呢。于是 git add . 了一下,哈哈,这个命令可还是不要随便用的好呀,这不,一不小心就把不该提交的修改内容添加到暂存区了吧。

第二方后悔药:撤销提交到暂存区的修改

git reset [commitID] [file1] [file2]...
​
如:
git reset commitID       # 将暂存区和本地仓库的内容重置为 commitID 所指向的提交
git reset file1 file2    # 撤销 file1 file2 的 add 操作</pre>

情况三 撤销已提交到本地仓库的修改

撤销完本不应该添加到暂存区的文件后,Jack 运行 git commit 命令完成了第一次提交,终于可以安心地去吃午饭了。饭后,Jack 又完成了几次提交,在某一次提交后,发现有几次提交的内容不太恰当,想要撤销这几次提交,重新提交。

第三方后悔药:撤销已提交到本地仓库的修改

若此次提交还未 push 到远程仓库,那么可以调用 git reset 命令重置 HEAD 指针,或调用 git revert 命令撤销此次提交的修改。若此次提交已经 push 到远程仓库,那么只能调用 git revert 命令撤销此次修改,然后重新提交正确的内容,并 push 到远程仓库。此时如果调用 git reset 命令重置 HEAD 指针的话,会导致本地 HEAD 指针落后于远程仓库的 HEAD 指针,从而在 push 时出错。

git reset [commitID]
git revert commitID

如:
git reset commitID    # 重置 HEAD 指针,使其指向 commitID 对应的提交
git reset HEAD^       # 重置 HEAD 指针,使其指向 HEAD 的父提交
git revert commitID   # 创建一个新的提交,其内容为撤销 commitID 对应的修改
git revert HEAD       # 创建一个新的提交,其内容为撤销 HEAD 指向的提交对应的修改

情况四 撤销已提交到远程仓库的修改

一般来说,不能撤销已经提交到远程仓库的修改,尤其是那些已经提交到一个与他人合作的分支的修改。但是,可以通过 git revert 命令创建一个新的提交,撤销上一个提交的修改,然后重新 push 到远程仓库来变相完成撤销已提交到远程仓库的修改。

P.S. 如果想要撤销的提交位于一个自己独有的远程分支上的话,也可以采用硬重置远程分支的方式来强制撤销已提交到远程仓库的修改。不过此类操作有风险,需谨慎执行。

示例:

假设远程分支 A 是一个 Jack 独有的分支,此时 Jack 想要撤销分支 A 上的一个提交,那么他可以按照如下操作

git checkout HEAD^ -b AR  # 创建本地分支 AR,指向本次提交的上一次提交,也就撤销了本次提交的修改
git log --oneline --all   # 查看所有分支的日志,每次提交一行,以确保 AR 指向的提交符合自己想要的状态
git push origin :A        # 如果 AR 指向的提交满足自己的需求,那么可以通过该命令删除远程分支 A
git push origin AR:A      # 将本地分支 AR 指向的提交提交到远程分支 A

至此,便真正撤销了已提交到远程仓库的修改</pre>

扩展知识

git checkout 命令详解

git checkout 命令有两项功能:

  • 切换到指定分支;

  • 将工作区的文件恢复到指定提交对应的状态;

使用语法:
git checkout [-q] [-f] [-m] [<branch>]
git checkout [-q] [-f] [-m] --detach [<branch>]
git checkout [-q] [-f] [-m] [--detach] <commit>
git checkout [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]
git checkout [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...
git checkout [<tree-ish>] [--] <pathspec>...
git checkout (-p|--patch) [<tree-ish>] [--] [<paths>...]</pre>

如果给定 checkout 命令的路径,那么会恢复路径指定的文件到指定提交的状态;如果指定分支名,则切换到指定分支;如果给定 -b 选项,那么会新建一个名为<new_branch>的新分支,并切换到该分支。

示例:
git checkout br        # 切换到 br 分支
git checkout -b br1    # 创建新分支 br1,并切换到 br1 分支
git checkout file      # 将工作区中的文件 file 恢复到上一个提交对应的状态,即撤销工作区对 file 的修改
git checkout -- file  # 与上一条命令含义相同,添加 -- 是为了避免文件名和分支名一致而引发歧义
git checkout <commit> --file  # 将工作区中的文件 file 恢复到 commit 提交对应的状态

git reset 命令详解

git reset 命令用于将 HEAD 指针重新指向某一个指定的提交,并根据 选项参数 而决定是否修改暂存区和工作区中的内容以与 HEAD 指针指向的内容相匹配。当给 reset 命令指定作用路径时,表示将路径指向的文件恢复到 add 操作前的状态,即与 git add 操作是一个相反的过程。

使用语法:
git reset [-q] [<tree-ish>] [--] <paths>...
git reset (--patch | -p) [<tree-ish>] [--] [<paths>...]
git reset [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>]
示例:
git reset --soft <commit>     # 只改变HEAD指针,暂存区和工作目录的内容保持不变
git reset --mixed <commit>    # 改变HEAD指针,同时改变暂存区的内容,工作目录的内容保持不变
git reset --hard <commit>     # 暂存区、工作区的内容都会被修改到与HEAD指针完全一致的状态
git reset --hard HEAD         # 让工作区回到上次提交时的状态
git reset file                # 将文件 file 恢复到 add 前的状态</pre>

《Git Pro》一书中将 git reset 命令和 git checkout 命令通过图示的方式解释得非常清楚,建议大家阅读一下。

git reset VS git revert

  • git revert 是用一次新的 commit 来回滚之前的 commit,git reset 是直接删除指定的 commit;

  • 虽然从回滚的结果来看,这两者差不多,都能使 commit 指向的内容恢复到修改前的状态,但这两者的作用过程是不同的。git revert 会新增一次 commit 来撤销指定 commit 的修改,而被撤销的提交依然是存在的,所以在与包含该被撤销的提交的分支进行 merge 时不会出现冲突,而 git reset 是直接删除需要被撤销的 commit,导致该 commit 在该分支上不复存在,从而在与包含该被撤销的提交的分支进行 merge 时,会存在冲突;

  • git reset 是把 HEAD 指针向后移动了一下,而 git revert 是将 HEAD 指针继续向前移动了一步,只是新的commit 的内容和要 revert 的 commit 的内容正好相反,能够抵消要被 revert 的 commit 的修改。

猜你喜欢

转载自blog.csdn.net/weixin_34273481/article/details/90842247