1. 遠程倉庫-GitHub
Git是分布式版本控制系統,同一個Git倉庫,可以分布到不同的機器上。怎么分布呢?最早,肯定只有一台機器有一個原始版本庫,此后,別的機器可以“克隆”這個原始版本庫,而且每台機器的版本庫其實都是一樣的,並沒有主次之分。
實際情況往往是這樣,找一台電腦充當服務器的角色,每天24小時開機,其他每個人都從這個“服務器”倉庫克隆一份到自己的電腦上,並且各自把各自的提交推送到服務器倉庫里,也從服務器倉庫中拉取別人的提交。
1.1 本地電腦如何關聯GitHub?
由於你的本地Git倉庫和GitHub倉庫之間的傳輸是通過SSH加密的,所以,需要一點設置:
- 創建SSH Key
$ ssh-keygen -t rsa -C "haochen273@gmail.com"
一路回車就可以了
如果一切順利的話,可以在用戶主目錄(C:\Users\haoch.ssh)里找到.ssh目錄,里面有id_rsa和id_rsa.pub兩個文件,這兩個就是SSH Key的秘鑰對,id_rsa是私鑰,不能泄露出去,id_rsa.pub是公鑰,可以放心地告訴任何人。
- 設置GitHub的SSH Key
在GitHub主頁找到Account Seetings, SSH Key頁面:將id_rsa.pub的內容粘貼到里面即可Add

GitHub為什么要用SSH Key呢?
因為GitHub需要識別出你推送的提交確實是你推送的,而不是別人冒充的,而Git支持SSH協議,所以,GitHub只要知道了你的公鑰,就可以確認只有你自己才能推送。
當然,GitHub允許你添加多個Key。假定你有若干電腦,你一會兒在公司提交,一會兒在家里提交,只要把每台電腦的Key都添加到GitHub,就可以在每台電腦上往GitHub推送了。
1.2. 創建並操控遠程庫GitHub
在GitHub中創建一個遠程庫名為learngit:

(1) 把一個已有的本地倉庫與之關聯,然后,把本地倉庫的內容推送到GitHub倉庫。:本地->遠程
- 關聯
在本地庫運行命令:
$ git remote add origin git@github.com:haochen95/learngit.git
添加后,遠程庫的名字就是origin,這是Git默認的叫法,也可以改成別的,但是origin這個名字一看就知道是遠程庫。
- 推送
使用命令git push -u origin master -f
$ git push -u origin master -f
Enumerating objects: 23, done.
Counting objects: 100% (23/23), done.
Delta compression using up to 8 threads
Compressing objects: 100% (17/17), done.
Writing objects: 100% (23/23), 1.82 KiB | 465.00 KiB/s, done.
Total 23 (delta 5), reused 0 (delta 0)
remote: Resolving deltas: 100% (5/5), done.
To github.com:haochen95/learngit.git
+ cc71eae...1f108b4 master -> master (forced update)
Branch 'master' set up to track remote branch 'master' from 'origin'.
把本地庫的內容推送到遠程,用git push命令,實際上是把當前分支master推送到遠程。
由於遠程庫是空的,我們第一次推送master分支時,加上了-u參數,Git不但會把本地的master分支內容推送的遠程新的master分支,還會把本地的master分支和遠程的master分支關聯起來,在以后的推送或者拉取時就可以簡化命令。
成功后的頁面

- 以后的每次提交只寫一個命令
$ git push origin master
把本地master分支的最新修改推送至GitHub,現在,你就擁有了真正的分布式版本庫!
(2) 從GitHub克隆庫到本地: 遠程->本地
現在我們另外創建一個空文件夾: D:\git_clone
克隆的代碼是: $ git clone git@github.com:haochen/learngit.git
$ git clone git@github.com:haochen95/learngit.git
Cloning into 'learngit'...
remote: Enumerating objects: 23, done.
remote: Counting objects: 100% (23/23), done.
remote: Compressing objects: 100% (12/12), done.
Receiving objects: 100% (23/23), done.
Resolving deltas: 100% (5/5), done.
remote: Total 23 (delta 5), reused 23 (delta 5), pack-reused 0
你看,本地的庫里面就有了跟GitHub一樣的內容

2. Git分支管理(重要)
創建了一個屬於你自己的分支,別人看不到,還繼續在原來的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到開發完畢后,再一次性合並到原來的分支上,這樣,既安全,又不影響別人工作。
2.1. 創建和合並分支
在版本回退里,你已經知道,每次提交,Git都把它們串成一條時間線,這條時間線就是一個分支。截止到目前,只有一條時間線,在Git里,這個分支叫主分支,即master分支。HEAD嚴格來說不是指向提交,而是指向master,master才是指向提交的,所以,HEAD指向的就是當前分支
一開始的時候,master分支是一條線,Git用master指向最新的提交,再用HEAD指向master,就能確定當前分支,以及當前分支的提交點:

每次提交,master分支都會向前移動一步,這樣,隨着你不斷提交,master分支的線也越來越長:

