掌握Git撤销操作,随心所欲控制文件状态

shenlank2 8年前
   <p>本文主要讨论和撤销有关的 git 操作。目的是让读者在遇到关于撤销问题时能够方便迅速对照执行解决问题,而不用去翻阅参数繁多的 git 使用说明。</p>    <p>一开始你只需了解大致功能即可,不必记住所有命令和具体参数。事实上,如果没有经过反复多次的操作,这些没血没肉的命令是很难被全部记清楚的,就算现在记住了,也会很快遗忘(天赋异禀,过目不忘者除外)。建议读者在遇到特定问题时对照场景操作,多用几次自然就记住了。</p>    <p>首先,我们再看看上一篇文章中的这张图:</p>    <p style="text-align: center;"><img src="https://simg.open-open.com/show/0c777e6cdf03e5b269a4ec31b8dc74ea.jpg"></p>    <p style="text-align: center;">git 命令和文件状态转换</p>    <p>这张图能让你一目了然的看到各种命令产生的效果。其中箭头移动方向可以理解成文件版本的复制方向。根据箭头的指向,我们能清楚的看到每个操作所产生的影响。几乎所有的git操作也就是让文件在这三个工作区域内移动,列如:</p>    <ul>     <li>git add <em>files</em> 工作目录下files复制 ->暂存区;</li>     <li>git checkout <em>files</em> 暂存区files复制 -> 工作目录;</li>     <li>git commit 暂存区内的文件作为一个版本保存 -> 版本库。</li>    </ul>    <p>为了使本文更具实用性,让我们来结合上图,通过实际项目中可能遇到的问题,以 Q&A 的形式来逐一讨论,当你遇到相似问题时可以迅速找到解决方法。</p>    <ol>     <li> <p><strong>Q: git add之后如何撤销?</strong></p> <p>TL;DR <sup> [1] </sup> 答案: 可以使用 git reset 撤销 <strong>所有</strong> 暂存区域文件。使用git reset <em>file</em> 来撤销特定的文件。该命令只是把暂存区内容移除,不会覆盖工作目录中已经修改过的同名文件。</p> <p>考虑这个场景,你正在为你的项目添加一个新功能,快完成时,你打算暂存起来,测试好了再 commit,于是你使用:</p> git add home.js <p>把涉及新功能改动的文件 home.js 暂存起来。此时你突然接到一个很紧急的 bug,需要立刻修复,你在一通 debug 之后成功 fix 了 bug,于是把涉及该 bug 的更新添加到暂存区:</p> git add main.js common.js <p>在准备使用 git commit 提交改动之前,你习惯性看看暂存区有哪些文件会被提交,于是你使用 git status 发现暂存区有三个文件:</p> home.js main.js common.js <p>很显然,你此时只想提交关于 bug 的修改部分文件,如果此时使用 git commit 会把关于其他功能的修改一起提交。那如何移走暂存区域home.js?很简单,只要执行 git reset 即可移除:</p> git reset home.js <p>之后执行 git commit 来提交这次 bug 修复所引起的改动。</p> <p>当然,如果你打算先提交新功能改动之后再提交 bug 修复内容,你可以先使用</p> <p>git reset main.js common.js 取消暂存区 bug 修改相关文件,再执行</p> git commit</li>     <li> <p>Q: 如何丢弃工作目录的更改?</p> <p>答案:使用 git checkout <em>file</em> 。 <strong>注意</strong> ,该操作不可逆,一旦被执行,你的改动都会消失,一般情况下只有你非常确定不需要工作目录下的最新改动内容才会这么做。 该命令会用暂存区的 <em>file</em> 内容覆盖掉工作目录的 <em>file</em> 文件内容。如果这个文件没有加入暂存区(或者说暂存区该 <em>file</em> 内容和Git仓库内容相同),执行该命令后,工作目录中该文件版本将和Git仓库最近一次提交版本相同。举例:</p> git checkout home.js <p>注意,如果你正好有个 branch 名字叫 home.js, git checkout 会checkout 一个 branch,此时你可以加上 -- 来用来表明这是个文件。</p> git checkout -- home.js <p>如果你想直接用最新的git仓库文件同时覆盖掉暂存区和工作目录内容可以使用(参见前文图示):</p> git checout HEAD -- files</li>     <li> <p><strong>Q: 如何丢弃本地仓库的 commit 内容?</strong></p> git reset --soft HEAD~1 <p>执行上述操作会撤销最后一次 commit 产生的效果并且保留 working directory 你所改动的内容。</p> <p>git reset --hard HEAD~1<br> 执行上述操作会撤销最后一次 commit 产生的效果并且 <strong>覆盖</strong> working directory 你所改动的内容。慎用,你一天的工作可能就此付之一炬,出了问题不能懒我(无辜状)。</p> <p>如果想取消最近N次的 commit 内容,只要把上面的1换成相应的数字即可。</p> </li>     <li> <p><strong>Q: 如何获取Git服务器上最新内容?</strong></p> <p>经常拉取新内容,可以减少 code merge。只要简单的执行 git pull 即可。注意还有一个类似的命令是 git fetch,二者的区别简单来说是:</p> git pull = git fetch + git merge</li>    </ol>    <p><strong>最后总结关于撤销操作的要点</strong></p>    <table>     <thead>      <tr>       <th>操作</th>       <th>撤销</th>      </tr>     </thead>     <tbody>      <tr>       <td>git add file</td>       <td>git reset<br> 重置暂存区内容,使得暂存区版本和仓库版本一致<br> git reset <em>file</em><br> 重置某一个文件的暂存状态,产生效果也就是把该文件从暂存区移除</td>      </tr>      <tr>       <td>修改了工作区文件</td>       <td>git checkout -- <em>files</em><br> 用暂存区版本 <strong>覆盖</strong> 工作区文件<br> git checout HEAD -- <em>files</em><br> 用git仓库最近版本 <strong>覆盖</strong> 工作区文件</td>      </tr>      <tr>       <td>git commit</td>       <td>git reset --soft HEAD~1<br> 撤销最近一次 commit 且不删除工作区改动<br> git reset --hard HEAD~1<br> 撤销最近一次 commit 且强制同步工作区版本到这 commit 改动之前状态</td>      </tr>     </tbody>    </table>    <h2>参考文献</h2>    <ol>     <li><a href="/misc/goto?guid=4959714969548613963" rel="nofollow,noindex">Pro Git book</a></li>     <li><a href="/misc/goto?guid=4959632063524710820" rel="nofollow,noindex">A Visual Git Reference</a> 须自备梯子</li>    </ol>    <p> </p>    <p>来自:http://www.jianshu.com/p/4ebbff227b87</p>    <p> </p>