版本控制工具Git詳解

一、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.txtLICENSE都添加後,用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操作,發現完成解決衝突。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章