任何版本控制系统的一个最有的用特性就是“撤销 (undo)”你的错误操作的能力。在 Git 里,“撤销” 蕴含了不少略有差别的功能。
当你进行一次新的提交的时候,Git 会保存你代码库在那个特定时间点的快照;之后,你可以利用 Git 返回到你的项目的一个早期版本。
在本篇博文里,我会讲解某些你需要“撤销”已做出的修改的常见场景,以及利用 Git 进行这些操作的最佳方法。
场景: 你已经执行了 git push
, 把你的修改发送到了 GitHub,现在你意识到这些 commit 的其中一个是有问题的,你需要撤销那一个 commit.
方法: git revert
原理: git revert
会产生一个新的 commit,它和指定 SHA 对应的 commit 是相反的(或者说是反转的)。如果原先的 commit 是“物质”,新的 commit 就是“反物质” — 任何从原先的 commit 里删除的内容会在新的 commit 里被加回去,任何在原先的 commit 里加入的内容会在新的 commit 里被删除。
这是 Git 最安全、最基本的撤销场景,因为它并不会改变历史 — 所以你现在可以 git push
新的“反转” commit 来抵消你错误提交的 commit。
场景: 你在最后一条 commit 消息里有个笔误,已经执行了 git commit -m "Fxies bug #42",但在
git push
之前你意识到消息应该是 “Fixes bug #42″。
方法: git commit --amend
或 git commit --amend -m "Fixes bug #42"
原理: git commit --amend
会用一个新的 commit 更新并替换最近的 commit ,这个新的 commit 会把任何修改内容和上一个 commit 的内容结合起来。如果当前没有提出任何修改,这个操作就只会把上次的 commit 消息重写一遍。
场景: 一只猫从键盘上走过,无意中保存了修改,然后破坏了编辑器。不过,你还没有 commit 这些修改。你想要恢复被修改文件里的所有内容 — 就像上次 commit 的时候一模一样。
方法: git checkout --
原理: git checkout
会把工作目录里的文件修改到 Git 之前记录的某个状态。你可以提供一个你想返回的分支名或特定 SHA ,或者在缺省情况下,Git 会认为你希望 checkout 的是HEAD
,当前 checkout 分支的最后一次 commit。
记住:你用这种方法“撤销”的任何修改真的会完全消失。因为它们从来没有被提交过,所以之后 Git 也无法帮助我们恢复它们。你要确保自己了解你在这个操作里扔掉的东西是什么!(也许可以先利用 git diff
确认一下)
场景: 你在本地提交了一些东西(还没有 push),但是所有这些东西都很糟糕,你希望撤销前面的三次提交 — 就像它们从来没有发生过一样。
方法: git reset
或 git reset --hard
原理: git reset
会把你的代码库历史返回到指定的 SHA 状态。 这样就像是这些提交从来没有发生过。缺省情况下, git reset
会保留工作目录。这样,提交是没有了,但是修改内容还在磁盘上。这是一种安全的选择,但通常我们会希望一步就“撤销”提交以及修改内容 — 这就是 --hard
选项的功能。
场景: 你提交了几个 commit,然后用 git reset --hard
撤销了这些修改(见上一段),接着你又意识到:你希望还原这些修改!
方法: git reflog
和 git reset
或 git checkout
原理: git reflog
对于恢复项目历史是一个超棒的资源。你可以恢复几乎 任何东西 — 任何你 commit 过的东西 — 只要通过 reflog。
你可能已经熟悉了 git log
命令,它会显示 commit 的列表。 git reflog
也是类似的,不过它显示的是一个 HEAD
发生改变的时间列表.
一些注意事项:
它涉及的只是 HEAD
的改变。在你切换分支、用 git commit
进行提交、以及用 git reset
撤销 commit 时,HEAD
会改变,但当你用 git checkout --
撤销时(正如我们在前面讲到的情况),HEAD 并不会改变 — 如前所述,这些修改从来没有被提交过,因此 reflog 也无法帮助我们恢复它们。git reflog
不会永远保持。Git 会定期清理那些 “用不到的” 对象。不要指望几个月前的提交还一直躺在那里。reflog
就是你的,只是你的。你不能用 git reflog
来恢复另一个开发者没有 push 过的 commit。那么…你怎么利用 reflog 来“恢复”之前“撤销”的 commit 呢?它取决于你想做到的到底是什么:
git reset --hard
git checkout --
git cherry-pick
场景: 你进行了一些提交,然后意识到你开始 check out 的是 master 分支。你希望这些提交进到另一个特性(feature)分支里。
方法: git branch feature
, git reset --hard origin/master
, and git checkout feature
原理: 你可能习惯了用 git checkout -b
— 这是创建新分支并马上 check out 的流行捷径 — 但是你不希望马上切换分支。这里, git branch feature
创建一个叫做 feature
的新分支并指向你最近的 commit,但还是让你 check out 在 master 分支上。
下一步,在提交任何新的 commit 之前,用 git reset --hard
把 master
分支倒回 origin/master 。不过别担心,那些 commit 还在
feature 分支里。
最后,用 git checkout
切换到新的 feature
分支,并且让你最近所有的工作成果都完好无损。
场景: 你在 master 分支的基础上创建了 feature
分支,但 master
分支已经滞后于 origin/master 很多。现在
master
分支已经和 origin/master 同步,你希望
在 feature
上的提交是从现在开始,而不是也从滞后很多的地方开始。
方法: git checkout feature
和 git rebase master
原理: 要达到这个效果,你本来可以通过 git reset
(不加 --hard
, 这样可以在磁盘上保留修改) 和 git checkout -b
然后再重新提交修改,不过这样做的话,你就会失去提交历史。我们有更好的办法。
git rebase master
会做如下的事情:
master 分支的共同祖先。
master
的末尾部分,并从临时保存区重新把存放的 commit 提交到 master
分支的最后一个 commit 之后。场景: 你向某个方向开始实现一个特性,但是半路你意识到另一个方案更好。你已经进行了十几次提交,但你现在只需要其中的一部分。你希望其他不需要的提交统统消失。
方法: git rebase -i
原理: -i
参数让 rebase
进入“交互模式”。它开始类似于前面讨论的 rebase,但在重新进行任何提交之前,它会暂停下来并允许你详细地修改每个提交。
rebase -i
会打开你的缺省文本编辑器,里面列出候选的提交。如下所示:
前面两列是键:第一个是选定的命令,对应第二列里的 SHA 确定的 commit。缺省情况下, rebase -i
假定每个 commit 都要通过 pick
命令被运用。
要丢弃一个 commit,只要在编辑器里删除那一行就行了。如果你不再需要项目里的那几个错误的提交,你可以删除上例中的1、3、4行。
如果你需要保留 commit 的内容,而是对 commit 消息进行编辑,你可以使用 reword
命令。 把第一列里的 pick
替换为 reword
(或者直接用 r
)。有人会觉得在这里直接重写 commit 消息就行了,但是这样不管用 —rebase -i
会忽略 SHA 列前面的任何东西。它后面的文本只是用来帮助我们记住 0835fe2
是干啥的。当你完成 rebase -i
的操作之后,你会被提示输入需要编写的任何 commit 消息。
如果你需要把两个 commit 合并到一起,你可以使用 squash
或 fixup
命令,如下所示:
squash
和 fixup
会“向上”合并 — 带有这两个命令的 commit 会被合并到它的前一个 commit 里。在这个例子里, 0835fe2
和 6943e85
会被合并成一个 commit, 38f5e4e
和 af67f82
会被合并成另一个。
如果你选择了 squash,
Git 会提示我们给新合并的 commit 一个新的 commit 消息; fixup
则会把合并清单里第一个 commit 的消息直接给新合并的 commit 。 这里,你知道 af67f82
是一个“完了完了….” 的 commit,所以你会留着 38f5e4e
as的 commit 消息,但你会给合并了 0835fe2
和 6943e85
的新 commit 编写一个新的消息。
在你保存并退出编辑器的时候,Git 会按从顶部到底部的顺序运用你的 commit。你可以通过在保存前修改 commit 顺序来改变运用的顺序。如果你愿意,你也可以通过如下安排把 af67f82
和 0835fe2
合并到一起:
场景: 你在一个更早期的 commit 里忘记了加入一个文件,如果更早的 commit 能包含这个忘记的文件就太棒了。你还没有 push,但这个 commit 不是最近的,所以你没法用 commit --amend
.
方法: git commit --squash
和 git rebase --autosquash -i
原理: git commit --squash
会创建一个新的 commit ,它带有一个 commit 消息,类似于 squash! Earlier commit
。 (你也可以手工创建一个带有类似 commit 消息的 commit,但是 commit --squash
可以帮你省下输入的工作。)
如果你不想被提示为新合并的 commit 输入一条新的 commit 消息,你也可以利用 git commit --fixup
。在这个情况下,你很可能会用commit --fixup
,因为你只是希望在 rebase
的时候使用早期 commit 的 commit 消息。
rebase --autosquash -i
会激活一个交互式的 rebase
编辑器,但是编辑器打开的时候,在 commit 清单里任何 squash!
和 fixup!
的 commit 都已经配对到目标 commit 上了,如下所示:
在使用 --squash
和 --fixup
的时候,你可能不记得想要修正的 commit 的 SHA 了— 只记得它是前面第 1 个或第 5 个 commit。你会发现 Git 的 ^
和 ~ 操作符特别好用。
HEAD^
是 HEAD
的前一个 commit。 HEAD~4
是 HEAD
往前第 4 个 – 或者一起算,倒数第 5 个 commit。
场景: 你偶然把 application.log
加到代码库里了,现在每次你运行应用,Git 都会报告在 application.log
里有未提交的修改。你把 *.log
in 放到了 .gitignore
文件里,可文件还是在代码库里 — 你怎么才能告诉 Git “撤销” 对这个文件的追踪呢?
方法: git rm --cached application.log
原理: 虽然 .gitignore
会阻止 Git 追踪文件的修改,甚至不关注文件是否存在,但这只是针对那些以前从来没有追踪过的文件。一旦有个文件被加入并提交了,Git 就会持续关注该文件的改变。类似地,如果你利用 git add -f
来强制或覆盖了 .gitignore
, Git 还会持续追踪改变的情况。之后你就不必用-f
来添加这个文件了。
如果你希望从 Git 的追踪对象中删除那个本应忽略的文件, git rm --cached
会从追踪对象中删除它,但让文件在磁盘上保持原封不动。因为现在它已经被忽略了,你在 git status
里就不会再看见这个文件,也不会再偶然提交该文件的修改了。
原文来自https://www.cnblogs.com/allencelee/p/5603914.html
1、使用方法及其作用
git cherry-pick可以选择某一个分支中的一个或几个commit(s)来进行操作(操作的对象是commit)。例如,假设我们有个稳定版本的分支,叫v2.0,另外还有个开发版本的分支v3.0,我们不能直接把两个分支合并,这样会导致稳定版本混乱,但是又想增加一个v3.0中的功能到v2.0中,这里就可以使用cherry-pick了。
就是对已经存在的commit 进行 再次提交;
使用方法如下:
git cherry-pick
查询commit id 的查询可以使用git log查询(查询版本的历史),最简单的语法如下:
git log
详细的git log 语法如下:
git log [
主要参数选项如下:
-p:按补丁显示每个更新间的差异
--stat:显示每次更新的修改文件的统计信息
--shortstat:只显示--stat中最后的行数添加修改删除统计
--name-only:尽在已修改的提交信息后显示文件清单
--name-status:显示新增、修改和删除的文件清单
--abbrev-commit:仅显示SHA-1的前几个字符,而非所有的40个字符
--relative-date:使用较短的相对时间显示(例如:"two weeks ago")
--graph:显示ASCII图形表示的分支合并历史
--pretty:使用其他格式显示历史提交信息
结果大概如下:
commit 0771a0c107dbf4c96806d22bbc6ef4c58dfe7075 Author: zhengcanruiDate: Mon Aug 8 14:41:54 2016 +0800 [modify] [what] commit的备注信息
其中0771a0c107dbf4c96806d22bbc6ef4c58dfe7075就是我们的commit id
注意:当执行完 cherry-pick 以后,将会 生成一个新的提交;这个新的提交的哈希值和原来的不同,但标识名 一样;(commit id会变)
2、实践
首先切换到你要添加commit的分支,如:你要将A分支上面的commit添加到B分支上面,我们可以要先切换到B分支上面。(注意:cherry-pick是一个本地的操作,假如你pull代码之后有人在A分支上有了新的commit,需要你先pull代码在进行cherry-pick,原因及其错误提示请见最后)。
git checkout B
将0771a0c107dbf4c96806d22bbc6ef4c58dfe7075这个commit(提交)合并到B分支上面。正常情况下,可以给出全部的commit id,也可以只给出前面的一段,只要你提交中没有这一段重复的就好,剩下的部分git会帮你填充。
git cherry-pick 0771a0c107dbf4c #将上面的commit id为0771a0c107dbf4c96806d22bbc6ef4c58dfe7075的提交添加到B分支上面
1.成功的情况
顺利的话,出现下面的情况证明你已经成功了
Finished one cherry-pick. # On branch B # Your branch is ahead of 'origin/B' by 1 commits.
2.有冲突的情况
下面是有文件冲突,和15a2b6c61927e5aed6718de89ad9dafba939a90b这个提交冲突
Automatic cherry-pick failed. After resolving the conflicts, mark the corrected paths with 'git add' or 'git rm ' and commit the result with: git commit -c 15a2b6c61927e5aed6718de89ad9dafba939a90b
解决的冲突的方法也和普通的一样,手工检查。
1)查看冲突的文件
使用git status
both modified: app/models/user.rb
2)打开上面的那个文件,解决冲突。 执行add命令、执行commit命令,最后在提交即可了
3、遇到的一些错误
使用下面cherry-pick命令执行某个commit (编号为:77c6905dcf7f946cff594a69a33d12e22bedfae4)
git cherry-pick 77c6905dcf7f946cff594a69a33d12e22bedfae4
出现了如下的错误:
fatal: bad object 77c6905dcf7f946cff594a69a33d12e22bedfae4
场景及出现错误的原因:
我的情况是在B分支的同步A分支的一个commit,出现了如标题的错误。我是直接在web上看到A分支新提交的这个commit,然后我直接在本地的B分支中进行git cherry-pick xxx。就出现了这个问题。经过尝试问题是出在了我没有切到B分支pull一下。总结过来就是git cherry-pick是本地特性,本地要有这个commit才可以被git cherry-pick。
原文来自https://www.cnblogs.com/0201zcr/p/5752771.html
用git命令,想看到自己的操作记录,则可以使用log与reflog,它两个的区别如下:
1.git log 命令可以显示所有提交过的版本信息
例如
如果感觉太繁琐,可以加上参数 --pretty=oneline,只会显示版本号和提交时的备注信息
2.git reflog 可以查看所有分支的所有操作记录(包括已经被删除的 commit 记录和 reset 的操作)
例如执行 git reset --hard HEAD~1,退回到上一个版本,用git log则是看不出来被删除的commitid,用git reflog则可以看到被删除的commitid,我们就可以买后悔药,恢复到被删除的那个版本。
原文:https://blog.csdn.net/u013252047/article/details/80230781
阿里云学生机1年114元限时活动(24岁以下都可以购买)https://promotion.aliyun.com/ntms/act/campus2018.html?userCode=a6violqw阿里云1888元红包:https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=a6violqw