我從沒有用過 SVN,爲何,由於我在接觸 Git 以前從沒有接觸到版本工具,大部分時間都是一我的在盲幹。我對 SVN 的命令仍是熟悉那麼一點點,但當我身邊使用過 Git 和 SVN 的人都在誇讚前者的時候,我想,不會 SVN,那又如何呢。html
接觸到 Git 以後,就常常混跡在 GitHub。一開始的時候,只知道 GitHub 是一個能夠下載不少牛逼代碼的倉庫,沒事就去 Download zip 包來觀摩。當團隊在內網用 Git 管理項目時(GitLab),這段時間是我對 Git 由淺入深的一個重要階段,add、commit、reset、checkout。前端
用 Git 這麼久了,我想來總結下個人使用經驗。linux
我不喜歡圖形界面,仍然靠命令行來使用 git。我認可公司使用的 pyCharm 中的 git 模塊已經足夠好了,但我只喜歡用命令行,固然也有例外,好比解決衝突的時候,我會使用圖形界面,畢竟 git 命令行解決衝突真的有點弱雞。若是你們以爲命令行真的用不習慣,推薦 SourceTree,一款不錯的 git 圖形化界面軟件,用官網的話說,就是「Harness the power of Git and Hg in a beautifully simple application」。git
下面是一些有用的 git 教程,寫的都挺好的,(不,是很是好):程序員
我最常敲的 git 命令還屬 git status
,這個命令就像 linux 的 ls
命令同樣深刻人心。我是那種有強迫症的程序員,每執行完一個操做,好比 add,commit 等,我都會用 status 命令來查看下執行結果,由於總有一種不放心的感受。作事情認真的同窗,寫出來的代碼確定會讓人放心。緩存
git init
能夠在當前文件夾下建立一個 git 倉庫,即 .git
文件夾,而大多數的時候,咱們會忽略這個命令,而經過 git clone url
的方式來獲取代碼,這樣能夠省去不少設置,好比 remote。服務器
git init 用途仍是很普遍的,對於一個用在本地的項目,咱們不須要把它提交到服務器,可是須要時刻觀察它有哪些文件通過改動,這時在這個項目建立一個 git 文件版本庫,仍是很是實用的。但仔細想一想,發現 git 的產生原本就是爲解決這個問題的,只不過有了 push pull 這樣的命令後,git 才由本地面向服務器,繼而有了 github 這樣的網站(pull request 請求)。app
git config
的命令太多了,這裏講不完的,不介紹了,須要設置什麼本身百度就能夠解決,config 的配置文件分爲 global 和 local,global 的文件位置通常是在 /etc/gitconfig
或 ~/.gitconfig
內,而 local 的配置文件在 .git 文件的 config 文件中,無需手動修改他們,用命令行就行啦。好比:ssh
// 設置是否顯示不一樣顏色 git config color.ui true // window 平臺換行報錯 git config --global core.autocrlf false
從服務器上 push 項目須要提供 url,這裏有兩種類型的 url,http 和 git,不管哪一種方式,都須要咱們提供一個 username 和 useremail,通常安裝好一個 git 以後,首先要作的就是這個配置 user 的命令。
git config --global user.name songjinzhong git config --global user.email xx@xx.com
我說過了,我是一個有強迫症的程序員。git http 方式進行一些 push 操做的時候,每一次都必須輸入用戶名和密碼。若是單單是密碼也就算了,每次都輸入用戶名,真的很煩耶。
解決辦法就是用 ssh 的方式登錄,首先要在本地機器生成 ssh 密鑰,是以郵箱來生成的,目錄通常是 ~/.ssh
,把公鑰內容拷貝到 github 的 ssh 密鑰管理裏,此時須要輸入 github 密碼。更改後每次用 git 訪問 github 服務器端時候,就能夠驗證機器而不用再輸入用戶名和密碼了。
ssh-keygen -t rsa -C 'xx@xx.com' // copy 公鑰 to github // 測試是否配置成功 ssh -T git@github.com // 若是不報錯,且顯示用戶名,則成功了
PS:這個時候有同窗說 push 的時候仍是要輸入用戶名和密碼,那是由於你忘記了一個最重要的事情:修改 remote 的 url,若是你的 url 仍然是 http 或 https 的,那麼請修改成 ssh 的:
git remote set-url origin git@github.com:songjinzhong/yuren.space.git // 查看一下是否修改 git remote -v // origin git@github.com:songjinzhong/yuren.space.git (fetch) // origin git@github.com:songjinzhong/yuren.space.git (push)
還有一個頗有趣的問題,當我在同一臺機器上,既能夠鏈接到 github,又須要鏈接到 coding.net 的時候,git 如何得知我鏈接到的是哪一個服務器,用哪一個私鑰。仍然須要對 git 進行配置,新建一個公鑰,若是郵箱是同樣的,可使用同一個公鑰,這點經測試是能夠的。將公鑰添加到 coding.net 的 ssh 配置下,在 ~/.ssh
目錄下新建一個 config 文件,以下填寫:
# 配置github.com Host github.com HostName github.com // 配置私鑰所在位置 IdentityFile ~/.ssh/id_rsa PreferredAuthentications publickey User username1 # 配置git.coding.net Host git.coding.net HostName git.coding.net IdentityFile ~/.ssh/id_rsa PreferredAuthentications publickey User username2
分別做測試:
ssh -T git@github.com // successful ssh -T git@git.coding.net // successful
在多人協做的項目中,須要在 remote 中添加多個源,好比老大建了一個 A 項目,我在 gitlab 上 fork 了這個項目 A',服務器上的項目就有兩個,一個是老大的,一個是個人。對於我本身的項目,我 clone 下來的時候,remote 默認是添加了 origin url 的,若是是 init 方法,可能還須要手動添加。
此時,須要添加老大項目 A 的 URL 到 remote 中。
在不少狀況下,我更新本身的代碼後,push 到本身的服務器上的項目,並經過 gitlab 服務器把 commit 提交給老大,老大合併,這沒問題。若是老大更新了代碼(不必定是老大寫的,有多是其餘開發人員寫的),我這邊須要把老大代碼合併到個人 A' 項目中。
// 獲取 leader 最新代碼 git fetch leader/master // 查看更新狀況 git log leader/master --stat // 暫不討論 merge 和 rebase 問題 git merge leader/master
到底何時用 pull 或 fetch,要我說,千萬別要用 pull,git 就應該把 pull 命令給刪掉,由於 pull 命令就是 fetch 和 merge 命令的組合,若是從謹慎狀況來考慮,確定是 fetch 大法好。仍是那句話,建議刪除 git pull 命令。
git log
命令能夠查看 log 日誌,即咱們所說的 commit 狀況,通常經常使用的幾個參數以下:
// 查看當前分支的日誌 git log // 顯示文件變化情況(縮略) git log --stat // 顯示文件變化情況(詳細)自帶 diff git log -p // 顯示任意分支日誌 git log origin/master git log leader/master
git log remote
是多人合做的重點,只有觀察別人修改了什麼,才能最好的解決衝突,對,就是解決衝突。
我經常使用的命令就這些,並且我所經手的項目,基本都不是很複雜,看了 commit (前提是 commit message 沒毛病)就徹底能夠搞清楚情況。
有時候會以爲 git log 命令不夠形象,沒有圖形界面那種層次關係,並且圖形界面還提供各個 remote 所在的位置,確實比較直觀,但我就喜歡命令行。
有時候真的沒必要糾結這個問題,由於這兩種合併的方式,都各有好處。
git merge 默認是把當前項目分支(其實就是本地項目最新的 commit)與其它分支進行合併,好比與 origin/master:git merge origin/master
,與 leader:git merge leader/master
。
在這裏借用圖解Git的幾張圖。
第一種狀況,若是要合併的分支(這裏是 master)與當前分支並不存在分叉,一條直線,這個時候,只是簡單的移動分支(請注意合併的順序,是將 master 分支合併到當前分支,是不會改變 master 分支到)。這個時候最無聊了,若 Head 分支比較慢,就把 master 分支內容拿過來,如上圖,若是 Head 分支本事就很快,比 master 還要靠右,啥也不作,若是兩個分支內容同樣,也是啥也不作。
真正的合併應該如上圖所說,兩個分支,不只不同,還有互相都有對方沒有的 commit。
如圖,b325c
是兩個分支相同的,33104
是 other 分支,ed489
是 master 分支的,會把這三個分支融合成一個新分支,固然,若是有衝突的話,還要解決衝突。圖中沒有說清楚的一點是,最終的 commit 路線仍然是直線,會按照時間關係從新排列。不管如何,最終會有一個 commit 用來記錄此次三方合併。
有時候有人會有這樣的疑問:沒有衝突的狀況下,爲何也會生成一個空的 commit,能夠刪掉嗎?答案是能夠的,有兩種方法,一種是用 git merge --no-commit
,若是有衝突會比較麻煩,例外一種是 git reset $pre-commit
跳過這個 commit,雖然很摳腳。。
git rebase
命令和 merge 功能是同樣的,只不過在處理 commit 的時候,方式不同。前面說了 merge 是會生成一個三方合併 commit,commit 會按照時間來從新排列,不會打亂 commit 序列,而 rebase 合併方式就不一樣了:
從圖中能夠看出,rebase 比較奇葩的會把當前分支的每個 commit 移到序列的最前端,打亂了按時間提交的序列,顯然這對於之後查找歷史記錄會帶來麻煩。rebase 還有不少命令,好比 --onto 來限制提交。
咱們項目組,目前採用的合做方式以下,咱們每一個模塊分工互不干擾,不多出現 conflict 狀況:
每人在開始新工做以前,先 fetch leader 的項目,處於同一塊兒跑線;
每一個人完成任務,產生新的 commit,再次 fetch leader 項目,對比別人有沒有提交,若暫時沒有別人提交,直接提交給 leader;
若發現已經有人提交,先對比,合併後沒有衝突直接提交,發現衝突,須要商量並解決衝突再提交,基本都是用 merge 方式。
其實這個命令,我幾乎沒怎麼使用過,首先公司裏的項目,基本就一個主分支 master,同志們都很齊心,不搞分裂,因此沒怎麼使用。
對於 branch,我以爲是 git 另外一個很是強大的功能,拋除服務器,只在本地的話,branch 能夠衍生出許多分支,好比測試分支(這個最多見啦),版本分支(或許 git tag 更好用些)。不過像 github 的 gh-pages 和 coding.net 的 coding-pages 分支,雖然脫離了版本控制的範疇,卻也不失爲一種頗有意義的用法。
git tag
,沒用過,想用。
這三個命令均可以用來作對比,但使用的方法卻大不相同。
好比 git show $commitid
表示那個 commit 作了哪些新改變,固然這個能夠被 git log -p
所掩蓋,表示全部 commit 作了哪些改變,而 git diff
默認是緩存區的內容與 open file 的內容作對比,固然也能夠git diff $id1 $id2
比較兩個 commit 之間的不一樣,全部要根據本身的需求來判斷到底哪一個更合適。
固然,貌似還有更多的用法,我就沒用過,就不一一探討了。
上面的內容,沒有乾貨,只是簡單說明了一下本身的使用經歷。
其實,對於 git,若是用服務器的話,必需要了解幾個倉庫。你從 leader 那裏 fork 了項目 A 爲 A',這個時候就有了兩個倉庫,並且這兩個倉庫是互相獨立的,通常的 git 服務器像 github 或 gitlab 都有自帶的 pull request 命令,容許我將 A' 的 commit 提交給 A。
經過 git clone 命令能夠將服務器上的倉庫拉取到本地,這是第三個倉庫,並且咱們提交的 commit 通常都會先提交到本地倉庫。git push 命令能夠將本地倉庫推到服務器,並且本地的分支都會有一個 remote url 用來做爲 pull push 的源頭。我也能夠對 A 倉庫進行 push,但會由於沒有權限致使 push 失敗。
不過呢,即便沒有服務器的 pull request 請求,也徹底是能夠的,leader 須要有每個 fork 用戶的 remote url 便可:
這時候,我會對 leader 喊一下,「個人代碼更新了,老大,更新一下」;
老大不耐煩的打開 git,並把個人代碼 git fetch 到他本地;
老大解決下衝突,把代碼合併到他的分支中,並喊 「好了,代碼更新好了」;
我 fetch leader 的新代碼,覆蓋我本地代碼。
對於倉庫,瞭解這些就夠了,而比較讓人費解的是每一個倉庫的文件目錄,請看下圖(來源於萬能的互聯網):
這裏有三個目錄,分別是 History,Stage,Working Director:
History 表示提交的歷史記錄,全部的 commit 都會被保存;
Stage 表示暫存區,做爲 open file 與 History 的中間人;
Working Director 表示咱們能看到的,即咱們打開的文件。
若是從存儲的位置來看,History 和 stage 都存在 .git 文件夾中,而 working director 就是剩下的工做區文件。
從圖中也能夠看出,咱們經常使用的 add、commit、reset、checkout 命令,其實就是對這三個文件目錄進行的修改,圖已經說的很清楚了,仔細看圖吧。
也能夠直接跳,好比 git reset HEAD
直接從 History 調到 working director,git commit -a
能夠省去 add 命令。
那麼,這樣劃分,有什麼合理性嗎?
都說 git 是版本管理工具,如何找回那個丟失好久的文件?
事情是這樣的,前天我修改了 a.js 文件,效果很好,並提交了 commit A,昨天我又修改了 a.js 和 b.js 文件,並提交了 commit B,今天我再次修改了 a.js 和 b.js 文件,提交 commit C。問題來了,今天下班前測試,發現昨天和今天修改的 a.js 文件對整個系統的效益不大,組織上準備仍是採用前天的那個版本,準備回滾,有一個問題,由於昨天和今天也修改了 b.js 文件,而這個文件是不容許回滾的。想了想,若是能夠拿回前天的那個 a.js 文件,並用它替換掉今天的 a.js 就行啦。
基本的步驟就是:
git branch test && git checkout test
新建一個分支用來回滾,程序員嘛,以防萬一,出現錯誤直接 delete 分支,不會對主分支形成影響;
git reset A
,回滾到前天的 status,這個時候緩存區裏與 working 目錄文件有差別了,但 working 目錄文件並無改變;
git checkout a.js
,經過 checkout 命令,將 a.js 回滾(b.js 不變),working 目錄文件改變;
git reset C
,回到今天的文件狀態,此時緩存區的內容又和 working 目錄保持一致,除了 a.js 文件,由於它已經改變了;
git commit -a -m "message here"
,將找回的 a.js 文件提交新 commit;
git checkout master && git merge test && git branch -d test
,切換分支,合併分支,刪除分支,一鼓作氣。
我用了 git 這麼久,乾貨全在這了,有些關於 git 的東西我很是想去弄懂,但苦於沒有找到合理的藉口,好比到底 .git 文件夾有哪些東西,都有什麼用。哪天以後,當我有了需求,我就會去查資料學習了。共勉!
歡迎來個人博客討論。