1. 程式人生 > >版本控制工具——Git常用操作(下)

版本控制工具——Git常用操作(下)

之前 將在 根據 並且 fsck args git工作區 with 索引

本文由雲+社區發表

作者:工程師小熊

摘要:上一集我們一起入門學習了git的基本概念和git常用的操作,包括提交和同步代碼、使用分支、出現代碼沖突的解決辦法、緊急保存現場和恢復現場的操作。學會以後已經足夠我們使用Git參加協作開發了,但是在開發的過程中難免會出錯,本文主要介紹版本控制的過程中出錯了的場景,以及Git開發的一些技巧,讓我們用的更流暢。

上集回顧:

  • Git的基本概念
  • 一個人使用Git時的代碼版本控制--(提交、拉代碼、分支操作)
  • 多人合作時的代碼版本控制--(合並沖突、暫存代碼)

本文核心:

  • 後悔藥-各種後悔操作(撤消commit,回滾,回退遠程倉庫等)
  • 哎呀,提交的時候漏了文件
  • tag操作
  • git忽略不想提交的文件

後悔藥

撤消當前commit

如果你發現剛剛的操作一不小心commit了,所幸你還沒有推送到遠程倉庫,你可以用reset命令來撤消你的這次提交。

reset命令的作用:重置HEAD(當前分支的版本頂端)到另外一個commit。

我們的撤消當前提交的時候往往不希望我們此次提交的代碼發生任何丟失,只是撤消掉commit的操作,以便我們繼續修改文件。如果我們是想直接不要了這次commit的全部內容的任何修改我們將在下一小節討論。

來,我們先說一句蠢話來diss老板

$ touch to_boss.txt

$ echo 'my boss is a bad guy!' > to_boss.txt

$ git add to_boss.txt

$ git status
On branch master
Your branch is up to date with 'origin/master'.

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

    new file:   to_boss.txt

$ git commit -m "[+]罵了我的boss"
[master 3d113a7] [+]罵了我的boss
 1 file changed, 1 insertion(+)
 create mode 100644 to_boss.txt
  • 創建to_boss.txt文件,並向其寫入了my boss is a bad guy!
  • add然後status查看新文件已經加入跟蹤
  • commit提交了這次的修改

好了,剛剛我們“不小心”diss了我們的老板,要是被發現就完了,所幸還沒有push,要快點撤消這些提交,再換成一些好話才行。

我們使用以下命令:

$ git reset --soft head^

$ git status
On branch master
Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)

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

    new file:   to_boss.txt

$ cat to_boss.txt
my boss is a bad guy!

$ echo 'my boss is a good boy!'
my boss is a good boy!

$ echo 'my boss is a good boy!' > to_boss.txt

$ cat to_boss.txt
my boss is a good boy!

$ git add to_boss.txt

$ git status
On branch master
Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)

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

    new file:   to_boss.txt
    
$ git commit -m "[*]誇了我的boss"
[master 8be46aa] [*]誇了我的boss
 1 file changed, 1 insertion(+)
 create mode 100644 to_boss.txt
  • git reset --soft head^撤消了本次提交,將工作區恢復到了提交前但是已經add的狀態
  • to_boss.txt的內容改成了my boss is a good boy!
  • add然後commit提交

好了,有驚無險,這就是撤消commit的操作。另一種情況是如果你想撤消commit的時候支持舍棄這次全部的修改就把git reset --soft head^改成git reset --hard head^,這樣你本地修改就徹底丟掉了(慎用),如果真用了想找回來怎麽辦?見救命的後悔藥。

當然了,你只要開心不加softhard參數也是安全的(相當於使用了--mixed參數),只不過是撤消以後你的本次修改就會回到add之前的狀態,你可以重新檢視然後再做修改和commit

回退遠程倉庫

要是我們做的更過分一點,直接把這次commit直接給push怎麽辦?要是被發現就全完了,我們來看看github上的遠程倉庫。

