关于git:Git-代码回滚与找回的艺术

44次阅读

共计 4014 个字符,预计需要花费 11 分钟才能阅读完成。

本文作者:marklai(赖泽浩)- 高级软件工程师,十年软件配置管理教训,现服务于 CSIG 云与智慧产业事业群质量部

导语

Git 是一个灵便和弱小的版本管理工具,正确应用可能无效促成团队合作,避免版本失落。然而实际中,有些开发人员会或无意或无心地误用局部 Git 的性能,给团队带来困扰,甚至造成损失。不失当的代码回滚操作是其中的次要问题之一。

本文次要分享针对不同场景的代码回滚操作,以及如何抢救误删的内容。

一个典型案例

咱们先通过一个我的项目团队实在呈现过的典型案例,来看看不失当的代码回滚可能带来的问题。

(1)小红、小黄、小蓝独特工作在同一条分支上。

(2)小红利用 reset 回滚了一些内容,发现 push 失败,最初用 push -f 操作胜利。更甚者,push -f提醒指标是爱护分支(例如 master)而无奈推送胜利,于是小红勾销了分支爱护,从而使得push -f 胜利。

(3)小黄小蓝进行惯例 git pull,遇到了一大堆抵触,并且 commit 历史都乱了!

(4)过一段时间,须要查看某次公布的源代码,却发现无奈找到精确的代码!原来它刚好被小红之前 reset 掉了。

意识 Git 的四个工作区域

在盘点常见的代码回滚场景之前,有必要认识一下 Git 的四个工作区域。

平时咱们 clone 一个代码库之后,本地看起来就是一个蕴含所有我的项目文件的目录。其实从逻辑上能够分为四个工作区域:

  • 工作区
    也称工作目录、工作正本,简略来说就是 clone 后咱们看到的蕴含我的项目文件的目录。咱们日常开发操作也是在工作区中进行的。
  • 本地仓库(.git)
    在工作区中有个暗藏目录.git,这就是 Git 本地仓库的数据库。工作区中的我的项目文件实际上就是从这里签出(checkout)而失去的,批改后的内容最终提交后记录到本地仓库中。
    Tips:不要手动批改 .git 目录的内容
  • 暂存区
    也称缓存区,逻辑上处于工作区和本地仓库之间,次要作用是标记批改内容,暂存区里的内容默认将在下一次提交时记录到本地仓库中。
  • 远端仓库
    团队合作往往须要指定远端仓库(个别是一个,也能够有多个),团队成员通过跟远端仓库交互来实现团队合作。

一个根本的 Git 工作流程如下:

  1. 工作区 中批改文件
  2. 暂存文件,将文件寄存在 暂存区
  3. 将改变从 暂存区 提交到 本地仓库
  4. 本地仓库 推送到 远端仓库

常见的代码回滚场景

回滚场景:仅在工作区批改时

当文件在工作区批改,还没有提交到暂存区和本地仓库时,能够用 git checkout -- 文件名 来回滚这部分批改。

不过须要特地注意的是 这些改变没有提交到 Git 仓库,Git 无奈追踪其历史,一旦回滚就间接抛弃了。

示例:用 git status 查看,还没提交到暂存区的批改呈现在“Changes not staged for commit:”局部。

执行以下命令回滚工作区的批改:

git checkout — build.sh

回滚场景:已增加到暂存区时

即执行过 git add 增加到暂存区,但还没 commit,这时能够用 git reset HEAD 文件名 回滚。通过git status 能够看到相干提醒:

执行以下命令回滚暂存区的批改:

git reset HEAD build.sh

回滚后工作区会保留该文件的改变,可从新编辑再提交,或者 git checkout -- 文件名 彻底抛弃批改。

回滚场景:已 commit,但还没有 push 时

即曾经提交到本地代码库了,不过还没有 push 到远端。这时候可用 git reset 命令,命令格局为:

git reset < 要回滚到的 commit> 或者 git reset --hard < 要回滚到的 commit>

需注意的是,提供的是 要回滚到的 commit,该 commit 之后的提交记录会被抛弃。

示例:

git reset 默认会将被抛弃的记录所改变的文件保留在工作区中,以便从新编辑和再提交。加上 --hard 选项则不保留这部分内容,需谨慎应用。

回滚场景:批改本地最近一次 commit

有时 commit 之后发现方才没改全,想再次批改后仍记录在一个 commit 里。利用 “git reset” 可达到这个目标,不过,Git 还提供了更简便的办法来批改最近一次 commit。

命令格局如下:

git commit --amend [-m <commit 阐明 >]

如果命令中不加 -m <commit 阐明 > 局部,则 Git 拉起编辑器来输出日志阐明。示例:

请留神,”git commit –amend” 只可用于批改本地未 push 的 commit,不要改变已 push 的 commit!

回滚场景:已 push 到远端时

留神!此时不能用 “git reset”,须要用 “git revert”!
留神!此时不能用 “git reset”,须要用 “git revert”!
留神!此时不能用 “git reset”,须要用 “git revert”!

