版本控制工具——Git常用操作(下)
本文由云+社區發表
作者:工程師小熊
摘要:上一集我們一起入門學習了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] [+]罵了我的boss1 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] [*]夸了我的boss1 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上的遠程倉庫。
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.lognothing 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.txtno 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被改變了
我們來把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用來記錄你的每一次命令,貼個圖吧直觀點:
upload successful
- 有沒有發現,git reflog里的全部都是和改變目錄樹有關的,比如commit rebase reset merge,也就是說一定要有改變目錄樹的操作才恢復的回來
- 像add這種操作就不能恢復了嗎?那肯定不是,只是要用更麻煩點的方式來恢復
- git log是一樣的,也可以看到所有分支的歷史提交,不一樣的是看不到已經被刪除的 commit 記錄和 reset rebase merge 的操作 我們可以看到git reflog前面的就是commit id,現在我們就可以用之前介紹過的方法來回滾版本了,撤消當前commit
- 根據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等類型的。他們是什么含義呢?
upload successful
- blob組件并不會對文件信息進行存儲,而是對文件的內容進行記錄
- commit組件在每次提交之后都會生成,當我們進行commit之后,首先會創建一個commit組件,之后把所有的文件信息創建一個tree組件,所以哪個blob代表什么文件都可以在tree 里找到 我們來看看怎么恢復剛剛不見了的lose_file.txt文件,在上面執行完git fsck --lost-found命令,返回的第一行blob我們看看他的內容
- 看到沒有,就是我們丟失的文件內容,這樣就找回來了! 我們再來看看commit tree的內容$ git cat-file -p fdbb19cf4c5177003ea6610afd35cda117a41109 tree 673f696143eb74ac5e82a46ca61438b2b2d3bbf4 parent e278392ccbf4361f27dc338c854c8a03daab8c49 parent 7b54a8ae74be7192586568c6e36dc5a813ff47cf author pzqu pzqu@example.com 1544951197 +0800 committer pzqu pzqu@example.com 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.txtnew file: test_amend.txt$ git commit --amend --no-edit [master 31cc277] [*]update time to 00:50Date: Sun Dec 23 00:51:54 2018 +08003 files changed, 2 insertions(+), 1 deletion(-)create mode 100644 lose_file.txtcreate 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
標簽是一個類似于快照的東西,常常用于測試和發布版本。所以我們常常把tag名以版本號來命名,比如:v1.0beat1這樣
我們怎么創建標簽呢?首先先切換到想打標簽的分支,然后直接打就可以了。
$ git branchdev/pzqumaster * 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
對歷史提交打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遠程倉庫里有這些文件,就像通用的配置文件,我們必須要在本地修改配置來適應運行環境,這種情況下我們不想每次提交的時候都去跟蹤這些文件。
忽略自動生成的垃圾文件、中間文件、敏感信息文件
忽略文件的原則是:
我們要怎么做呢?
在Git工作區的根目錄下創建一個特殊的.gitignore文件,然后把要忽略的文件名填進去,Git就會自動忽略這些文件。$ echo ".log" > .gitignore$ touch test.log$ touch test2.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查看確實沒有被跟蹤 看遠程倉庫
upload successful
取消跟蹤忽略
核心命令:
git update-index —no-assume-unchanged 文件名upload successful
- pull同步遠程倉庫,真的沒有更新剛剛被添加跟蹤忽略的文件
- git update-index —no-assume-unchanged取消跟蹤忽略
- status查看,出現文件的跟蹤
查看跟蹤記錄
如果忘記了哪些文件被自己本地跟蹤
upload successful
- 使用命令git update-index —assume-unchanged加time.txt加到忽略名單里
- 使用git ls-files -v| grep '^h\ '命令可以看到小寫h代表本地不跟蹤的文件
小結
學完本文章,你將學會
- 撤消commit,回滾暫存區,回滾工作區、回退遠程倉庫
- 兩種方法找回不小心丟失的文件
- 提交的時候漏了文件,修改commit的提交信息
- tag操作,創建、創建有描述信息的tag、刪除tag、刪除遠程tag、推送本地單個tag和全部tag
- git忽略自動生成的垃圾文件、中間文件、敏感信息文件;忽略遠程存在,本地不想與遠程同步的文件并恢復跟蹤和查看哪些文件被跟蹤
注意事項
理論上,git日常用到的命令是 diff show fetch rebase pull push checkout commit status 等,這些命令都不會導致代碼丟失,假如害怕代碼丟失,可以預先commit一次,再進行修改,但切記
不可使用自己不熟悉的命令 任何命令,不要加上-f的強制參數,否則可能導致代碼丟失
建議多使用命令行,不要使用圖形界面操作
下集
引用
git官網
廖雪峰的官方網站-git篇
hexo博客部署到vps
關于git reset --hard這個命令的慘痛教訓
Git 基礎再學習之:git checkout -- file
如何理解git checkout -- file和git reset HEAD -- file
此文已由騰訊云+社區在各渠道發布
獲取更多新鮮技術干貨,可以關注我們騰訊云技術社區-云加社區官方號及知乎機構號
轉載于:https://www.cnblogs.com/qcloud1001/p/10497305.html
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的版本控制工具——Git常用操作(下)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 创建git工程
- 下一篇: bootstrap思考一