技術分享圖片upload successful

完了,真的提交了(我剛剛push的)讓我們冷靜下來,用撤消當前commit的方法先撤消本地的commit,這次我們來試試用hard參數來撤消

$ git reset --hard head^
HEAD is now at 3f22a06 [+]add file time.txt

$ git status
On branch master
Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)

nothing to commit, working tree clean

$ git push origin master --force
Total 0 (delta 0), reused 0 (delta 0)
To github.com:pzqu/git_test.git
 + 3d113a7...3f22a06 master -> master (forced update)
  • 使用git reset --hard head^回滾到上一個commit
  • 使用git status查看現在的工作區情況,提示Your branch is behind ‘origin/master‘ by 1 commit,代表成功表了上一次的提示狀態,nothing to commit, working tree clean代表這次的修改全沒了,清理的算是一個徹底。如果還想找回來怎麽辦,我們還真是有辦法讓你找回來的,見救命的後悔藥。
  • git push origin master --force 命令強制提交到遠程倉庫(註意,如果是在團隊合作的情況下,不到迫不得已不要給命令加--force參數) 讓我們看看github

技術分享圖片upload successful

真的撤消了遠程倉庫,長舒一口氣。

暫存區(Stage)到工作區(Working Directory)

如果我們剛剛執行了git reset --soft或者add等的操作,把一些東西加到了我們的暫存區,比如日誌文件,我們就要把他們從暫存區拿出來。

$ git status
On branch master
Your branch is up to date with 'origin/master'.

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

    new file:   mysql.log
    
$ git reset -- mysql.log

$ git status
On branch master
Your branch is up to date with 'origin/master'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    mysql.log

nothing added to commit but untracked files present (use "git add" to track)
  • status查看暫存區,裏面有一個mysql.log被放進去了
  • git reset -- mysql.logmysql.log取出來
  • status可以看到真的取出來了 然後如果不要想這個文件的話再rm掉就好啦,但是如果這些文件每次自動生成都要用這種方式取出暫存區真的好累,我們可以用 git忽略不想提交的文件

回滾文件到某個提交

當我們想要把某個文件任意的回滾到某次提交上,而不改變其他文件的狀態我們要怎麽做呢?

我們有兩種情況,一種是,只是想在工作區有修改的文件,直接丟棄掉他現在的修改;第二種是想把這個文件回滾到以前的某一次提交。我們先來說第一種:

取消文件在工作區的修改

$ cat time.txt
10:41

$ echo 18:51 > time.txt

$ git status
On branch master
Your branch is up to date with 'origin/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:   time.txt

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

$ cat time.txt
18:51

$ git checkout -- time.txt

$ cat time.txt
10:41
  • 更新time.txt的內容,可以status看到他發生了變化
  • git checkout -- time.txt , 取消這次在工作區的修改,如果他已經被add加到了暫存區,那麽這個命令就沒有用了,他的意思是取消本次在工作區的修改,去上一次保存的地方。如果沒有add就回到和版本庫一樣的狀態;如果已經加到了暫存區,又做了修改,那麽就回加到暫存區後的狀態將文件回滾到任意的版本我們這裏說的把文件回滾到以前的某個版本的狀態,完整的含義是保持其他文件的內容不變,改變這個文件到以前的某個版本,然後修改到自己滿意的樣子和做下一次的提交。

核心命令

git checkout [<options>] [<branch>] -- <file>...

我們還是用time.txt這個文件來做試驗,先搞三個版本出來,在這裏我已經搞好了,來看看:

版本1,time.txt內容00:50

commit 35b66ed8e3ae2c63cc4ebf323831e3b917d2b1d4 (HEAD -> master, origin/master, origin/HEAD)
Author: pzqu <[email protected]>
Date:   Sun Dec 23 00:51:54 2018 +0800
    [*]update time to 00:50

版本2,time.txt內容18:51