重要事件说三遍!之所以这样强调,是因为 “git reset” 会抹掉历史,用在曾经 push 的记录上会带来各种问题;而 “git revert” 用于回滚某次提交的内容,并生成新的提交,不会抹掉历史。

示例:

过程中如果遇到问题(如解决抵触时搞乱了),可用 “git revert –abort” 勾销本次回滚行为。

如果要回滚的是一个合并 commit,revert 时要加上 ”-m < 父节点序号 >”,指定回滚后以哪个父节点的记录作为主线。合并的 commit 个别有 2 个父节点,按 1、2 数字排序,对于要回滚“分支合入主干的 commit”,罕用 ”-m 1″,即用骨干记录作为主线。回滚合并 commit 是一个较为简单的话题,作为一般性倡议,应防止回滚合并 commit。对该话题感兴趣的可进一步理解:https://github.com/git/git/blob/master/Documentation/howto/revert-a-faulty-merge.txt

Reset 与 revert 比照

本节再来讲一个示例,以便大家更好地了解 git resetgit revert的差别。

分支初始状态如下:

  • 如果执行 git reset B 工作区会指向 B,其后的提交(C、D)被抛弃。

    此时如果做一次新提交生成 C1C1跟 C、D 没有关联。

    • 如果执行 git revert B 回滚了 B 提交的内容后生成一个新 commit E,原有的历史不会被批改。

找回已删除的内容

虽说 Git 是一款弱小的版本管理工具,一般来说,提交到代码库的内容不必放心失落,然而某些非凡状况下仍免不了要做抢救找回,例如不失当的 reset、错删分支等。这就是 git reflog派上用场的时候了。

“git reflog” 是复原本地历史的强力工具,简直能够复原所有本地记录,例如被 reset 抛弃掉的 commit、被删掉的分支等,称得上代码找回的“最初一根救命稻草”。

然而须要留神,并非真正所有记录 ”git reflog” 都可能复原,有些状况依然无能为力:

  1. 非本地操作的记录 “git reflog” 能治理的是本地工作区操作记录,非本地(如其他人或在其余机器上)的记录它就无从通晓了。

  2. 未 commit 的内容 例如只在工作区或暂存区被回滚的内容(git checkout — 文件 或 git reset HEAD 文件)。

  3. 太长远的内容 “git reflog” 保留的记录有肯定工夫限度(默认 90 天),超时的会被主动清理。另外如果被动执行清理命令也会提前清理掉。

Reflog – 复原到特定 commit

一个典型场景是执行 reset 进行回滚,之后发现回滚错了,要复原到另一个 commit 的状态。

咱们通过 git reflog 查看 commit 操作历史,找到指标 commit,再通过 reset 复原到指标 commit。

通过这个示例咱们还能够看到 清晰、有意义的 commit log 十分有帮忙。如果 commit 日志都是 ”update”、”fix” 这类无明确意义的阐明,那么即便有 ”git reflog” 这样的工具,想找回指标内容也是一件艰辛的事。

### Reflog – 复原特定 commit 中的某个文件

场景:执行 reset 进行回滚,之后发现抛弃的 commit 中局部文件是须要的。解决办法:通过 reflog 找到指标 commit,再通过以下命令复原指标 commit 中的特定文件。

git checkout < 指标 commit> -- < 文件 >

示例:Reset 回滚到 commit 468213d 之后,发现原先最新状态中(即 commit d57f339)的 build.sh 文件还是须要的,于是将该文件版本独自复原到工作区中。

Reflog – 找回本地误删除的分支

场景:用 ”git branch -D” 删除本地分支,后发现删错了,下面还有未合并内容!解决办法:通过 reflog 找到分支被删前的 commit,基于指标 commit 重建分支。

git branch < 分支名 > < 指标 commit>

Reflog 记录中,”to < 分支名 >”(如 moving from master to dev/pilot-001)到切换到其余分支(如 moving from dev/pilot-001 to master)之间的 commit 记录就是分支上的改变,从中抉择须要的 commit 重建分支。

示例:

找回合流后删除的分支

作为 Git 优良实际之一,开发分支合流之后即可删掉,以放弃代码库整洁,只保留沉闷的分支。

一些同学合流后仍保留着分支,次要出于“分支当前可能还用失去”的想法。其实大可不必,已合入主干的内容不用放心失落,随时能够找回,包含从特定 commit 重建开发分支。并且,理论须要用到旧开发分支的状况真的很少,一般来说,即便性能有 bug,也是基于骨干拉出新分支来修复和验证。

如果要重建已合流分支,可通过骨干历史找到分支合并记录,进而找到分支节点,基于该 commit 新建分支,例如:

git branch dev/feature-abc 1f85427

对于代码回滚的一些倡议

以下是对于特定命令的应用倡议:

此外,总体来讲,回滚要审慎,不要过于依赖回滚性能,防止应用 ”git push -f”。正如某愚人所说:如果用到 ”git push -f”,你必定哪里做错了!

正文完
 0