1. 程式人生 > >Git快速入門-git stash 暫存變更,git reset 撤銷commit,git revert 回退遠端版本庫

Git快速入門-git stash 暫存變更,git reset 撤銷commit,git revert 回退遠端版本庫

注:本文基於git version 2.13.5 版本進行演示

1. 概述

Git入門系列第四篇,基於場景,介紹一些非常實用的Git命令,也許這些命令會讓你產生“還有這種操作”的感嘆。例如如何把修改暫存起來,留著以後使用?想撤銷已提交(commit)到本地版本庫的程式碼該怎麼辦?撤銷已push到遠端程式碼庫的程式碼呢?

2. 如何把修改暫存起來,留著以後使用?

2.1 使用場景

實際開發過程中,我們經常會遇到這樣的問題,當我們想把遠端倉庫的最新程式碼拉下來時,發現git會提示說我們本地做的修改會被覆蓋,建議先commit程式碼或者stash(暫存)程式碼。你一定遇到過類似下面這樣的提示:

$ git pull origin master
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 1), reused 3 (delta 1), pack-reused 0
Unpacking objects: 100% (4/4), done.
From github.com:flysqrlboy/git-command-tutorials
 * branch            master     -> FETCH_HEAD
   7
dd2e09..d7e1e19 master -> origin/master Updating 7dd2e09..d7e1e19 error: Your local changes to the following files would be overwritten by merge: b.txt Please commit your changes or stash them before you merge. Aborting

留意下面兩句提示,我們可以有兩個選擇:commit或stash。

error: Your local changes to the following files would be overwritten by merge:
b.txt
Please commit your changes or stash them before you merge.

如果我們本地的程式碼修改得差不多了,可以選擇commit到本地版本庫。但如果我們的修改只是個半成品,我們不想把這樣的程式碼留在提交歷史裡的話。git stash就能派上用場了。

2.2 git stash 暫存進度

先來看下當前的工作區和暫存區狀態:

$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   a.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   b.txt

git status 輸出中告訴我們,a.txt 和 b.txt 都有修改,a.txt 已加入到暫存區(在Changes to be committed下面),b.txt 的修改則還在工作區中(在Changes not staged for commit下面)。正是因為遠端程式碼庫有人更改了b.txt ,才導致本地拉取程式碼時提示會被覆蓋。這裡,用git stash 命令儲存當前工作進度。

$ git stash
Saved working directory and index state WIP on master: 7dd2e09 add two files a.txt b.txt

執行git stash 之後,再檢視工作區狀態,會發現之前工作區和暫存區的修改都不見了。

$ git status
On branch master
nothing to commit, working tree clean

2.3 檢視進度

檢視stash進度用命令git stash list

$ git stash list
[email protected]{0}: WIP on master: 7dd2e09 add two files a.txt b.txt

可以看到剛剛暫存的進度有個標識 [email protected]{0}。如果想檢視某個進度具體修改的檔案可以用命令git stash show

$ git stash show stash@{0}
 a.txt | 1 +
 b.txt | 1 +
 2 files changed, 2 insertions(+)

2.4 恢復進度

使用 git stash pop 從最近儲存的進度進行恢復。

$ git stash pop
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

modified:   a.txt
modified:   b.txt

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/[email protected]{0} (ddc97ea74d33f3417f5ddab429a1dfeb3c08ca19)

通過git status檢視工作區狀態,可以看到之前的修改又回來了。

$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   a.txt
    modified:   b.txt

no changes added to commit (use "git add" and/or "git commit -a")

不過可能你也發現了一個小瑕疵,原來的a.txt 的修改已經新增到暫存區的,但現在用git stash pop 恢復進度後,a.txt 的修改卻還在工作區,暫存區的修改被撤銷了。這裡其實可以在執行git stash pop命令時帶上一個 –index 的選項來告訴git重新應用被暫存的變更。

git stash pop --index
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   a.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   b.txt

Dropped refs/[email protected]{0} (c62afccafe9aaec2b44abe85b4206728479b9902)
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   a.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   b.txt

3. 如何撤銷工作區的修改?

如果覺得對b.txt這個檔案的修改是沒有必要的,該如何撤消修改,回到之前的狀態(也就是回到沒有修改前的狀態)?git status 命令輸出是有告訴我們怎麼做的:

$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   a.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   b.txt

就在“Changes not staged for commit”下面第二個括號內,

(use “git checkout – < file >…” to discard changes in working directory)

git checkout – filename 可以丟棄某個檔案在工作區的改動。我們試試看。

$ git checkout -- b.txt
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   a.txt

git status 已經沒有b.txt 的資訊,說明b.txt的修改已經撤出工作區,恢復到修改前的版本了。這裡提醒一下:git checkout – filename 這個命令是有點危險的,因為它會丟棄掉之前做的改動,這是找不回來的。只有在確定某個檔案是真的不需要改動才撤銷。一般情況下,如果只是想回到沒修改前的版本,但仍然想保留修改的內容,可以用git stash命令把改動暫存起來。

4. 如何把暫存區的修改撤回到工作區

如果我們不小心把一些還不想提交的修改新增到了暫存區(例如不小心用了 git add . 命令把所有改動都add 到暫存區),我們怎麼把某些檔案撤回工作區呢?實際上git status命令也有告訴我們。

$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   a.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   b.txt

在 “Changes to be committed” 下面,

(use “git reset HEAD < file >…” to unstage)

用 git reset HEAD filename 命令把暫存區的改動撤回到工作區,我們試試。