commit 856a74084bbf9b678467b2615b6c1f6bd686ecff
Author: pzqu <[email protected]>
Date:   Sat Dec 22 19:39:19 2018 +0800
    [*]update time to 18:51

版本3,time.txt內容10:41

commit 3f22a0639f8d79bd4e329442f181342465dbf0b6
Author: pzqu <[email protected]>
Date:   Tue Dec 18 10:42:29 2018 +0800
    [+]add file time.txt

現在的是版本1,我們把版本3檢出試試。

$ git checkout 3f22a0639f8d -- time.txt

$ cat time.txt
10:41

$ git status
On branch master
Your branch is up to date with 'origin/master'.

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

    modified:   time.txt
  • 使用checkout+commit id+-- filename的組合,橫跨版本2把歷史版本3的time.txt搞出來了
  • 查看狀態,time.txt被改變了

我們來把time.txt恢復到版本1,同樣的方法,因為版本1是上一次提交我們可以省略掉版本號

$ git checkout -- time.txt

$ cat time.txt
00:50

看到了吧!只要用git checkout commit_id -- filename的組合,想搞出哪個文件歷史版本就搞出哪個。

到了這裏,你可能會很懵比,resetcheckout命令真的好像啊!都可以用來做撤消

  • checkout語義上是把什麽東西取出來,所以此命令用於從歷史提交(或者暫存區域)中拷貝文件到工作目錄,也可用於切換分支。
  • reset語義上是重新設置,所以此命令把當前分支指向另一個位置,並且有選擇的變動工作目錄和索引。也用來在從歷史倉庫中復制文件到索引,而不動工作目錄。

還想不通可以給我發郵件:[email protected]

救命的後悔藥

來到這裏我已經很清楚的你的現況了,你的代碼丟了現在一定非常的著急,不要慌,總是有辦法找回他們的。但是前提是要保證你的項目根目錄下.git文件夾是完整的,要是手動刪除了裏面的一些東西那就真完了。還要保證一點,你的代碼以前是有過git追蹤的,最少add

找回你丟失的歷史記錄

Git提供了一個命令git reflog用來記錄你的每一次命令,貼個圖吧直觀點:

技術分享圖片upload successful

  • 有沒有發現,git reflog裏的全部都是和改變目錄樹有關的,比如commit rebase reset merge,也就是說一定要有改變目錄樹的操作才恢復的回來
  • 像add這種操作就不能恢復了嗎?那肯定不是,只是要用更麻煩點的方式來恢復
  • git log是一樣的,也可以看到所有分支的歷史提交,不一樣的是看不到已經被刪除的 commit 記錄和 reset rebase merge 的操作 我們可以看到git reflog前面的就是commit id,現在我們就可以用之前介紹過的方法來回滾版本了,撤消當前commit
$ git reset --hard 856a740
HEAD is now at 856a740 [*]update time to 18:51

$ git log -1
commit 856a74084bbf9b678467b2615b6c1f6bd686ecff (HEAD -> master)
Author: pzqu <[email protected]>
Date:   Sat Dec 22 19:39:19 2018 +0800

    [*]update time to 18:51
 
$ git reset --hard 35b66ed
HEAD is now at 35b66ed [*]update time to 00:50

$ git log -2
commit 35b66ed8e3ae2c63cc4ebf323831e3b917d2b1d4 (HEAD -> master, origin/master, origin/HEAD)
Author: pzqu <[email protected]>
Date:   Sun Dec 23 00:51:54 2018 +0800

    [*]update time to 00:50

commit 856a74084bbf9b678467b2615b6c1f6bd686ecff
Author: pzqu <[email protected]>
Date:   Sat Dec 22 19:39:19 2018 +0800

    [*]update time to 18:51
  • 根據git reflog返回的結果,用git reset --hard commit_id回退到856a740這個版本
  • git log -1看近一行的日誌,可以看到目前就在這了
  • 再根據git reflog的結果,用git reset --hard 35b66ed跑到這次提交
  • git log -2看到兩次提交的日誌,我們就這麽再穿梭過來了,就是這麽爽 但是我們如果只是想把此提交給找回來,恢復他,那還是不要用reset的方式,可以用cherry-pick或者merge來做合並

