這篇文章主要介紹Git分布式版本管理與集中式管理的一些差異,總結下Git常用命令作為日后的速查表,最后介紹Git進階的一些案例。
本文分為以下幾個部分:
- Git與SVN差異
- Git常用命令
- Git進階指南
Git與SVN差異
Git的第一個版本是Linux之父Linus Torvalds親手操刀設計和實現的,Git 基於 DAG 結構 (Directed Acyclic Graph),其運行起來相當的快,它已經是現在的主流。
Git 和 SVN 思想最大的差別有四個:
- 去中心化
- 直接記錄快照,而非差異
- 不一樣的分支概念
- 三個文件狀態
去中心化
Git:是一個DVCS(分布式版本管理系統),在技術層面上並不存在一個像中心倉庫這樣的東西 , 所有的數據都在本地,不存在誰是中心
圖中每個開發者拉取(pull)並推送(push)到origin。但除了這種集中式的推送拉取關系,每個開發者也可能會從其他的開發者處拉取代碼的變更,從技術上講,這意味着Alice定義了一個名為bob的Git的remote,它指向了Bob的軟件倉庫。反之亦然。
直接記錄快照,而非差異
Git每一個版本都是直接記錄快照,而非文件的差異。 下面兩個對比圖在網上是廣為流傳大家應該熟悉:
SVN:
Git:
Git使用SHA-1算法計算數據的校驗和,通過文件的內容或目錄計算出SHA-1哈希值,作為指紋字符串,每個Version 都是一個快照。
不一樣的分支概念
Git的分支本質是一個指向提交快照的指針,是從某個提交快照往回看的歷史。當創建/切換分支的時候,只是變換了指針指向而已.而SVN創建一個分支, 是的的確確的復制了一份文件。
三個文件狀態
在Git中文件有三種狀態:
- 已提交(committed):該文件被安全地保存在了本地數據庫
- 已修改(modified):修改了某個文件,但還沒有保存
- 已暫存(staged):把已修改的文件放下下次保存的清單中
Git常用命令
創建
復制一個已創建的倉庫:
1 $ git clone ssh://user@domain.com/repo.git
創建一個新的本地倉庫:
1 $ git init
本地修改
顯示工作路徑下已修改的文件:
1 $ git status
顯示與上次提交版本文件的不同:
1 $ git diff
把當前所有修改添加到下次提交中:
1 $ git add
把對某個文件的修改添加到下次提交中:
1 $ git add -p <file>
提交本地的所有修改:
1 $ git commit -a
提交之前已標記的變化:
1 $ git commit
附加消息(注釋)提交:
1 $ git commit -m 'message here'
提交,並將提交時間設置為之前的某個日期:
1 git commit --date="`date --date='n day ago'`" -am "Commit Message"
修改上次提交
請勿修改已發布的提交記錄!
1 $ git commit --amend
把當前分支中未提交的修改移動到其他分支
1 $ git stash 2 $ git checkout branch2 3 $ git stash pop
搜索
從當前目錄的所有文件中查找文本內容:
1 $ git grep "Hello"
在某一版本中搜索文本:
1 $ git grep "Hello" v2.5
提交歷史
從最新提交開始,顯示所有的提交記錄(顯示hash, 作者信息,提交的標題和時間):
1 $ git log
顯示所有提交(僅顯示提交的hash和message):
1 $ git log --oneline
顯示某個用戶的所有提交:
1 $ git log --author="username"
顯示某個文件的所有修改:
1 $ git log -p <file>
誰,在什么時間,修改了文件的什么內容:
1 $ git blame <file>
分支與標簽
列出所有的分支:
1 $ git branch
切換分支:
1 $ git checkout <branch>
創建並切換到新分支:
1 $ git checkout -b <branch>
基於當前分支創建新分支:
1 $ git branch <new-branch>
基於遠程分支創建新的可追溯的分支:
1 $ git branch --track <new-branch> <remote-branch>
刪除本地分支:
1 $ git branch -d <branch>
給當前版本打標簽:
1 $ git tag <tag-name>
更新與發布
列出當前配置的遠程端:
1 $ git remote -v
顯示遠程端的信息:
1 $ git remote show <remote>
添加新的遠程端:
1 $ git remote add <remote> <url>
下載遠程端版本,但不合並到HEAD中:
1 $ git fetch <remote>
下載遠程端版本,並自動與HEAD版本合並:
1 $ git remote pull <remote> <url>
將遠程端版本合並到本地版本中:
1 $ git pull origin master
將本地版本發布到遠程端:
1 $ git push remote <remote> <branch>
刪除遠程端分支:
1 $ git push <remote> :<branch> (since Git v1.5.0) 2 或 3 $ git push <remote> --delete <branch> (since Git v1.7.0)
發布標簽:
1 $ git push --tags
合並與重置
將分支合並到當前HEAD中:
1 $ git merge <branch>
將當前HEAD版本重置到分支中:
請勿重置已發布的提交!
1 $ git rebase <branch>
退出重置:
1 $ git rebase --abort
解決沖突后繼續重置:
1 $ git rebase --continue
使用配置好的merge tool 解決沖突:
1 $ git mergetool
在編輯器中手動解決沖突后,標記文件為已解決沖突
1 $ git add <resolved-file> 2 $ git rm <resolved-file>
撤銷
放棄工作目錄下的所有修改:
1 $ git reset --hard HEAD
移除緩存區的所有文件(i.e. 撤銷上次git add):
1 $ git reset HEAD
放棄某個文件的所有本地修改:
1 $ git checkout HEAD <file>
重置一個提交(通過創建一個截然不同的新提交)
1 $ git revert <commit>
將HEAD重置到指定的版本,並拋棄該版本之后的所有修改:
1 $ git reset --hard <commit>
將HEAD重置到上一次提交的版本,並將之后的修改標記為未添加到緩存區的修改:
1 $ git reset <commit>
將HEAD重置到上一次提交的版本,並保留未提交的本地修改:
1 $ git reset --keep <commit>
Git進階指南
問:如何修改 origin 倉庫信息?
1、添加 origin 倉庫信息
1 $ git remote add origin <git倉庫地址>
2、查看 origin 倉庫信息
1 # 以下三種方式均可 2 $ git config get --remote.origin.url 3 $ git remote -v 4 $ git remote show origin
3、刪除 origin 倉庫信息
1 $ git remote rm origin
問:如何配置 git ssh keys ?
在本地生成 ssh 私鑰 / 公鑰 文件
將「公鑰」添加到 git 服務(github、gitlab、coding.net 等)網站后台
測試 git ssh 連接是否成功
接下來以添加 github ssh keys 為例,請注意替換 github 文件名。
注:如果對密鑰機制不熟悉,建議不要指定 -f 參數,直接使用默認的 id_rsa 文件名。
1 # 運行以下命令,一直回車,文件名可隨意指定 2 $ ssh-keygen -t rsa -b 4096 -C "kaiye@macbook" -f ~/.ssh/github 3 4 # 如果不是默認密鑰 id_rsa ,則需要以下命令注冊密鑰文件,-K 參數將密鑰存入 Mac Keychain 5 $ ssh-add -K ~/.ssh/github 6 7 # 將 pub 公鑰的內容粘貼到線上網站的后台 8 $ cat ~/.ssh/github.pub 9 10 # 測試 git ssh 是否連接成功 11 $ ssh -T git@github.com
問:如何撤銷修改?
修改包含四種情況,需單獨區分。
1、新建的文件和目錄,且從未提交至版本庫
此類文件的狀態為 Untracked files ,撤銷方法如下:
1 $ git clean -fd .
其中,. 表示當前目錄及所有子目錄中的文件,也可以直接指定對應的文件路徑,以下其他情況類似。
2、提交過版本庫,但未提交至暫存區的文件(未執行 git add)
此類文件的狀態為Changes not staged for commit
,撤銷方法:
1 $ git checkout .
3、已提交至暫存區的文件
此類文件的狀態為 Changes to be committed,撤銷方法:
1 $ git reset .
執行之后文件將會回到以上的 1 或者 2 狀態,可繼續按以上步驟執行撤銷,若 git reset 同時加上 –hard 參數,將會把修改過的文件也還原成版本庫中的版本。
4、已提交至版本庫(執行了 git commit)
每次提交都會生成一個 hash 版本號,通過以下命令可查閱版本號並將其回滾:
1 $ git log 2 $ git reset <版本號>
如果需要「回滾至上一次提交」,可直接使用以下命令:
1 $ git reset head~1
執行之后,再按照 1 或者 2 狀態進行處理即可,如果回滾之后的代碼同時需要提交至 origin 倉庫(即回滾 origin 線上倉庫的代碼),需要使用 -f 強制提交參數,且當前用戶需要具備「強制提交的權限」。
5、如果回滾了之后又不想回滾了怎么辦?
如果是以上的情況 1 或者 2,只能歇屁了,因為修改沒入過版本庫,無法回滾。
如果是情況 4,回滾之后通過 git log 將看不到回滾之前的版本號,但可通過 git reflog 命令(所有使用過的版本號)找到回滾之前的版本號,然后 git reset <版本號> 。
問:遇到沖突了怎么解決?
兩個分支進行合並時(通常是 git pull 時),可能會遇到沖突,同時被修改的文件會進入 Unmerged 狀態,需要解決沖突。
1、最快的辦法
大部分時候,「最快解決沖突」的辦法是:使用當前 HEAD 的版本(ours),或使用合並進來的分支版本(theirs)。
1 # 使用當前分支 HEAD 版本,通常是沖突源文件的 <<<<<<< 標記部分,======= 的上方 2 $ git checkout --ours <文件名> 3 4 # 使用合並分支版本,通常是源沖突文件的 >>>>>>> 標記部分 5 $ git checkout --theirs <文件名> 6 7 # 標記為解決狀態加入暫存區 8 $ git add <文件名>
2、最通用的辦法
用編輯器打開沖突的源文件進行修改,可能會發生遺留,且體驗不好,通常需要借助 git mergetool 命令。
在 Mac 系統下,運行 git mergetool <文件名> 可以開啟配置的第三方工具進行 merge,默認的是 FileMerge 應用程序,還可以配置成 Meld 或 kdiff3,體驗更佳。
3、最好的習慣
有三個好的習慣,可以減少代碼的沖突:
在開始修改代碼前先 git pull 一下;
將業務代碼進行划分,盡量不要多個人在同一時間段修改同一文件;
通過Gitflow 工作流也可以提升 git流程效率,減少發生沖突的可能性。
4、最復雜的情況
如果你的項目周期比較長,還應該養成「定期 rebase 的習慣」,git pull –rebase 可以讓分支的代碼和 origin 倉庫的代碼保持兼容,同時還不會破壞線上代碼的可靠性。
它的大概原理是,先將 origin 倉庫的代碼按 origin 的時間流在本地分支中提交,再將本地分支的修改記錄追加到 origin 分支上。如果發生沖突,則可以即時的發現問題並解決,否則到項目上線時再解決沖突,可能會發生額外的風險。
rebase 大概的操作步驟如下:
1 # 將當前分支的版本追加到從遠程 pull 回來的節點之后 2 $ git pull --rebase 3 4 # 若發生沖突,則按以上其他方法進行解決,解決后繼續 5 $ git rebase --continue 6 7 # 直到所有沖突得以解決,待項目最后上線前再執行 8 $ git push origin 9 10 # 若多次提交修改了同一文件,可能需要直接跳過后續提交,按提示操作即可 11 $ git rebase --skip
問:如何在不提交修改的前提下,執行 pull / merge 等操作?
有些修改沒有完全完成之前,可能不需要提交到版本庫,圡方法是將修改的文件 copy 到 git 倉庫之外的目錄臨時存放,pull / merge 操作完成之后,再 copy 回來。
這樣的做法一個是效率不高,另外一個可能會遺漏潛在的沖突。此類需求最好是通過 git stash 命令來完成,它可以將當前工作狀態(WIP,work in progress)臨時存放在 stash 隊列中,待操作完成后再從 stash 隊列中重新應用這些修改。
以下是 git stash 常用命令:
1 # 查看 stash 隊列中已暫存了多少 WIP 2 $ git stash list 3 4 # 恢復上一次的 WIP 狀態,並從隊列中移除 5 $ git stash pop 6 7 # 添加當前 WIP,注意:未提交到版本庫的文件會自動忽略,只要不運行 git clean -fd . 就不會丟失 8 $ git stash 9 10 # 恢復指定編號的 WIP,同時從隊列中移除 11 $ git stash pop stash@{num} 12 13 # 恢復指定編號的 WIP,但不從隊列中移除 14 $ git stash apply stash@{num}
問:如何在 git log 中查看修改的文件列表?
默認的 git log 會顯示較全的信息,且不包含文件列表。使用 –name-status 可以看到修改的文件列表,使用 –oneline 可以將參數簡化成一行。
1 $ git log --name-status --oneline
每次手動加上參數很麻煩,可以通過自定義快捷命令的方式來簡化操作:
1 $ git config --global alias.ls 'log --name-status --oneline --graph'
運行以上配置后,可通過 git ls 命令來實現「自定義 git log」效果,通過該方法也可以創建 git st 、 git ci 等一系列命令,以便沿用 svn 命令行習慣。
1 $ git config --global alias.st 'status --porcelain'
更多 git log 參數,可通過 git help log 查看手冊。
如果是看上一次提交的版本日志,直接運行 git show 即可。
此外,如果你的 Mac 安裝了zsh(參考《全新Mac安裝指南(編程篇),那么可以直接使用 gst、glog 等一系列快捷命令,詳情見此列表:Plugin:git 。
問:git submodule update 時出錯怎么解決?
例如,在執行 git submodule update 時有以下錯誤信息:
fatal: reference is not a tree:
f869da471c5d8a185cd110bbe4842d6757b002f5
Unable to checkout
‘f869da471c5d8a185cd110bbe4842d6757b002f5’ in submodule
path ‘source/i18n-php-server’
在此例中,發生以上錯誤是因為 i18n-php-server 子倉庫在某電腦 A 的「本地」commit 了新的版本 「f869da471c5d8a185cd110bbe4842d6757b002f5」,且該次 commit 未 push origin。但其父級倉庫 i18n-www 中引用了該子倉庫的版本號,且將引用記錄 push origin,導致其他客戶機無法 update 。
解決方法,在電腦 A 上將 i18n-php-server 版本庫 push origin 后,在其他客戶機上執行 git submodule update 。或者用以上提到的 git reset 方法,將子倉庫的引用版本號還原成 origin 上存在的最新版本號。
其他問題
設置本地分支與遠程分支保持同步,在第一次 git push 的時候帶上 -u 參數即可
1 $ git push origin master -u
支持中文目錄與文件名的顯示(git 默認將非 ASCII 編碼的目錄與文件名以八進制編碼展示)
1 $ git config core.quotepath off
常用的打 tag 操作,更多請查看《Git 基礎 - 打標簽》
1 # 列出所有本地 tag 2 $ git tag 3 4 # 本地新增一個 tag,推送至 origin 服務器 5 $ git tag -a v1.0.0 -m 'tag description' 6 $ git push origin v1.0.0 7 8 # 刪除本地與 origin tag 9 $ git tag -d v1.0.0 10 $ git push origin --delete v1.0.0
使用 git GUI 客戶端(如,SoureTree、Github Desktop)能極大的提升分支管理效率。分支合並操作通常只有兩種情況:從 origin merge 到本地,使用 git pull 即可;從另外一個本地分支 merge 到當前分支,使用 git merge <分支名>,以下是常用命令:
1 # 新建分支 branch1,並切換過去 2 $ git checkout -b branch1 3 4 # 查看所有本地與遠程分支 5 $ git branch -a 6 7 # 修改完成后,切換回 master 分支,將 branch1 分支合並進來 8 $ git checkout master 9 $ git merge branch1 10 11 # 刪除已完成合並的分支 branch1 12 $ git branch -d branch1
參考資料
- Pro Git 簡體中文版
- Git權威指南
- 命令行man手冊