$ git reset HEAD a.txt
Unstaged changes after reset:
M   a.txt
M   b.txt

$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   a.txt
    modified:   b.txt

no changes added to commit (use "git add" and/or "git commit -a")

git status 輸出可以看到,a.txt 的改動已經被撤出暫存區了。

5. 如何把最近的一次commit撤回到暫存區

如果我們對最近的一次commit感到不滿意,想把它從本地版本庫撤回到暫存區,該怎麼做呢?讓我們先做一次commit:

git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   a.txt
    modified:   b.txt

$ git commit -m 'modify a.txt b.txt'
[master c80c16c] modify a.txt b.txt
 2 files changed, 2 insertions(+)

$ git log --oneline
c80c16c (HEAD -> master) modify a.txt b.txt
7dd2e09 add two files a.txt b.txt
e6e0035 1.oneline 2.second line

提交已經成功,commitid 為 c80c16c 。現在要撤銷這次提交,把改動撤回到暫存區。同樣是使用命令 git reset,只不過這次要加上 –soft 選項。

$ git reset --soft HEAD^
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   a.txt
    modified:   b.txt

git status 輸出告訴我們,a.txt 和 b.txt的改動又回到暫存區了。再檢視提交歷史。

$ git log --oneline
7dd2e09 (HEAD -> master) add two files a.txt b.txt
e6e0035 1.oneline 2.second line

commitid 為 c80c16c 的那次提交已經沒有了。現在來解釋下 git reset –soft HEAD^ 的含義。先說一下”HEAD^”,它代表最新的一次提交的前一次提交。“HEAD”在Git中就是一個引用,它總是指向(當前分支中)最新的那一次提交。所以上面的命令意思是把頭部引用(HEAD)向前回退一次。而選項–soft 的作用就是把最新那次提交的所有改動撤回到暫存區。

6. 如何回退已經push到遠端版本庫的提交

上面我們討論的只是撤銷本地做的提交,那如果提交已經push到遠端程式碼庫。要怎麼回退呢?我們先把上面對a.txt , b.txt的修改push到遠端程式碼庫。

$ git push origin master
Counting objects: 4, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (4/4), 373 bytes | 373.00 KiB/s, done.
Total 4 (delta 0), reused 0 (delta 0)
To github.com:flysqrlboy/git-command-tutorials.git
   d7e1e19..f0e0628  master -> master

已push成功,看下提交歷史

$ git log --oneline
f0e0628 (HEAD -> master, origin/master) modify a.txt b.txt
d7e1e19 Merge pull request #1 from imflysquirrel/master
0c550df modify b.txt by imflysquirrel
7dd2e09 add two files a.txt b.txt

commitid 為 f0e0628的這次提交已經push到遠端。現在想回退遠端的這次提交,可能你會想到,先在本地用git reset命令撤銷f0e0628 ,再push到遠端,這種方案可行嗎?試試看:

$ git reset --soft HEAD^
$ git log --oneline
d7e1e19 (HEAD -> master) Merge pull request #1 from imflysquirrel/master
0c550df modify b.txt by imflysquirrel
7dd2e09 add two files a.txt b.txt

執行git reset後本地已經回退到上一次提交,這時我們使用git push推送到遠端看看能否成功。

$ git push origin master
To github.com:flysqrlboy/git-command-tutorials.git
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to '[email protected]:flysqrlboy/git-command-tutorials.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

推送失敗了,reject的原因是說本地當前分支落後於遠端程式碼庫,如下

hint: Updates were rejected because the tip of your current branch is behind

因為我們本地的HEAD引用當前指向的提交是 d7e1e19,而遠端的HEAD指向的是f0e0628。d7e1e19落後於f0e0628。其實不應該用git reset來回退遠端倉庫的提交,取而代之的是用git revert。git revert 這個命令也會建立一次提交,只不過這個提交相當於被回退的那次提交的一個反向提交。比如在f0e0628 這次提交提交中,b.txt增加了一行“Hello World!”,git diff 如下

diff --git a/b.txt b/b.txt
index 696ac20..0f47c73 100644
--- a/b.txt
+++ b/b.txt
@@ -1 +1,2 @@
 add by imflysquirrel
+Hello World!

那麼反向提交的話就會刪掉這行“Hello World!”, 下面用git revert 演示下。

$ git revert HEAD
[master 9086b68] Revert "modify a.txt b.txt"
 2 files changed, 2 deletions(-)

git revert HEAD 表示revert HEAD指向的那次提交,也就是最新的那一次提交f0e0628。用git log看下提交歷史:

git log --oneline
9086b68 (HEAD -> master) Revert "modify a.txt b.txt"
f0e0628 (origin/master) modify a.txt b.txt
d7e1e19 Merge pull request #1 from imflysquirrel/master
0c550df modify b.txt by imflysquirrel
7dd2e09 add two files a.txt b.txt

新增了一個提交9086b68 ,原來的f0e0628 是還存在的。看下這時的b.txt,

$ cat b.txt
add by imflysquirrel

“Hello World!” 那行已經被刪除掉了。那麼現在可以push了。

$ git push origin master
Counting objects: 3, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 316 bytes | 316.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To github.com:flysqrlboy/git-command-tutorials.git
   f0e0628..9086b68  master -> master

Ok! 成功push 到遠端。

7. 小結

本文介紹了幾個使用的git 命令。git stash 暫存程式碼。git reset 撤銷本地提交。git revert 回退遠端程式碼庫。希望對你在使用git時有所幫助。如果覺得本文寫的還行,請點個贊吧!也歡迎在討論區留言做進一步交流。