一、Git和SVN的區別?
這是一個學Git無法繞開的話題,也是面試的常見題,我猜很多人的回答都是百度上直接背的,有了解過SVN底層的實現原理嗎?
SVN是一種集中式版本控制工具,SVN架構如圖:
A、B、C三個開發者如果需要提交自己的代碼到遠程倉庫,必須聯網(上傳),上傳之後SVN倉庫內部做了什麼?
假設用戶A提交代碼,會將用戶A改動過的A.java提交給SVN倉庫,倉庫中記錄的僅僅是變化(增量),對於B.java,C.java等沒有h執行操作的文件,則沒有增量。
那麼引申出兩個問題
1、假設無法聯網怎麼辦?
2、假設SVN倉庫硬盤壞了怎麼辦?
Git是一個分佈式的版本控制工具,其架構如圖所示:
local computer1、local computer2本質上就是一個本地倉庫,所以Git無需聯網就可以在本地開發實現提交等操作,Server、local computer1、computer2的內容是一樣的。並且與SVN不同的是local computer1和local computer2直接可以進行通信。
Git版本控制的底層原理:
對於修改過的文件,如圖中的A.java,會在新版本中保存修改過後的文件副本,可以理解爲是一個Snapshot(注意:並不是增量文件)。對於沒有修改過的文件,則在新版本中保存的是舊版本的引用。
二、工作區與暫存區
Git和其他版本控制系統如SVN的一個不同之處就是有暫存區的概念。
工作區(Working Directory):就是你在電腦裏能看到的目錄。
版本庫(Repository):工作區有一個隱藏目錄.git
,這個不算工作區,而是Git的版本庫。
Git的版本庫裏存了很多東西,其中最重要的就是稱爲stage(或者叫index)的暫存區,還有Git爲我們自動創建的第一個分支master
,以及指向master
的一個指針叫HEAD
。
我們把文件往Git版本庫裏添加的時候,是分兩步執行的:
第一步是用git add
把文件添加進去,實際上就是把文件修改添加到暫存區;
第二步是用git commit
提交更改,實際上就是把暫存區的所有內容提交到當前分支。
因爲我們創建Git版本庫時,Git自動爲我們創建了唯一一個master
分支,所以,現在,git commit
就是往master
分支上提交更改。
你可以簡單理解爲,需要提交的文件修改通通放到暫存區,然後,一次性提交暫存區的所有修改。
俗話說,實踐出真知。現在,我們再練習一遍,先對readme.txt
做個修改,比如加上一行內容:
Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
然後,在工作區新增一個LICENSE
文本文件(內容隨便寫)。
先用git status
查看一下狀態:
$ git status
On branch master
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
Untracked files:
(use "git add <file>..." to include in what will be committed)
LICENSE
no changes added to commit (use "git add" and/or "git commit -a")
Git非常清楚地告訴我們,readme.txt
被修改了,而LICENSE
還從來沒有被添加過,所以它的狀態是Untracked
。
現在,使用兩次命令git add
,把readme.txt
和LICENSE
都添加後,用git status
再查看一下:
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: LICENSE
modified: readme.txt
現在,暫存區的狀態就變成這樣了:
所以,git add
命令實際上就是把要提交的所有修改放到暫存區(Stage),然後,執行git commit
就可以一次性把暫存區的所有修改提交到分支。
$ git commit -m "understand how stage works"
[master e43a48b] understand how stage works
2 files changed, 2 insertions(+)
create mode 100644 LICENSE
一旦提交後,如果你又沒有對工作區做任何修改,那麼工作區就是“乾淨”的:
$ git status
On branch master
nothing to commit, working tree clean
現在版本庫變成了這樣,暫存區就沒有任何內容了:
本文第二章節以上部分參考自《工作區和暫存區——廖雪峯的官方網站》
git中包含四種狀態:Untracked、Unmodified、modified [可以通過git status查看當前狀態]
1、Untracked一般出現在新建文件時候,表示新建的文件處於未被追蹤的狀態
2、將新建文件add之後,此時新建文件處於暫存區(Stage),狀態爲Staged狀態
3、當用戶commit提交暫存區域的文件到分支時,所有文件將處於Unmodified狀態,此時Stage(暫存區是完全乾淨的)
4、當用戶對文件進行修改後,被修改的文件將處於Modified狀態,等待add 【重複2-4步驟】
三、Git最常用的幾個命令
- remote
- fetch/pull/push
- reset
- checkout
- log
- merge
當然還包括我們上一章節所說的git add <file> 和 git commit -m '信息' 和 git status 命令,下面我們將一個個介紹。
3.1 git remote(建立遠端連接)
因爲我們會存在遠端的分支,比如使用gitlab舉例來說。
假設我們本地已有一個項目,但是GitLab/GitHub上沒有,我現在想將該項目推送上去,該怎麼做呢?
1、這是一個比較笨的方法,在遠端建立一個項目,從遠端倉庫clone到本地,將本地代碼拷貝到該項目,再分別執行三個命令add->commit->push。
2、這是一個比較明智的做法,步驟如下:
(1)將需要提交項目的目錄的項目初始化,打開Git Bash,比如我要將我的目錄/User/itcats_cn/git_test項目提交到Gitlab,就cd到git_test,輸入git init命令,初始化該項目爲git管理的項目,在執行完該命令後,輸入ls -a,應該可以看到.git的隱藏文件。
(2)在本地git add <file> 和 git commit -m 'message'
(3)在Gitlab中創建項目,項目名需要與第(2)步驟中提交的文件<file>名稱一致
(4)複製gitlab中的git協議的地址,如[email protected]:web/test.git
(5)執行命令 git remote add origin [email protected]:web/test.git 【含義是"add" 本地項目與遠端建立聯繫】
(6)執行命令 git config --list 發現[email protected]:web/test.git,已經關聯上了
輸入git remote -v命令,返回的是一個origin集合,說明用戶可以對遠端的倉庫執行fetch和push操作
origin [email protected]:web/test.git (fetch)
origin [email protected]:web/test.git (push)
3.2 git fetch(版本更新)
從遠程倉庫獲取最新到本地,不會自動merge,Git中從遠程的分支獲取最新的版本到本地方式如下:
方式一
(1)查看遠程倉庫
$ git remote -v
eoecn https://github.com/eoecn/android-app.git (fetch)
eoecn https://github.com/eoecn/android-app.git (push)
origin https://github.com/com360/android-app.git (fetch)
origin https://github.com/com360/android-app.git (push)
su@SUCHANGLI /e/eoe_client/android-app (master)
從上面的結果可以看出,遠程倉庫有兩個,一個是eoecn,一個是origin
(2)從遠程獲取最新版本到本地
$ git fetch origin master
From https://github.com/com360/android-app
* branch master -> FETCH_HEAD
su@SUCHANGLI /e/eoe_client/android-app (master)
$ git fetch origin master 這句的意思是:從遠程的origin倉庫的master分支下載代碼到本地的origin master
(3)比較本地的倉庫和遠程參考的區別
$ git log -p master.. origin/master
su@SUCHANGLI /e/eoe_client/android-app (master)
我的本地參考代碼和遠程代碼相同,所以是Already up-to-date
以上的方式有點不好理解,大家可以使用下面的方式,並且很安全
方式二
(1)查看遠程分支,和上面的第一步相同
(2)從遠程獲取最新版本到本地
$ git fetch origin master:temp
From https://github.com/com360/android-app
* [new branch] master -> temp
su@SUCHANGLI /e/eoe_client/android-app (master)
git fetch origin master:temp 這句命令的意思是:從遠程的origin倉庫的master分支下載到本地並新建一個分支temp
(3)比較本地的倉庫和遠程參考的區別
$ git diff temp
su@SUCHANGLI /e/eoe_client/android-app (master)
命令的意思是:比較master分支和temp分支的不同
由於我的沒有區別就沒有顯示其他信息
(4)合併temp分支到master分支
$ git merge temp
Already up-to-date.
su@SUCHANGLI /e/eoe_client/android-app (master)
由於沒有區別,所以顯示Already up-to-date.
合併的時候可能會出現衝突,有時間了再把如何處理衝突寫一篇博客補充上。
(5)如果不想要temp分支了,可以刪除此分支
$ git branch -d temp
Deleted branch temp (was d6d48cc).
su@SUCHANGLI /e/eoe_client/android-app (master)
如果該分支沒有合併到主分支會報錯,可以用以下命令強制刪除git branch -D <分支名>
總結:方式二更好理解,更安全,對於pull也可以更新代碼到本地,相當於fetch+merge,多人寫作的話不夠安全。
git fetch章節參考自:《git fetch的簡單用法》
3.3 git pull(版本更新合併)
git pull origin master
3.4 git push(版本推送)
git push origin master
3.5 git reset(版本回退)
可以通過git log命令查看歷史的commit記錄,通過選擇複製commit後的SHA1 id進行指定的版本回退,如:
localhost:git_test fatah$ git log
commit 617866c48b75d62b8b33a29fc21eb6d32cff3eb1 (HEAD -> master)
Author: itcats_cn <[email protected]>
Date: Tue Aug 6 23:00:27 2019 -0400
Add new file
commit 8ea66b2158d45ddd459fc6b538669be5b546bbb0
Author: itcats_cn <[email protected]>
Date: Wed Aug 7 10:20:37 2019 +0800
init2
目前版本爲617866開頭的commit id版本號,如果想回退版本,可以通過指定版本號進行回退
git reset --hear '8ea66b2158d45ddd459fc6b538669be5b546bbb0'
如果嫌輸出信息太多,看得眼花繚亂的,可以試試加上--pretty=oneline
參數:
localhost:git_test fatah$ git log --pretty=oneline
881667e679a88b557b1014d33e6a46a3c1d0a442 (HEAD -> master, origin/master) add hahaha
617866c48b75d62b8b33a29fc21eb6d32cff3eb1 Add new file
Git的commit id(版本號)和SVN不一樣,Git的commit id
不是1,2,3……遞增的數字,而是一個SHA1計算出來的一個非常大的數字,用十六進制表示,而且你看到的commit id
和我的肯定不一樣,以你自己的爲準。爲什麼commit id
需要用這麼一大串數字表示呢?因爲Git是分佈式的版本控制系統,後面我們還要研究多人在同一個版本庫裏工作,如果大家都用1,2,3……作爲版本號,那肯定就衝突了。
Git必須知道當前版本是哪個版本,在Git中,用HEAD
表示當前版本,上一個版本就是HEAD^
,上上一個版本就是HEAD^^
,當然往上100個版本寫100個^
比較容易數不過來,所以寫成HEAD~100。
3.6
checkout
作用:
1、切換到新的分支: 一般來說master分支上都是最穩定的版本,我們日常開發的時候在分支開發,開發完畢之後再合併到master中,具體操作如下。
$ git checkout -b dev-0807-work
Switched to a new branch 'dev-0807-work'
$ git checkout -b master
Switched to a new branch 'master'
2、撤銷更改 :只能撤銷更改過的文件,不能撤銷新增的文件或刪除的文件
比如我們修改了3.txt的內容
$ git status
On branch master
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: 3.txt
no changes added to commit (use "git add" and/or "git commit -a")
現在我想撤銷3.txt的更改,操作如下:
$ git checkout 3.txt
Updated 1 path from the index
此時3.txt的內容就成功被撤銷,這樣不會影響之前的版本,恢復成非常"乾淨"的狀態
3.7
merge
一般我們開發環境都在分支上開發,分支開發完畢時候測試通過則發佈,並且在master進行merge合併併發布,這樣才能保證master上是最"clean"的狀態,但是一般我們將分支合併到Master都是在圖形化界面發起的,下面我將展示GitLab中的 Pull Request。
這種merge方法方便code-review,查看改動之處並讓參與者、審閱者也可以方便在下面書寫評論。
在確認沒有任何問題後,再點擊綠色的按鈕"Merge',就可以將我們的工作分支合併到master之中。
合併之後,在本地機器執行 git fetch 命令,從遠端拉取最新的代碼[此時本機處於dev-08-07-work分支]
本機切換到master分支,執行命令 git checkout master
localhost:git_test fatah$ git checkout master
Switched to branch 'master'
Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded.
(use "git pull" to update your local branch)
提示遠端的master有兩處commit,先pull到本機的orgin/master
localhost:git_test fatah$ git pull origin master
Enter passphrase for key '/Users/fatah/.ssh/id_rsa':
From 192.168.1.103:web/test
* branch master -> FETCH_HEAD
Updating 881667e..b117d8f
Fast-forward
a.txt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
整個過程我們發現,本地的master並沒有push到遠端倉庫,而是使用分支push到遠端倉庫中,再使用Request Merge在遠端將分支與Master進行合併,在code-view沒有問題之後,在遠端倉庫執行Merge操作。由於本地在fetch最新代碼的時候並不會直接合並,所以在本機切換爲master分支的時候會提示pull一下,因爲有兩處commit,在pull成功之後本機master便擁有了最新的master代碼。
在merge的時候如何解決衝突呢?
3.7.1 merge解決衝突
模擬場景:
(1)比如修改gitlab中的a.txt文件,本地也修改git管理目錄下的a.txt文件
(2)本地進行add 和 commit操作
(3)執行git pull origin master時候,發現衝突文件,因爲遠端和本地兩處修改位置一樣,到底選用哪一行?
(4)刪除衝突部分,其中<<<<HEAD到=====之間的內容爲本地倉庫內容,後面的內容爲遠端倉庫的內容,兩處衝突地方選用哪個可以自己權衡,再修改完之後,輸入git status,發現a.txt變爲了unmodified狀態,重新執行add -> commit -> push操作,發現完成解決衝突。