本文由雲+社區發表html
做者:工程師小熊mysql
摘要:上一集咱們一塊兒入門學習了git的基本概念和git經常使用的操做,包括提交和同步代碼、使用分支、出現代碼衝突的解決辦法、緊急保存現場和恢復現場的操做。學會之後已經足夠咱們使用Git參加協做開發了,可是在開發的過程當中不免會出錯,本文主要介紹版本控制的過程當中出錯了的場景,以及Git開發的一些技巧,讓咱們用的更流暢。git
上集回顧:github
本文核心:算法
若是你發現剛剛的操做一不當心commit了,所幸你尚未推送到遠程倉庫,你能夠用reset
命令來撤消你的此次提交。sql
reset
命令的做用:重置HEAD(當前分支的版本頂端)到另一個commit。安全
咱們的撤消當前提交的時候每每不但願咱們這次提交的代碼發生任何丟失,只是撤消掉commit的操做,以便咱們繼續修改文件。若是咱們是想直接不要了此次commit的所有內容的任何修改咱們將在下一小節討論。hexo
來,咱們先說一句蠢話來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
複製代碼
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^
,這樣你本地修改就完全丟掉了(慎用),若是真用了想找回來怎麼辦?見救命的後悔藥。
固然了,你只要開心不加soft
或hard
參數也是安全的(至關於使用了--mixed
參數),只不過是撤消之後你的本次修改就會回到add
以前的狀態,你能夠從新檢視而後再作修改和commit
。
要是咱們作的更過度一點,直接把此次commit
直接給push
怎麼辦?要是被發現就全完了,咱們來看看github上的遠程倉庫。
完了,真的提交了(我剛剛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
真的撤消了遠程倉庫,長舒一口氣。
若是咱們剛剛執行了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.log
把mysql.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 <pzqu@example.com>
Date: Sun Dec 23 00:51:54 2018 +0800
[*]update time to 00:50
複製代碼
版本2,time.txt內容18:51
commit 856a74084bbf9b678467b2615b6c1f6bd686ecff
Author: pzqu <pzqu@example.com>
Date: Sat Dec 22 19:39:19 2018 +0800
[*]update time to 18:51
複製代碼
版本3,time.txt內容10:41
commit 3f22a0639f8d79bd4e329442f181342465dbf0b6
Author: pzqu <pzqu@example.com>
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恢復到版本1,一樣的方法,由於版本1是上一次提交咱們能夠省略掉版本號
$ git checkout -- time.txt
$ cat time.txt
00:50
複製代碼
看到了吧!只要用git checkout commit_id -- filename
的組合,想搞出哪一個文件歷史版本就搞出哪一個。
到了這裏,你可能會很懵比,reset
和checkout
命令真的好像啊!均可以用來作撤消
checkout
語義上是把什麼東西取出來,因此此命令用於從歷史提交(或者暫存區域)中拷貝文件到工做目錄,也可用於切換分支。reset
語義上是從新設置,因此此命令把當前分支指向另外一個位置,而且有選擇的變更工做目錄和索引。也用來在從歷史倉庫中複製文件到索引,而不動工做目錄。還想不通能夠給我發郵件:pzqu@qq.com
來到這裏我已經很清楚的你的現況了,你的代碼丟了如今必定很是的着急,不要慌,老是有辦法找回他們的。可是前提是要保證你的項目根目錄下.git文件夾是完整的,要是手動刪除了裏面的一些東西那就真完了。還要保證一點,你的代碼之前是有過git追蹤的,最少add
過
Git提供了一個命令git reflog
用來記錄你的每一次命令,貼個圖吧直觀點:
git reflog
裏的所有都是和改變目錄樹有關的,好比commit rebase reset merge
,也就是說必定要有改變目錄樹的操做才恢復的回來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 <pzqu@example.com>
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 <pzqu@example.com>
Date: Sun Dec 23 00:51:54 2018 +0800
[*]update time to 00:50
commit 856a74084bbf9b678467b2615b6c1f6bd686ecff
Author: pzqu <pzqu@example.com>
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
)版本。status
和ls
再看,這個叫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
等類型的。他們是什麼含義呢?
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 ls-tree 3bd4c055afedc51df0326def49cf85af15994323 100644 blob c44be63b27a3ef835a0386a62ed168c91e680e87 share_file.txtgit 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
git commit --amend --no-edit
合併到上一個提交裏,若是不加--no-edit
參數的話,會提示你來修改commit提示信息(這個命令也能夠用在重複編輯commit message
)。標籤是一個相似於快照的東西,經常用於測試和發佈版本。因此咱們經常把tag
名以版本號來命名,好比:v1.0beat1這樣
咱們怎麼建立標籤呢?首先先切換到想打標籤的分支,而後直接打就能夠了。
$ git branch
dev/pzqu
master
* release_v1.0
$ git tag -a release_v1.0 -m "release v1.0"
$ git tag release_v1.1
$ git tag
release_v1.0
release_v1.1
$ git push --tags
Counting objects: 2, done.
Writing objects: 100% (2/2), 158 bytes | 158.00 KiB/s, done.
Total 2 (delta 0), reused 0 (delta 0)
To github.com:pzqu/git_test.git
* [new tag] release_v1.0 -> release_v1.0
* [new tag] release_v1.1 -> release_v1.1
複製代碼
tag
的分支release_v1.0
帶有信息release v1.0
的tag
tag
的提交信息的release_v1.1
git tag
查看tag
tag
也能夠推送單個tag
$ git push origin release_v1.1
Total 0 (delta 0), reused 0 (delta 0)
To github.com:pzqu/git_test.git
* [new tag] release_v1.1 -> release_v1.1
複製代碼
咱們來刪除tag
$ git tag -d release_v1.0
Deleted tag 'release_v1.0' (was eb5d177)
$ git push origin :refs/tags/release_v1.0
To github.com:pzqu/git_test.git
- [deleted] release_v1.0
$ git tag
release_v1.1
複製代碼
release_v1.0
的tag
release_v1.0
的tag
先看看當前的log
31cc277 (HEAD -> release_v1.0, tag: release_v1.1, origin/release_v1.0, master) [*]update time to 00:50
856a740 [*]update time to 18:51
3f22a06 [+]add file time.txt
4558a25 (origin/dev/pzqu, dev/pzqu) [*]test stash
d9e018e [*]merge master to dev/pzqu
複製代碼
比方說要對[*]update time to 18:51
此次提交打標籤,它對應的commit id是856a740
,敲入命令:
$ git tag v.9 856a740
$ git log --pretty=oneline --abbrev-commit
31cc277 (HEAD -> release_v1.0, tag: release_v1.1, origin/release_v1.0, master) [*]update time to 00:50
856a740 (tag: v0.9) [*]update time to 18:51
複製代碼
咱們有兩種狀況,一種是咱們根本就不想這些文件出如今git庫裏好比日誌文件;另外一種是git遠程倉庫裏有這些文件,就像通用的配置文件,咱們必需要在本地修改配置來適應運行環境,這種狀況下咱們不想每次提交的時候都去跟蹤這些文件。
忽略文件的原則是:
咱們要怎麼作呢?
在Git工做區的根目錄下建立一個特殊的.gitignore文件,而後把要忽略的文件名填進去,Git就會自動忽略這些文件。 touch test.log ls -a . .git README.md need_stash.txt test.log test_amend.txt .. .gitignore lose_file.txt share_file.txt test2.log time.txt$ git status On branch release_v1.0 nothing to commit, working tree clean* 建立並寫入忽略規則
*.log
忽略所有以.log
爲後綴的文件 * 建立了test.log
和test2.log
*status
查看,真是工做區是clean
,新建立的文件沒有被跟蹤
核心命令:
git update-index —assume-unchanged 文件名
複製代碼
upload successful
time.txt
文件並寫入10:41
,提交到遠程倉庫git update-index —assume-unchanged
加time.txt
加到忽略名單裏time.txt
的內容爲10:43
status
查看確實沒有被跟蹤 看遠程倉庫核心命令:
git update-index —no-assume-unchanged 文件名
複製代碼
upload successful
pull
同步遠程倉庫,真的沒有更新剛剛被添加跟蹤忽略的文件git update-index —no-assume-unchanged
取消跟蹤忽略status
查看,出現文件的跟蹤若是忘記了哪些文件被本身本地跟蹤
upload successfulgit update-index —assume-unchanged
加time.txt
加到忽略名單裏git ls-files -v| grep '^h\ '
命令能夠看到小寫h表明本地不跟蹤的文件學完本文章,你將學會
理論上,git平常用到的命令是 diff show fetch rebase pull push checkout commit status 等,這些命令都不會致使代碼丟失,假如懼怕代碼丟失,能夠預先commit一次,再進行修改,但切記
不可以使用本身不熟悉的命令 任何命令,不要加上-f的強制參數,不然可能致使代碼丟失
建議多使用命令行,不要使用圖形界面操做
Git 基礎再學習之:git checkout -- file
如何理解git checkout -- file和git reset HEAD -- file
此文已由騰訊雲+社區在各渠道發佈
獲取更多新鮮技術乾貨,能夠關注咱們騰訊雲技術社區-雲加社區官方號及知乎機構號