找回忘記提交的歷史記錄

你之前沒有commit過的文件,被刪除掉了,或者被reset --hard的時候搞沒了,這種情況可以說是相當的難搞了,所幸你以前做過add的操作把他放到過暫存區,那我們來試試找回來,先來創建一個災難現場

$ echo 'my lose message' > lose_file.txt

$ git add lose_file.txt

$ git status
On branch master
Your branch is up to date with 'origin/master'.

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

    new file:   lose_file.txt

$ git reset --hard 35b66ed8
HEAD is now at 35b66ed [*]update time to 00:50

$ git status
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

$ ls
README.md      need_stash.txt share_file.txt time.txt
  • 創建一個叫lose_file.txt的文件並寫入內容my lose message,並把他加到暫存區
  • git reset --hard 35b66ed8用丟棄一切修改的方式來使現在的工作區恢復到35b66ed8版本,因為還沒提交所以也就是恢復到當前的(head)版本。
  • 我們用statusls再看,這個叫lose_file.txt的文件真的沒了,完蛋了,第一反應用剛剛學到的命令git reflow會發現根本就不好使

核心命令:git fsck --lost-found,他會通過一些神奇的方式把歷史操作過的文件以某種算法算出來加到.git/lost-found文件夾裏

$ git fsck --lost-found
Checking object directories: 100% (256/256), done.
Checking objects: 100% (3/3), done.
dangling blob 7f5965523d2b9e850b39eb46e8e0f7c5755f6719
dangling commit fdbb19cf4c5177003ea6610afd35cda117a41109
dangling commit 8be46aa83f0fe90317b0c6b9c201ad994f8caeaf
dangling blob 11400c1d56142615deba941a7577d18f830f4d85
dangling tree 3bd4c055afedc51df0326def49cf85af15994323
dangling commit 3d113a773771c09b7c3bf34b9e974a697e04210a
dangling commit bfdc065df8adc44c8b69fa6826e75c5991e6cad0
dangling tree c96ff73cb25b57ac49666a3e1e45e0abb8913296
dangling blob d6d03143986adf15c806df227389947cf46bc6de
dangling commit 7aa21bc382cdebe6371278d1af1041028b8a2b09

這裏涉及到git的一些低層的知識,我們可以看到這裏有blob、commit、tree類型的數據,還有tag等類型的。他們是什麽含義呢?

技術分享圖片upload successful

  • blob組件並不會對文件信息進行存儲,而是對文件的內容進行記錄
  • commit組件在每次提交之後都會生成,當我們進行commit之後,首先會創建一個commit組件,之後把所有的文件信息創建一個tree組件,所以哪個blob代表什麽文件都可以在tree 裏找到 我們來看看怎麽恢復剛剛不見了的lose_file.txt文件,在上面執行完git fsck --lost-found命令,返回的第一行blob我們看看他的內容
git show 7f5965523d2b9e850b39eb46e8e0f7c5755f6719
my lose message

git show 7f5965523d2b9e850b39eb46e8e0f7c5755f6719 > lose_file.txt

