前言
相信大家都知道任何版本控制系統的一個最有的用特性就是“撤銷 (undo)”你的錯誤操作的能力。在 Git 里,“撤銷” 蘊含了不少略有差別的功能。當你進行一次新的提交的時候,Git 會保存你代碼庫在那個特定時間點的快照;之后,你可以利用 Git 返回到你的項目的一個早期版本。
撤銷一個“已公開”的改變
場景: 你已經執行了 git push
, 把你的修改發送到了 GitHub,現在你意識到這些 commit 的其中一個是有問題的,你需要撤銷那一個 commit.
方法: git revert <SHA>
原理: git revert
會產生一個新的 commit,它和指定 SHA 對應的 commit 是相反的(或者說是反轉的)。如果原先的 commit 是“物質”,新的 commit 就是“反物質” ― 任何從原先的 commit 里刪除的內容會在新的 commit 里被加回去,任何在原先的 commit 里加入的內容會在新的 commit 里被刪除。
這是 Git 最安全、最基本的撤銷場景,因為它并不會改變歷史 ― 所以你現在可以 git push 新的“反轉” commit 來抵消你錯誤提交的 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 -- <bad filename>
原理: git checkout
會把工作目錄里的文件修改到 Git 之前記錄的某個狀態。你可以提供一個你想返回的分支名或特定 SHA ,或者在缺省情況下,Git 會認為你希望 checkout 的是 HEAD,當前 checkout 分支的最后一次 commit。
記住:你用這種方法“撤銷”的任何修改真的會完全消失。因為它們從來沒有被提交過,所以之后 Git 也無法幫助我們恢復它們。你要確保自己了解你在這個操作里扔掉的東西是什么!(也許可以先利用 git diff 確認一下)
重置“本地的”修改
場景: 你在本地提交了一些東西(還沒有 push),但是所有這些東西都很糟糕,你希望撤銷前面的三次提交 ― 就像它們從來沒有發生過一樣。
方法: git reset <last good SHA>
或 git reset --hard <last good SHA>
原理: 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 -- <bad filename>
撤銷時(正如我們在前面講到的情況),HEAD 并不會改變 ― 如前所述,這些修改從來沒有被提交過,因此 reflog 也無法幫助我們恢復它們。
git reflog
不會永遠保持。Git 會定期清理那些 “用不到的” 對象。不要指望幾個月前的提交還一直躺在那里。
你的 reflog 就是你的,只是你的。你不能用 git reflog
來恢復另一個開發者沒有 push 過的 commit。
reflog
那么…你怎么利用 reflog 來“恢復”之前“撤銷”的 commit 呢?
它取決于你想做到的到底是什么:
如果你希望準確地恢復項目的歷史到某個時間點,用 git reset --hard <SHA>
如果你希望重建工作目錄里的一個或多個文件,讓它們恢復到某個時間點的狀態,用 git checkout <SHA> -- <filename>
如果你希望把這些 commit 里的某一個重新提交到你的代碼庫里,用 git cherry-pick <SHA>
利用分支的另一種做法
場景: 你進行了一些提交,然后意識到你開始 check out 的是 master 分支。你希望這些提交進到另一個特性(feature)分支里。
方法: git branch feature
, git reset --hard origin/master
, and git checkout feature
原理: 你可能習慣了用 git checkout -b <name>
創建新的分支 ― 這是創建新分支并馬上 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 很多?,F在 master 分支已經和 origin/master 同步,你希望在 feature 上的提交是從現在開始,而不是也從滯后很多的地方開始。
方法: git checkout feature
和 git rebase master
原理: 要達到這個效果,你本來可以通過 git reset
(不加 --hard, 這樣可以在磁盤上保留修改) 和 git checkout -b <new branch name> 然后再重新提交修改,不過這樣做的話,你就會失去提交歷史。我們有更好的辦法。
git rebase master
會做如下的事情:
首先它會找到你當前 check out 的分支和 master 分支的共同祖先。
然后它 reset 當前 check out 的分支到那個共同祖先,在一個臨時保存區存放所有之前的提交。
然后它把當前 check out 的分支提到 master 的末尾部分,并從臨時保存區重新把存放的 commit 提交到 master 分支的最后一個 commit 之后。
大量的撤銷/恢復
場景: 你向某個方向開始實現一個特性,但是半路你意識到另一個方案更好。你已經進行了十幾次提交,但你現在只需要其中的一部分。你希望其他不需要的提交統統消失。
方法: git rebase -i <earlier SHA>
原理: -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 里忘記了加入一個文件,如果更早的 commit 能包含這個忘記的文件就太棒了。你還沒有 push,但這個 commit 不是最近的,所以你沒法用 commit --amend.
方法: git commit --squash <SHA of the earlier commit>
和 git rebase --autosquash -i <even earlier SHA>
原理: 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 個 亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
亚洲免费小视频| 综合网日日天干夜夜久久| 日韩高清不卡av| 最近中文字幕2019免费| 97在线视频观看| www.欧美精品一二三区| 欧美成人免费网| 日本欧美一二三区| 国产精品欧美在线| 久久天天躁夜夜躁狠狠躁2022| 久久久精品久久久| 欧美中文字幕视频在线观看| 日韩一区二区三区在线播放| 国产精品老牛影院在线观看| 国产小视频国产精品| 国产免费一区视频观看免费| 亚洲图片欧洲图片av| 欧美性视频网站| 欧美理论片在线观看| 国产ts一区二区| 久久综合免费视频| 亚洲最大成人免费视频| 国产精品日韩在线播放| 国产精品丝袜久久久久久不卡| 国产欧美精品va在线观看| 久久久www成人免费精品| 国产综合色香蕉精品| 亚洲午夜色婷婷在线| 最近中文字幕日韩精品| 日本久久精品视频| 亚洲国产精品电影| 57pao国产成人免费| 成人免费淫片视频软件| 茄子视频成人在线| 91精品视频在线播放| 色综合久久中文字幕综合网小说| 亚洲一区第一页| 7m第一福利500精品视频| 欧美在线视频观看| 精品国产999| 国产精品久久一| 美女久久久久久久| xvideos亚洲人网站| www.午夜精品| 欧美日韩精品在线| 在线观看日韩视频| 亚洲精品电影网| 日韩中文视频免费在线观看| 色婷婷av一区二区三区久久| 久久男人资源视频| 日韩在线观看免费网站| 午夜精品福利在线观看| 午夜欧美不卡精品aaaaa| 91精品国产成人| 国产精品视频在线播放| 欧美一区二区三区图| 成人字幕网zmw| 精品欧美激情精品一区| 国产精品久久久久秋霞鲁丝| 欧美一级高清免费播放| 日韩在线观看免费网站| 亚洲欧美在线磁力| 在线观看免费高清视频97| 久久影院资源网| 日本一区二三区好的精华液| 日韩欧美在线观看| 久久久久久国产精品三级玉女聊斋| 久久久女人电视剧免费播放下载| 成人黄色av播放免费| 亚洲午夜未删减在线观看| 97国产精品视频人人做人人爱| 91精品国产综合久久男男| 国产精品电影观看| 成人国产精品色哟哟| 欧美另类极品videosbestfree| 孩xxxx性bbbb欧美| 国产一区二区三区在线播放免费观看| 成人乱色短篇合集| 日本电影亚洲天堂| 日韩一区二区三区xxxx| 日本久久中文字幕| 日韩免费电影在线观看| 亚洲精品动漫久久久久| 欧美黑人巨大精品一区二区| 久久噜噜噜精品国产亚洲综合| 亚洲桃花岛网站| 日韩av一区在线观看| 97色在线视频| 国产视频欧美视频| 高清日韩电视剧大全免费播放在线观看| 精品国产精品三级精品av网址| 欧美日韩激情美女| 欧美成人一区二区三区电影| 51久久精品夜色国产麻豆| 午夜精品一区二区三区在线视| 欧美成人小视频| 国产精品aaaa| 欧美成人久久久| 日韩在线免费av| 38少妇精品导航| 在线视频一区二区| 国产精品网红福利| 国产丝袜高跟一区| 国产精品久久久久久av下载红粉| 欧美午夜激情视频| 美女精品久久久| 国产专区欧美专区| 亚洲国产精品成人av| 亚洲午夜小视频| 成人性生交大片免费观看嘿嘿视频| 亚洲资源在线看| 国产美女高潮久久白浆| 精品久久久久久久中文字幕| 成人免费看黄网站| www.国产精品一二区| 久久久久久999| 亚洲精品国精品久久99热| 98精品国产高清在线xxxx天堂| 国产精品一区二区三区在线播放| 日韩av片永久免费网站| 欧美一区三区三区高中清蜜桃| 欧美老女人性生活| 久久亚洲国产精品成人av秋霞| 国产精品情侣自拍| 亚洲天堂免费观看| 久久久精品美女| 国产欧美一区二区白浆黑人| 91精品在线播放| 日韩a**站在线观看| 中文字幕不卡在线视频极品| 亚洲一区中文字幕在线观看| 欧美激情精品久久久久久大尺度| 亚洲精品电影在线| 色播久久人人爽人人爽人人片视av| 中文字幕亚洲国产| 国产欧美日韩中文字幕在线| 亚洲视频网站在线观看| 国产热re99久久6国产精品| 日韩精品在线看| 国产精品久久久久久久久久东京| 久久久久999| 狠狠久久五月精品中文字幕| 日韩精品在线免费| 中文字幕亚洲欧美日韩2019| 91精品视频在线| 欧美视频第一页| 久久91亚洲人成电影网站| 亚洲精品91美女久久久久久久| 亚洲自拍偷拍色片视频| 精品国产乱码久久久久久天美| 国产精品欧美久久久| 亚洲日本成人女熟在线观看| 国产91色在线|| 97在线视频国产| 日本a级片电影一区二区| 亚洲裸体xxxx| 国产精品小说在线| 欧美性感美女h网站在线观看免费| 2018国产精品视频| 欧美一级免费看| 亚洲欧美一区二区三区四区| 热门国产精品亚洲第一区在线| 久久精品视频在线播放| 日韩av电影手机在线|