使用Git已有很長一段時間,遇到一些痛點問題,而且大都是網上難以直接查到的,故總結于此。
1.兩幅重要的圖
以上兩幅圖對理解Git的原理十分重要。
2.基礎命令
首先是一些基礎命令,這些命令經常使用,而少為人知。
git add -A
或git add --all
把包括修改、增加、刪除都全部加入暫存區。等同于git add .
和git add -u
兩個命令。git add -p
一段段地把修改提交到暫存區,每一個修處改都會提示你是否加入暫存區。可以按照想要的方式把所有修改分成多個commit。git commit -am
把已經加入暫存區(Stage)的文件的更改提交到本地版本庫,無論這些更改是否已經加入暫存區。與git commit -m
的區別在于后者只能向本地版本庫提交已經加入暫存區的更改。如果只是改動某些代碼,而沒有增加或者刪除文件,使用git commit -am
一個命令就可以替代git add --all
和git commit -m
兩個命令。git commit --amend
修改最后一次commit的提交信息。注意,是最后一次,也就是HEAD指向的commit。git branch -vv
顯示本地分支與遠程分支的對應關系。執行git push
(沒有帶分支參數)時代碼被提交到對應遠程分支,就是依據該對應關系。這里需要注意Git的simple和matching模式,后者會一次性提交所有的對應關系的分支的代碼到遠程分支,前者只會提交當前所在分支。git stash
和git statsh pop
主要用來暫存當前的工作。如果需要git pull
又不想把當前的修改提交commit,則可以先使用git stash
把修改暫存。該命令運行完后工作區是干凈的,此時再使用git pull
拉新代碼。完事了后使用git statsh pop
恢復之前的工作狀態。git reflog
顯示曾經的commit。如果我們使用git reset
將HEAD指向某個過去的commit了,此時要想回到最開始的commit,使用git log
是無法查到最開始的commit的。但是也不要慌,git reflog
能給你回到最開始的commit的機會,它會顯示所有的提交記錄。
3.使用場景
根據一些常見的場景給出我自己的解決方法。
3.1 基于遠程分支建立本地分支
你可能剛入職。你的新同事突然丟過來一個地址A,讓你git clone
一下代碼。接著他又丟過來一個分支名dev0.1,讓你基于該分支進行開發。如果之前只會在master上git pull
和git push
,此時就可能有點不知所措了。
你可能首先想到如何把遠程的dev0.1分支clone下來,因為執行git clone A
之后,你再用git branch
查一下,發現本地只有master分支。
這里需要糾正一個問題。git clone A
實際上把所有的分支都從遠程拉下來了,但是git branch
只顯示那些與遠程保持了追蹤(tracked)的分支。并且git clone A
還會創建本地master分支并將其與遠程(origin)的master建立追蹤(tracked)關系。于是我們最開始使用git branch
就只顯示master了。
那么,問題來了。既然所有分支都已經拉下來了,我們如何切入dev0.1分支并進行開發呢?其實很簡單:
git checkout dev0.1
這個命令的含義是將dev0.1分支從本地版本庫取到工作區來(參考上面的兩幅圖中的第二幅),并將工作區切換到該分支。此時一個本地名為dev0.1的分支與遠程(origin)的dev0.1分支就建立了追蹤關系。我們就可以基于dev0.1進行開發了。試試git branch
看看會不會顯示出dev0.1
。
假如遠程分支中并沒有一個叫dev0.1的分支,我們運行以上命令,就會發現這樣的錯誤提示:
error: pathspec 'dev0.1' did not match any file(s) known to git.
此時我們可以看看到底有哪些遠程分支,請使用git branch -r
。當然git branch -a
顯示的信息就多一些,它顯示全部本地分支和遠程分支。
但我通常并不這樣做。而是用以下命令:
git checkout -b dev origin/dev0.1
它的含義是新建一個本地分支dev,且讓這個分支與遠程dev0.1分支保持追蹤關系。這樣做的好處在于可以自己取一個本地分支名。
3.2 分支開發模式同步Master分支代碼
你可能基于本地的dev0.1分支寫了一些代碼,測試后并提交到本地倉庫中了。你們團隊代碼主干分支是master,且基于這個分支進行發布。這就面臨著一個問題,你的新寫的代碼如何提交到主干分支上。
1.如果的本地dev0.1分支與遠程(origin)dev0.1分支保持了追蹤關系,且如果你們團隊使用類似github/gitlab這樣的git倉庫托管服務,則可以直接在本地dev0.1分支上操作:
- 先
git pull origin dev0.1
拉下遠程代碼,以防有更新。 如果git是simple模式,則可以直接使用git pull
[目前git2.0及以上版本都默認為simple模式]。 - 如果有更新則會自動合并,合并如果失敗,則會要求手動處理沖突。處理完后將這次修改提交到本地倉庫。
- 使用
git push origin dev0.1
向遠程倉庫dev0.1分支提交代碼。git 2.0及以上版本直接git push
即可。 - 在github/gitlab的界面上操作,由dev0.1分支向master分支提交merge請求。負責master分支維護的同事合并你的分支代碼到master即可。
2.如果本地dev0.1分支是基于master分支新建的,即在本地master上使用git checkout -b dev0.1
命令---它會創建一個dev0.1的本地分支,并切換到該分支,但它不會設置與遠程分支的追蹤關系。這時候如何將該分支的新代碼合并到master分支呢?
- 首先dev0.1分支要合并到本地master上去。先使用
git checkout master
切換到主干分支,在主干分支上操作。 - 合并dev0.1分支。使用
git merge dev0.1 --no-ff
或git merge dev0.1
命令。這兩者的區別可用以下圖片說明:
前者帶有分支記錄,后者沒有。
- 然后將master分支的代碼提交到遠程master:
git pull
拉新代碼,解決沖突,git push
推到遠程。 - 如果dev0.1分支沒用處了,就可以直接刪掉:
git branch -d dev0.1
。
3.3 將工作區恢復成干凈的狀態
如果開發了一陣子,修改了一些代碼,但沒控制好,把工作區搞成了一堆亂麻。這時候就想,要是能把工作區恢復成最開始的樣子就好了。
首先確定要恢復成的最初狀態。一般來講就是將工作區恢復成當前本地倉庫中HEAD所指向的commit。不過如果你之前提交到本地的一些commit你也不想要了,那么先用
git log
查一下你要恢復到的commitID,復制下來。使用
git reset --hard HEAD/commitID
命令。運行完畢之后,用git status
查一下狀態。一般狀態下會顯示
nothing to commit, working directory clean
但是如果你最開始時新增了一些文件,且沒有將其加入暫存區,那么就不是這種提示了。你需要把你新增的那些文件刪掉,git status
才會恢復成以上狀態。主要原因是git reset
無法重置那些沒有加入暫存區的更改。
如果你只是需要把某個文件B恢復成倉庫里上一次提交的狀態,那么有以下兩種可能:1.這個文件沒有任何修改提交到暫存區;2.這個文件有一部分修改提交到暫存區了,但是想把暫存區的修改也恢復成HEAD指向的版本。
- 1情況下直接使用
git checkout B
恢復工作區的文件B。 - 2情況下先使用
git reset HEAD B
撤銷暫存區里面對B的修改,再使用git checkout B
恢復工作區中的修改。
3.4 修改某次的提交信息
這個場景并不常見,而有的時候又一定用的到。例如你開發一段時間了,卻發現公司的gitlab要求你push的時候必須使用公司的郵箱,而你之前的commit都用的是你自己的郵箱。我之前就遇到過這個問題,在這兒記錄一下解決方案。
首先,git commit --amend
只能對最新提交的Comment內容或郵箱的修改,并不能對中間提交的Comment或者郵箱進行修改。如果只是修改最新提交的信息:
-
git commit --amend
直接進入修改提交信息的模式。 -
git commit --amend --author="chenyi <chenyi@xxx.com>"
可以修改作者信息。
如果需要修改中間某一次的提交信息,則需要按照一定的方法操作。以下是一個修改示列。
如上圖,第二個提交的郵箱同其他提交不一樣,我們需要將它修改成與其他一樣的。
先進入第二個提交:
git rebase -i preCommitID
,這里preCommitID就是第二個提交的下面的一個ID,即ff4e24
。
我們可以看到,列表里面顯示了我們想要修改的commit的ID,即
c1c6685
。將其前面的pick
更改為edit
,保存并退出。
當我們從第2步的編輯過程中退出后,git會提示我們可以用的兩個命令
git commit --amend
和git rebae --continue
。這兩個命令在后面都會用到。
我們目前已經處于c1c6685
commit上了。此時我們只需要使用git commit --amend --author="chyoo <chyoo1991@gmail.com>"
修改該提交的作者信息。上圖就是運行該命令后進入修改的界面。
git rebase --continue
繼續rebase就可以完成目標了。
3.5 將Commit記錄變成直線模式
通常很多人合作寫代碼時,希望將遠程master分支維護成直線的形式,這樣commit干凈明確,檢查問題時能省不少事。而一般合并最新代碼時用的git pull
會將生成一個merge commit,這將導致推送到遠程分支的代碼也是各種分支交叉,一點也不干凈明確。
那么,該如何實現這個目的呢?
首先這里要上一份干貨,就是git merge
與git rebase
的區別,需要注意到git pull
實際上是調用git merge
進行代碼的合并。->干貨在此。
從干貨文章中我們可以發現,能夠使用git rebase
命令用打補丁的方式來實現commit保持直線的目的。我們這樣操作:
git fetch origin master
用這個命令將遠程(origin)的master分支最新提交取到本地。git rebase origin/master
使用rebase 進行pack打補丁,將我們自己提交的本地代碼以補丁的方式放在最新代碼的后面。這個過程中可能會出現沖突,修改完了之后使用git rebase --continue
繼續pack,直到完成rebase。如果出現無法解決的問題,想回到最開始,可以使用git rebase --abort
。git push
提交代碼到遠程master分支。
這里要求開發協作的每一個人都用這種方式操作和提交代碼,這樣才能保持遠程master分支的干凈整潔和直線性。
3.6 為一個本地倉庫設定多個遠程倉庫
這個看起來是有點奇怪的需求。不過前不久gitlab的員工刪除了它們的數據庫,導致很大部分項目受影響,這讓我們覺得如果能多一份代碼保障或許更好。
使用git clone
時,會自動設定遠程地址,一般都是以origin
指代。如果我們有特殊需求,需要將代碼提交到另一個遠程倉庫,這時就要設定新的遠程倉庫地址了。
git remote add hegel http://xxxx.git
這個命令就可以給當前的本地庫設置一個遠程倉庫hegel
。以后我們想推拉代碼到這個遠程庫時,就需要用git push hegel master
或git pull hegel master
了。
4.快捷設定
有一些比較有用的設定,能在寫命令時少了很多麻煩。
- 全局設置user和email
- git config --global user.name "chenyi"
- git config --global user.emali "chenyi@xxx.com"
- 給常用命令起一個簡單的名字
- git config --global alias.co checkout
- git config --global alias.st status
- git config --global alias.br branch
- git config --global alias.ci commit
后續就可以使用git co
/git ci
等命令了,是不是簡潔了很多?
3.更好看的git log
git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
配置了這個之后,使用git lg
簡直眼前一亮。