$ ls
README.md      lose_file.txt  need_stash.txt share_file.txt time.txt
  • 看到沒有,就是我們丟失的文件內容,這樣就找回來了! 我們再來看看commit tree的內容$ git cat-file -p fdbb19cf4c5177003ea6610afd35cda117a41109 tree 673f696143eb74ac5e82a46ca61438b2b2d3bbf4 parent e278392ccbf4361f27dc338c854c8a03daab8c49 parent 7b54a8ae74be7192586568c6e36dc5a813ff47cf author pzqu [email protected] 1544951197 +0800 committer pzqu [email protected] 1544951197 +0800 Merge branch ‘master‘ of github.com:pzqu/git_test $ git ls-tree 3bd4c055afedc51df0326def49cf85af15994323 100644 blob c44be63b27a3ef835a0386a62ed168c91e680e87 share_file.txt
  • git cat-file -p可以看到commit的內容,可以選擇把這個commit合並到我們的分支裏,還是reset merge rebase cherry-pick這些命令來合commit
  • git ls-tree列出tree下面的文件名和id的記錄信息,然後就可以根據這些來恢復文件了

後記:

如果你發現執行git fsck --lost-found的輸出找不到你想要的,那麽在執行完git fsck --lost-found後會出現一堆文件 在 .git/lost-found 文件夾裏,我們不管他。可以用以下命令來輸出近期修改的文件

$  find .git/objects -type f | xargs ls -lt | sed 3q
-r--r--r--  1 pzqu  staff    32 12 23 12:19 .git/objects/7f/5965523d2b9e850b39eb46e8e0f7c5755f6719
-r--r--r--  1 pzqu  staff    15 12 23 01:51 .git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
-r--r--r--  1 pzqu  staff   162 12 23 00:51 .git/objects/35/b66ed8e3ae2c63cc4ebf323831e3b917d2b1d4

$ git cat-file -t 7f5965523d2b9e850b39eb46e8e0f7c5755f6719
blob

$ git cat-file -p 7f5965523d2b9e850b39eb46e8e0f7c5755f6719
my lose message

$ git cat-file -t b2484b5ab58c5cb6ecd92dacc09b41b78e9b0001
tree

$ git cat-file -p b2484b5ab58c5cb6ecd92dacc09b41b78e9b0001
100644 blob f9894f4195f4854cfc3e3c55960200adebbc3ac5    README.md
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391    need_stash.txt
100644 blob 83f50ec84c00f5935da8089bac192171cfda8621    share_file.txt
100644 blob f0664bd6a49e268d3db47c508b08d865bc25f7bb    time.txt
  • 這裏用find .git/objects -type f | xargs ls -lt | sed 3q返回了近3個修改的文件,想要更多就改3q這個數值,比如你想輸出100個就用100q
  • git cat-file -t 7f5965523d2b9e850b39eb46e8e0f7c5755f6719 就能看見文件類型 把最後一個/去掉 復制從objects/ 後面的所有東西放在-t後面
  • git cat-file -p id就能看見文件內容,是不是很爽

漏提交

有時候會碰到我們已經commit但是有修改忘記了提交,想把他們放在剛剛的commit裏面,這種時候怎麽做呢?

$ git log --name-status --pretty=oneline -1
35b66ed8e3ae2c63cc4ebf323831e3b917d2b1d4 (HEAD -> master, origin/master, origin/HEAD) [*]update time to 00:50
M       time.txt

$ git status
On branch master
Your branch is up to date with 'origin/master'.

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

    new file:   lose_file.txt
    new file:   test_amend.txt
    
$ git commit --amend --no-edit
[master 31cc277] [*]update time to 00:50
 Date: Sun Dec 23 00:51:54 2018 +0800
 3 files changed, 2 insertions(+), 1 deletion(-)
 create mode 100644 lose_file.txt
 create mode 100644 test_amend.txt
 
$ git log --name-status --pretty=oneline -1
31cc2774f0668b5b7c049a404284b19e9b40dc5d (HEAD -> master) [*]update time to 00:50
A       lose_file.txt
A       test_amend.txt
M       time.txt
  • 查看文件提交日誌只有time.txt
  • stage裏還有新的修改在
  • 使用git commit --amend --no-edit合並到上一個提交裏,如果不加--no-edit參數的話,會提示你來修改commit提示信息(這個命令也可以用在重復編輯commit message)。
  • 查看日誌,合並提交成功!

tag標簽

創建一個tag