小白文-Git-版本控制
推薦閱讀
Git版本控制
注意:開始學習之前,確保自己的網路可以暢通的連線Github:https://github.com,這個是一個國外網站,連起來特別卡,至於用什麼方式實現流暢訪問,懂的都懂。
其實版本控制在我們的生活中無處不在,比如你的期末或是畢業答辯論文,由於你寫得不規範或是老師不滿意,你的老師可能會讓你改了又改,於是就會出現下面這種情況:
我們手裡的論文可能會經過多次版本迭代,最終我們會選取一個最好的版本作為最終提交的論文。
使用版本控制不僅僅是為了去記錄版本迭代歷史,更是為了能夠隨時回退到之前的版本,實現時間回溯。
同時,可能我們的論文是多個人一同完成,那麼多個人如何去實現同步,如何保證每個人提交的更改都能夠正常彙總,如何解決衝突,這些問題都需要一個優秀的版本控制系統來解決。
走進Git
我們開發的專案,也需要一個合適的版本控制系統來協助我們更好地管理版本迭代,而Git正是因此而誕生。
首先我們來了解一下Git是如何工作的:
可以看到,它大致分為4個板塊:
- 工作目錄:存放我們正在寫的程式碼(當我們新版本開發完成之後,就可以進行新版本的提交)
- 暫存區:暫時儲存待提交的內容(新版本提交後會存放到本地倉庫)
- 本地倉庫:位於我們電腦上的一個版本控制倉庫(存放的就是當前專案各個版本程式碼的增刪資訊)
- 遠端倉庫:位於伺服器上的版本控制倉庫(伺服器上的版本資訊可以由本地倉庫推送上去,也可以從伺服器抓取到本地倉庫)
它是一個分散式的控制系統,因此一般情況下我們每個人的電腦上都有一個本地倉庫,由大家共同向遠端倉庫去推送版本迭代資訊。
通過這一系列操作,我們就可以實現每開發完一個版本或是一個功能,就提交一次新版本,這樣,我們就可以很好地控制專案的版本迭代,想回退到之前的版本隨時都可以回退,想檢視新版本新增或是刪除了什麼程式碼,隨時都可以檢視。
安裝Git
首先請前往Git官網去下載最新的安裝包:https://git-scm.com/download/win
這手把手演示一下如何安裝Git環境。
安裝完成後,需要設定使用者名稱和郵箱來區分不同的使用者:
git config --global user.name "Your Name" git config --global user.email "[email protected]"
基本命令介紹
建立本地倉庫
我們可以將任意一個資料夾作為一個本地倉庫,輸入:
git init
輸入後,會自動生成一個.git
目錄,注意這個目錄是一個隱藏目錄,而當前目錄就是我們的工作目錄。
建立成功後,我們可以檢視一下當前的一個狀態,輸入:
git status
如果已經成功配置為Git本地倉庫,那麼輸入後可以看到:
On branch master
No commits yet
這表示我們還沒有向倉庫中提交任何內容,也就是一個空的狀態。
新增和提交
接著我們來看看,如何使用git來管理我們文件的版本,我們建立一個文字文件,隨便寫入一點內容,接著輸入:
git status
我們會得到如下提示:
Untracked files:
(use "git add <file>..." to include in what will be committed)
hello.txt
nothing added to commit but untracked files present (use "git add" to track)
其中Untracked files是未追蹤檔案的意思,也就是說,如果一個檔案處於未追蹤狀態,那麼git不會記錄它的變化,始終將其當做一個新建立的檔案,這裡我們將其新增到暫存區,那麼它會自動變為被追蹤狀態:
git add hello.txt #也可以 add . 一次性新增目錄下所有的
再次檢視當前狀態:
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: hello.txt
現在檔名稱的顏色變成了綠色,並且是處於Changes to be committed下面,因此,我們的hello.txt現在已經被新增到暫存區了。
接著我們來嘗試將其提交到Git本地倉庫中,注意需要輸入提交的描述以便後續檢視,比如你這次提交修改了或是新增了哪些內容:
git commit -m 'Hello World'
接著我們可以檢視我們的提交記錄:
git log
git log --graph
我們還可以檢視最近一次變更的詳細內容:
git show [也可以加上commit ID檢視指定的提交記錄]
再次檢視當前狀態,已經是清空狀態了:
On branch master
nothing to commit, working tree clean
接著我們可以嘗試修改一下我們的文字文件,由於當前檔案已經是被追蹤狀態,那麼git會去跟蹤它的變化,如果說檔案發生了修改,那麼我們再次檢視狀態會得到下面的結果:
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: hello.txt
也就是說現在此檔案是處於已修改狀態,我們如果修改好了,就可以提交我們的新版本到本地倉庫中:
git add .
git commit -m 'Modify Text'
接著我們來查詢一下提交記錄,可以看到一共有兩次提交記錄。
我們可以建立一個.gitignore
檔案來確定一個檔案忽略列表,如果忽略列表中的檔案存在且不是被追蹤狀態,那麼git不會對其進行任何檢查:
# 這樣就會匹配所有以txt結尾的檔案
*.txt
# 雖然上面排除了所有txt結尾的檔案,但是這個不排除
!666.txt
# 也可以直接指定一個資料夾,資料夾下的所有檔案將全部忽略
test/
# 目錄中所有以txt結尾的檔案,但不包括子目錄
xxx/*.txt
# 目錄中所有以txt結尾的檔案,包括子目錄
xxx/**/*.txt
建立後,我們來看看是否還會檢測到我們忽略的檔案。
回滾
當我們想要回退到過去的版本時,就可以執行回滾操作,執行後,可以將工作空間的內容恢復到指定提交的狀態:
git reset --hard commitID
執行後,會直接重置為那個時候的狀態。再次檢視提交日誌,我們發現之後的日誌全部消失了。
那麼要是現在我又想回去呢?我們可以通過檢視所有分支的所有操作記錄:
git reflog
這樣就能找到之前的commitID,再次重置即可。
分支
分支就像我們樹上的一個樹枝一樣,它們可能一開始的時候是同一根樹枝,但是長著長著就開始分道揚鑣了,這就是分支。
我們的程式碼也是這樣,可能一開始寫基礎功能的時候使用的是單個分支,但是某一天我們希望基於這些基礎的功能,把我們的專案做成兩個不同方向的專案,比如一個方向做Web網站,另一個方向做遊戲服務端。
因此,我們可以在一個主幹上分出N個分支,分別對多個分支的程式碼進行維護。
建立分支
我們可以通過以下命令來檢視當前倉庫中存在的分支:
git branch
我們發現,預設情況下是有一個master分支的,並且我們使用的也是master分支,一般情況下master分支都是正式版本的更新,而其他分支一般是開發中才頻繁更新的。我們接著來基於當前分支建立一個新的分支:
git branch test
# 對應的刪除分支是
git branch -d yyds
現在我們修改一下檔案,提交,再檢視一下提交日誌:
git commit -a -m 'branch master commit'
通過新增-a來自動將未放入暫存區的已修改檔案放入暫存區並執行提交操作。
檢視日誌,我們發現現在我們的提交只生效於master分支,而新建立的分支並沒有發生修改。
我們將分支切換到另一個分支:
git checkout test
我們會發現,檔案變成了此分支建立的時的狀態,也就是說,在不同分支下我們的檔案內容是相互隔離的。
我們現在再來提交一次變更,會發現它只生效在yyds分支上。我們可以看看當前的分支狀態:
git log --all --graph
合併分支
我們也可以將兩個分支更新的內容最終合併到同一個分支上,我們先切換回主分支:
git checkout master
接著使用分支合併命令:
git merge test
會得到如下提示:
Auto-merging hello.txt
CONFLICT (content): Merge conflict in hello.txt
Automatic merge failed; fix conflicts and then commit the result.
在合併過程中產生了衝突,因為兩個分支都對hello.txt檔案進行了修改,那麼現在要合併在一起,到底保留誰的hello檔案呢?
我們可以檢視一下是哪裡發生了衝突:
git diff
因此,現在我們將master分支的版本回退到修改hello.txt之前或是直接修改為最新版本的內容,這樣就不會有衝突了,接著再執行一次合併操作,現在兩個分支成功合併為同一個分支。
變基分支
除了直接合並分支以外,我們還可以進行變基操作,它跟合併不同,合併是分支回到主幹的過程,而變基是直接修改分支開始的位置,比如我們希望將yyds變基到master上,那麼yyds會將分支起點移動到master最後一次提交位置:
git rebase master
變基後,yyds分支相當於同步了此前master分支的全部提交。
優選
我們還可以選擇其將他分支上的提交作用於當前分支上,這種操作稱為cherrypick:
git cherry-pick <commit id>:單獨合併一個提交
這裡我們在master分支上建立一個新的檔案,提交此次更新,接著通過cherry-pick的方式將此次更新作用於test分支上。
使用IDEA版本控制
雖然前面我們基本講解了git的命令列使用方法,但是沒有一個圖形化介面,始終會感覺到很抽象,所以這裡我們使用IDEA來演示,IDEA內部集成了git模組,它可以讓我們的git版本管理圖形化顯示,當然除了IDEA也有一些獨立的軟體比如:SourceTree(挺好用)等…………
開啟IDEA後,找到版本控模組,我們直接點選建立本地倉庫,它會自動將當前專案的根目錄作為我們的本地倉庫,而我們編寫的所有程式碼和專案目錄下其他的檔案都可以進行版本控制。
我們發現所有專案中正在編寫的類檔案全部變紅了,也就是處於未追蹤狀態,接著我們進行第一次初始化提交,提交之後我們可以在下方看到所有的本地倉庫提交記錄。
接著我們來整合一下Web環境,建立新的類之後,IDEA會提示我們是否將檔案新增到Git,也就是是否放入暫存區並開啟追蹤,我們可以直接對比兩次程式碼的相同和不同之處。
接著我們來演示一下分支建立和分支管理。
遠端倉庫
遠端倉庫實際上就是位於伺服器上的倉庫,它能在遠端儲存我們的版本歷史,並且可以實現多人同時合作編寫專案,每個人都能夠同步他人的版本,能夠看到他人的版本提交,相當於將我們的程式碼放在伺服器上進行託管。
遠端倉庫有公有和私有的,公有的遠端倉庫有GitHub、碼雲、Coding等,他們都是對外開放的,我們註冊賬號之後就可以使用遠端倉庫進行版本控制,其中最大的就是GitHub,但是它伺服器在國外。
我們國內連線可能會有一點卡,私有的一般是GitLab這種自主搭建的遠端倉庫私服,在公司中比較常用,它只對公司內部開放,不對外開放。
這裡我們以GitHub做講解,官網:https://github.com,首先完成使用者註冊。
遠端賬戶認證和推送
接著我們就可以建立一個自定義的遠端倉庫了。
建立倉庫後,我們可以通過推送來將本地倉庫中的內容推送到遠端倉庫。
git remote add 名稱 遠端倉庫地址
git push 遠端倉庫名稱 本地分支名稱[:遠端分支名稱]
注意push
後面兩個引數,一個是遠端名稱,還有一個就是本地分支名稱,但是如果本地分支名稱和遠端分支名稱一致,那麼不用指定遠端分支名稱,但是如果我們希望推送的分支在遠端沒有同名的,那麼需要額外指定。
推送前需要登陸賬戶,GitHub現在不允許使用使用者名稱密碼驗證,只允許使用個人AccessToken來驗證身份,所以我們需要先去生成一個Token才可以。
推送後,我們發現遠端倉庫中的內容已經與我們本地倉庫中的內容保持一致了,注意,遠端倉庫也可以有很多個分支。
但是這樣比較麻煩,我們每次都需要去輸入使用者名稱和密碼,有沒有一勞永逸的方法呢?當然,我們也可以使用SSH來實現一次性校驗,我們可以在本地生成一個rsa公鑰:
ssh-keygen -t rsa
cat ~/.ssh/github.pub
接著我們需要在GitHub上上傳我們的公鑰,當我們再次去訪問GitHub時,會自動驗證,就無需進行登入了,之後在Linux部分我們會詳細講解SSH的原理。
接著我們修改一下工作區的內容,提交到本地倉庫後,再推送到遠端倉庫,提交的過程中我們注意觀察提交記錄:
git commit -a -m 'Modify files'
git log --all --oneline --graph
git push origin master
git log --all --oneline --graph
我們可以將遠端和本地的分支進行繫結,繫結後就不需要指定分支名稱了:
git push --set-upstream origin master:master
git push origin
在一個本地倉庫對應一個遠端倉庫的情況下,遠端倉庫基本上就是純粹的程式碼託管了(雲盤那種感覺,就純粹是存你程式碼的)
克隆專案
如果我們已經存在一個遠端倉庫的情況下,我們需要在遠端倉庫的程式碼上繼續編寫程式碼,這個時候怎麼辦呢?
我們可以使用克隆操作來將遠端倉庫的內容全部複製到本地:
git clone 遠端倉庫地址
這樣本地就能夠直接與遠端保持同步。
抓取、拉取和衝突解決
我們接著來看,如果這個時候,出現多個本地倉庫對應一個遠端倉庫的情況下,比如一個團隊裡面,N個人都在使用同一個遠端倉庫,但是他們各自只負責編寫和推送自己業務部分的程式碼,也就是我們常說的協同工作,那麼這個時候,我們就需要協調。
比如程式設計師A完成了他的模組,那麼他就可以提交程式碼並推送到遠端倉庫,這時程式設計師B也要開始寫程式碼了,由於遠端倉庫有其他程式設計師的提交記錄,因此程式設計師B的本地倉庫和遠端倉庫不一致,這時就需要有先進行pull操作,獲取遠端倉庫中最新的提交:
git fetch 遠端倉庫 #抓取:只獲取但不合並遠端分支,後面需要我們手動合併才能提交
git pull 遠端倉庫 #拉取:獲取+合併
在程式設計師B拉取了最新的版本後,再編寫自己的程式碼然後提交就可以實現多人合作編寫專案了,並且在拉取過程中就能將別人提交的內容同步到本地,開發效率大大提升。
如果工作中存在不協調的地方,比如現在我們本地有兩個倉庫,一個倉庫去修改hello.txt並直接提交,另一個倉庫也修改hello.txt並直接提交,會得到如下錯誤:
To https://github.com/xx/xxx.git
! [rejected] master -> master (fetch first)
error: failed to push some refs to 'https://github.com/xx/xxx.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 遠端倉庫
拉取後會自動進行合併,合併完成之後我們再提交即可。
但是如果兩次提交都修改了同一個檔案,那麼就會遇到和多分支合併一樣的情況,在合併時會產生衝突,這時就需要我們自己去解決衝突了。
我們可以在IDEA中演示一下,實際開發場景下可能會遇到的問題。