1)增加分支的原理
我們創建新的分支,例如dev時,Git新建了一個指針叫dev,指向master相同的提交,再把HEAD指向dev,就表示當前分支在dev`上

增加分支的原理: 增加一個指針,更改HEAD的指向
2)增加分支后的提交變化
現在開始,對工作區的修改和提交就是針對dev分支了,比如新提交一次后,dev指針往前移動一步,而master指針不變:

3) 分支合並
就是直接把master指向dev的當前提交,就完成了合並

合並分支的原理: 改改指針(dev->master)
4)分支刪除
刪除dev分支就是把dev指針給刪掉,刪掉后,我們就剩下了一條master分支

代碼測試
1. 創建分支
$ git checkout -b dev
Switched to a new branch 'dev'
git checkout命令加上-b參數表示創建並切換
2. 查看分支
$ git branch
* dev
master
3. 在分支上提交
增加一個文件命名為love.txt並且提交
$ git add love.txt
$ git commit -m "love"
[dev b3045f0] love
1 file changed, 1 insertion(+)
create mode 100644 love.txt
4. 從dev分支切換回master分支
$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
再次查看文件夾發現沒有love.txt文件,因為那個提交是在dev分支上,而master分支此刻的提交點並沒有變

5. master和dev分支合並
$ git merge dev
Updating 1f108b4..b3045f0
Fast-forward
love.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 love.txt
6. 刪除dev分支
$ git branch -d dev
Deleted branch dev (was b3045f0).
7. 再次查看branch信息
$ git branch
* master
2.2. 解決合並沖突
問題: 在分支上提交一個修改,轉到master后又提交一個修改,兩個修改都在同一個位置,請問如何合並?

現在,master分支和feature1分支各自都分別有新的提交,變成了這樣:

這種情況下,Git無法執行“快速合並”,只能試圖把各自的修改合並起來,但這種合並就可能會有沖突,我們試試看
$ git merge feature1
Auto-merging love.txt
CONFLICT (content): Merge conflict in love.txt
Automatic merge failed; fix conflicts and then commit the result.
需要手動解決沖突
通過git status查看狀態
$ 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: love.txt
no changes added to commit (use "git add" and/or "git commit -a")
我們可以直接查看love.txt的內容:
<<<<<<< HEAD
I love you
=======
I come from china
>>>>>>> feature1
我們重新修改為
I come from china
然后再提交
$ git commit -m "loving"
[master e6c7c5f] loving
現在,master分支和feature1分支變成了下圖所示:

我們可以通過命令$ git log --graph --pretty=oneline --abbrev-commit 查看分支情況
$ git log --graph --pretty=oneline --abbrev-commit
* e6c7c5f (HEAD -> master) loving
|\
| * 445380a (feature1) from china
* | 62b6881 love you
|/
* b3045f0 love
* 1f108b4 (origin/master) remove text.txt
* f2863ca new text
* aa4f64e git tracks
* 45700b2 how it works
* 5103166 delete software
* 9fbb435 github infor
* 4fb84da add new line
* f7f8050 write to readme
2.3. 分支管理策略
master分支應該是非常穩定的,也就是僅用來發布新版本,平時不能在上面干活;- 干活都在
dev分支上,也就是說,dev分支是不穩定的,到某個時候,比如1.0版本發布時,再把dev分支合並到master上,在master分支發布1.0版本 - 你和你的小伙伴們每個人都在
dev分支上干活,每個人都有自己的分支,時不時地往dev分支上合並就可以了。 - 使用
git merge --no-ff -m "merge with no-ff" dev合並會留下歷史信息,建議這樣用

2.4. Bug分支
假設,你正正在完成love.txt的編輯工作,但是這個文件需要2天才能完成,但是現在老板給你說readme.txt有一個錯誤(I love USA)你需要更改這個bug, 那怎么辦? 你想創建一個分支issue-101來修復它,但是,等等,當前正在feature1上進行的工作還沒有提交:
$ git status
On branch feature1
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: love.txt
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: readme.txt
1) git stash
這個功能是把當前工作現場“儲藏”起來,等以后恢復現場后繼續工作:
$ git stash
Saved working directory and index state WIP on feature1: f1f394f modifyed
現在,用git status查看工作區,就是干凈的(除非有沒有被Git管理的文件),因此可以放心地創建分支來修復bug。
2) 創建issue分支解決bug
在master上創建分支
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 4 commits.
(use "git push" to publish your local commits)
$ git checkout -b issue
Switched to a new branch 'issue'
然后修改readme.txt的刪除I love China,在提交
$ git commit -m "fix issue101"
[issue bdc0b2e] fix issue101
1 file changed, 1 deletion(-)
然后轉到master合並分支
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 4 commits.
(use "git push" to publish your local commits)
$ git merge --no-ff -m "merged bug fix 101" issue
Merge made by the 'recursive' strategy.
readme.txt | 1 -
1 file changed, 1 deletion(-)
哈哈,Bug解決啦,是時候回到feature1分支繼續工作啦
$ git checkout feature1
Switched to branch 'feature1'
$ git status
On branch feature1
nothing to commit, working tree clean
工作區是干凈的,剛才的工作現場存到哪去了?用git stash list命令看看
$ git stash list
stash@{0}: WIP on feature1: f1f394f modifyed
工作現場還在,Git把stash內容存在某個地方了,但是需要恢復一下,有兩個辦法
1)恢復辦法1:git stash apply
用git stash apply恢復,但是恢復后,stash內容並不刪除,你需要用git stash drop來刪除;
2)恢復方法2: git stash pop
git stash pop,恢復的同時把stash內容也刪了
2.5. Feature分支
添加一個新功能時,你肯定不希望因為一些實驗性質的代碼,把主分支搞亂了,所以,每添加一個新功能,最好新建一個feature分支,在上面開發,完成后,合並,最后,刪除該feature分支。
