這一版主要是參考廖海峯老師的博客,本身作了梳理,以後會根據使用經驗持續更新。git
版本庫 又名倉庫,英文名repository,你能夠簡單理解成一個目錄,這個目錄裏面的全部文件均可以被Git管理起來,每一個文件的修改、刪除,Git都能跟蹤,以便任什麼時候刻均可以追蹤歷史,或者在未來某個時刻能夠「還原」。github
# 安裝 $ sudo apt-get install git # 配置: --global表示全局生效,對於這個機器的全部倉庫都生效 # 分佈式版本控制,因此須要用 名字與郵箱 標記機器 $ git config --global user.name "Your Name" $ git config --global user.email "email@example.com" # 初始化Git倉庫: git init # 添加文件到Git倉庫【版本庫】,分兩步: git add <file> # 可反覆屢次使用,添加多個文件; git rm <file> # 從版本庫中刪除該文件 git commit -m <message> " 1 file changed, 1 insertion(+), 1 deletion(-)" # 查看【工做區】的狀態 git status "On branch master, Changes to be committed: modified: readme.txt" git diff <file> # 顯示從最近到最遠的提交日誌,commit的詳細狀況 git log --pretty=oneline # 穿梭前,以便肯定要回退到哪一個版本。 git log --graph --pretty=oneline --abbrev-commit git reflog # 重返將來,查看命令歷史,以便肯定要回到將來的哪一個版本 # commit id,用HEAD表示當前版本,上一個版本就是HEAD^,上上一個版本就是HEAD^^,HEAD~100 git reset --hard HEAD^/1094a git reset HEAD <file> "HEAD is now at e475afc add distributed" # 當你改亂了**工做區**某個文件的內容,想直接丟棄工做區的修改時 git checkout -- file # 當你不但改亂了工做區某個文件的內容,還添加到了**暫存區**時,想丟棄修改,分兩步,第一步用命令git reset HEAD <file>,就回到了場景1,第二步按場景1操做 git reset HEAD <file> git checkout -- file # 已經提交了不合適的修改到**版本庫(本地)**時,想要撤銷本次提交,參考版本回退一節,不過前提是沒有推送到遠程庫
由於GitHub須要識別出你推送的提交確實是你推送的,而不是別人冒充的,而Git支持SSH協議,因此,GitHub只要知道了你的公鑰,就能夠確認只有你本身才能推送。
GitHub容許你添加多個Key。假定你有若干電腦,你一下子在公司提交,一下子在家裏提交,只要把每臺電腦的Key都添加到GitHub,就能夠在每臺電腦上往GitHub推送了。web
$ ssh-keygen -t rsa -C "youremail@example.com" "在用戶主目錄裏找到.ssh目錄,裏面有id_rsa和id_rsa.pub兩個文件,這兩個就是SSH Key的祕鑰對,id_rsa是私鑰,不能泄露出去,id_rsa.pub是公鑰,能夠放心地告訴任何人。" # 添加遠程庫,遠程庫的名字就是origin # 把本地庫的內容推送到遠程,用git push命令,其實是把當前分支master推送到遠程。 # 因爲遠程庫是空的,第一次推送master分支時,加上了-u參數,把本地的master分支和遠程的master分支關聯起來 # 這是由於Git使用SSH鏈接,而SSH鏈接在第一次驗證GitHub服務器的Key時,須要你確認GitHub的Key的指紋信息是否真的來自GitHub的服務器,輸入yes回車便可。 $ git init $ git remote add origin git@github.com:michaelliao/learngit.git $ git push -u origin master # 從遠程庫克隆 # Git支持多種協議,默認的git://使用ssh,但也可使用https等其餘協議。 # https速度慢,每次推送都必須輸入口令,可是在某些只開放http端口的公司內部就沒法使用ssh協議而只能用https。 $ git clone git@github.com:michaelliao/gitskills.git
主分支,即master分支
HEAD嚴格來講不是指向提交,而是指向master,master纔是指向提交的,因此,HEAD指向的就是當前分支shell
$ git checkout -b dev # 或者 $ git branch dev $ git checkout dev # 切換分支:git checkout <name>或者git switch <name> $ git branch # 列出全部分支,當前分支前面會標一個*號。 $ git add readme.txt $ git checkout master # 切換回master分支 $ git merge dev # 把dev分支的工做成果合併到master分支,git merge命令用於合併指定分支到當前分支 $ git branch -d dev $ git switch -c dev # 建立並切換到新的dev分支,create $ git switch master # 直接切換到已有的master分支
**注意:**切換分支使用git checkout ,而前面講過的撤銷修改則是git checkout – 服務器
# 「快速合併」僅適用於線性狀況 Git還會自動提示咱們當前master分支比遠程的master分支要超前1個提交。 # 當出現分叉時沒法直接合並,發生衝突, $ git status "On branch master Your branch is ahead of 'origin/master' by 2 commits. (use "git push" to publish your local commits) You have unmerged paths. (fix conflicts and run "git commit") (use "git merge --abort" to abort the merge) Unmerged paths: (use "git add <file>..." to mark resolution) both modified: readme.txt no changes added to commit (use "git add" and/or "git commit -a")" $ vi <file> # 打開衝突文件,這時候有標註了,修改 $ git add readme.txt # 再提交 $ git commit -m "conflict fixed" "* cf810e4 (HEAD -> master) conflict fixed |\ | * 14096d0 (feature1) AND simple * | 5dc6824 & simple |/ * b17d20e branch test * d46f35e (origin/master) remove test.txt * b84166e add test.txt * 519219b git tracks changes * e43a48b understand how stage works * 1094adb append GPL * e475afc add distributed * eaadf4e wrote a readme file" # 一般,合併分支時,若是可能,Git會用Fast forward模式,但這種模式下,刪除分支後,會丟掉分支信息。(對於線性模式) # 若是要強制禁用Fast forward模式,Git就會在merge時生成一個新的commit,這樣,從分支歷史上就能夠看出分支信息。 # 合併分支時,加上--no-ff參數就能夠用普通模式合併,合併後的歷史有分支,能看出來曾經作過合併,而fast forward合併就看不出來曾經作過合併。 $ git merge --no-ff -m "merge with no-ff" dev # 由於要建立新的分支,因此要加-m $ git log --graph --pretty=oneline --abbrev-commit "* e1e9c68 (HEAD -> master) merge with no-ff |\ | * f52c633 (dev) add merge |/ * cf810e4 conflict fixed"
git stash
一下,而後去修復bug,修復後,再git stash pop
,回到工做現場;git cherry-pick
命令,把bug提交的修改「複製」到當前分支,避免重複勞動。# 當前正在dev上進行的工做尚未提交 $ git status # 能夠看出本地還有任務沒有commit掉 $ git stash # 能夠把當前工做現場「儲藏」起來,等之後恢復現場後繼續工做 $ git status # 此時工做區變乾淨的(除非有沒有被Git管理的文件),所以能夠放心地建立分支來修復bug。 # 首先肯定要在哪一個分支上修復bug,假定須要在master分支上修復,就從master建立臨時分支: $ git checkout master $ git checkout -b issue-101 # 如今修復bug $ git add readme.txt $ git commit -m "fix bug 101" # 修復完成後,切換到master分支,並完成合並,最後刪除issue-101分支 $ git switch master $ git merge --no-ff -m "merged bug fix 101" issue-101 # 接着回到dev分支幹活了! $ git switch dev $ git status # 工做區是乾淨的,剛纔的工做現場存到哪去了?用git stash list命令看看: $ git stash list #工做現場還在,Git把stash內容存在某個地方了 # 須要恢復一下,有兩個辦法: # 一是用git stash apply恢復,可是恢復後,stash內容並不刪除,你須要用git stash drop來刪除; $ git stash apply $ git stash drop # 另外一種方式是用git stash pop,恢復的同時把stash內容也刪了: $ git stash pop # 看不到任何stash內容 $ git stash list # 若是是屢次stash,恢復的時候,先用git stash list查看,而後恢復指定的stash,用命令: $ git stash list $ git stash apply stash@{0} # dev分支是早期從master分支分出來的,因此,這個bug其實在當前dev分支上也存在。那怎麼在dev分支上修復一樣的bug? # 方法1:是重複操做一次,提交 # 方法2:一樣的bug,要在dev上修復,咱們只須要把4c805e2 fix bug 101這個提交所作的修改「複製」到dev分支。 # 注意:咱們只想複製4c805e2 fix bug 101這個提交**所作的修改**,並非把整個master分支merge過來。 # cherry-pick命令,讓咱們能複製一個特定的提交到當前分支: $ git branch $ git cherry-pick 4c805e2 # Git自動給dev分支作了一次提交,注意此次提交的commit是1d4b803,它並不一樣於master的4c805e2,由於這兩個commit只是改動相同,但確實是兩個不一樣的commit。用git cherry-pick,咱們就不須要在dev分支上手動再把修bug的過程重複一遍。 # 方法3:既然能夠在master分支上修復bug後,在dev分支上能夠「重放」這個修復過程,那麼直接在dev分支上修復bug,而後在master分支上「重放」行不行?固然能夠,不過你仍然須要git stash命令保存現場,才能從dev分支切換到master分支。
添加一個新功能時,你確定不但願由於一些實驗性質的代碼,把主分支搞亂了,因此,每添加一個新功能,最好新建一個feature分支,在上面開發,完成後,合併,最後,刪除該feature分支。app
git branch -D
強行刪除。# 準備開發 $ git switch -c feature-vulcan # 開發完畢 $ git add vulcan.py $ git status $ git commit -m "add feature vulcan" # 切回dev,準備合併: $ git switch dev # 一切順利的話,feature分支和bug分支是相似的,合併,而後刪除。 $ git merge dev # or --no-ff # 若是要丟棄一個沒有被合併過的分支 $ git branch -D feature-vulcan
當你從遠程倉庫克隆時,實際上Git自動把本地的master
分支和遠程的master
分支對應起來了,而且,遠程倉庫的默認名稱是origin
。ssh
master
分支是主分支,所以要時刻與遠程同步;dev
分支是開發分支,團隊全部成員都須要在上面工做,因此也須要與遠程同步;$ git remote # 查看遠程庫的信息 $ git remote -v # 更詳細 # 推送分支,就是把該分支上的全部本地提交推送到遠程庫。 # 推送時,要指定本地分支,Git就會把該分支推送到遠程庫對應的遠程分支上: $ git push origin master # 若是要推送其餘分支,好比dev,就改爲: $ git push origin dev
共同在dev上進行開發分佈式
$ git clone git@github.com:michaelliao/learngit.git $ git branch # 此時,只有master # 要在dev分支上開發,就必須關聯遠程origin的dev分支到本地,因而他用這個命令建立本地dev分支 $ git checkout -b dev origin/dev # 注意:origin/dev的寫法,使用斜杆隔開的 $ git add env.txt $ git commit -m "add env" $ git push origin dev # 碰巧你也對一樣的文件做了修改,則會推送失敗,由於你的小夥伴的最新提交和你試圖推送的提交有衝突 $ git push origin dev "To github.com:michaelliao/learngit.git ! [rejected] dev -> dev (non-fast-forward) error: failed to push some refs to 'git@github.com:michaelliao/learngit.git' hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart. Integrate the remote changes (e.g. hint: 'git pull ...') before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details." # 先用git pull把最新的提交從origin/dev抓下來,而後,在本地合併,解決衝突,再推送: $ git pull "There is no tracking information for the current branch. Please specify which branch you want to merge with. See git-pull(1) for details. git pull <remote> <branch> If you wish to set tracking information for this branch you can do so with: git branch --set-upstream-to=origin/<branch> dev" # git pull也失敗了,緣由是沒有指定本地dev分支與遠程origin/dev分支的連接 # 在本地建立和遠程分支對應的分支 $ git checkout -b branch-name origin/branch-name # 本地和遠程分支的名稱最好一致 # 創建本地分支和遠程分支的關聯 $ git branch --set-upstream-to=origin/dev dev # 設置dev和origin/dev的連接 $ git pull # 若是合併有衝突,須要手動解決,解決的方法和分支管理中的解決衝突徹底同樣 $ git commit -m "fix env conflict" $ git push origin dev
所以,多人協做的工做模式一般是這樣:svg
git push origin
推送本身的修改;git pull
試圖合併;git push origin
推送就能成功!若是git pull
提示no tracking information
,則說明本地分支和遠程分支的連接關係沒有建立,用命令git branch --set-upstream-to origin/
。fetch
這就是多人協做的工做模式,一旦熟悉了,就很是簡單。
後push的童鞋不得不先pull,在本地合併,而後才能push成功,分支會很是地亂
$ git log --graph --pretty=oneline --abbrev-commit
rebase的操做,有人把它翻譯成「變基」,看看怎麼把分叉的提交變成直線。
$ git push origin master To github.com:michaelliao/learngit.git ! [rejected] master -> master (fetch first) #這說明有人先於咱們推送了遠程分支 error: failed to push some refs to 'git@github.com:michaelliao/learngit.git' hint: Updates were rejected because **the remote contains work that you do hint: not have locally**. This is usually caused by another repository pushing hint: to the same ref. You may want to first integrate the remote changes hint: (e.g., 'git pull ...') before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details. $ git pull remote: Counting objects: 3, done. remote: Compressing objects: 100% (1/1), done. remote: Total 3 (delta 1), reused 3 (delta 1), pack-reused 0 Unpacking objects: 100% (3/3), done. From github.com:michaelliao/learngit d1be385..f005ed4 master -> origin/master * [new tag] v1.0 -> v1.0 Auto-merging hello.py # 自動提交了 Merge made by the 'recursive' strategy. hello.py | 1 + 1 file changed, 1 insertion(+) $ git status On branch master Your branch is ahead of 'origin/master' by 3 commits. # 加上剛纔合併的提交,如今咱們本地分支比遠程分支超前3個提交。 (use "git push" to publish your local commits) nothing to commit, working tree clean $ git log --graph --pretty=oneline --abbrev-commit * e0ea545 (HEAD -> master) Merge branch 'master' of github.com:michaelliao/learngit # 這個是本地的commit,本題提交3 # 合併分支致使的增多一個commit |\ | * f005ed4 (origin/master) set exit=1 #(origin/master)對應的是遠程的,注意左邊graph,這條線是遠程分支 * | 582d922 add author # 本地提交2 # 這條線是本地分支 * | 8875536 add comment # 本地提交1 |/ * d1be385 init hello # (若是嫌棄太難看了),不然直接push $ git rebase First, rewinding head to replay your work on top of it... Applying: add comment Using index info to reconstruct a base tree... M hello.py Falling back to patching base and 3-way merge... Auto-merging hello.py Applying: add author Using index info to reconstruct a base tree... M hello.py Falling back to patching base and 3-way merge... Auto-merging hello.py $ git log --graph --pretty=oneline --abbrev-commit * 7e61ed4 (HEAD -> master) add author # 本地提交2 * 3611cfe add comment # 本地提交1 # 咱們本地的提交「挪動」了位置,放到了f005ed4 (origin/master) set exit=1以後 * f005ed4 (origin/master) set exit=1 # 能夠認爲rebase的做用是幫咱們在調整了commit順序,原來咱們是新增了2個commit以後,發現和遠程不一樣了,這時候才拉取;可是rebase調整了一下,先幫咱們作了拉取,而後咱們是基於這個最新的拉取再新增2個commit * d1be385 init hello # 這就是rebase操做的特色:把分叉的提交歷史「整理」成一條直線,看上去更直觀。缺點是本地的分叉提交已經被修改過了。 # 最後,經過push操做把本地分支推送到遠程: $ git push origin/master $ git log --graph --pretty=oneline --abbrev-commit * 7e61ed4 (HEAD -> master, origin/master) add author * 3611cfe add comment * f005ed4 set exit=1 * d1be385 init hello
總結一下:
若是你和小夥伴同時開發,push後有三種結果
merge 是相對於兩個分支而言的,好比 local/master vs. local/dev, local/master vs. origin/master.