摘要:用了很久的Git和svn,由於總是眼高手低,沒能靜下心來寫這些程式設計師日常開發最常用的知識點。現在準備開一個專題,專門來總結一下版本控制工具,讓我們從git開始。完成本系列部落格的閱讀以後,你將掌握git的基本概念與git的基本命令,可以在本地隨心所欲的完成程式碼的提交撤銷儲存修改等操作、可以流暢的參與多人協作,本文致力於快速的入門,如果涉及到更高階的功能需要進行更深一步的學習。
本文核心點:
- Git的基本概念
- 一個人使用Git時的程式碼版本控制--(提交、拉程式碼、分支操作)
- 多人合作時的程式碼版本控制--(合併衝突、暫存程式碼)
什麼是Git
簡介
git是世界上目前最先進的分散式版本控制系統,致力於團隊、個人進行專案版本管理,完美的解決難以比較程式碼、難以合併程式碼、難以取消修改、難以在寫當前程式碼的過程中儲存未完成的修改去修改線上版本的bug等的痛點。 git是一個非常強大的工具,但作為一個git使用者來說,不用完全學習Git的知識點與命令,因為有的命令的使用頻率非常的低甚至數年都不會用到,讓我們來由淺入深進行學習。
git的歷史
git是linux的創始人linus,在付費版本控制工具BitMover收回對Linux社群免費使用權利的時候,一怒之下花費兩個星期的時間寫出來的。(牛筆的人)
開始
安裝git
選擇自己的作業系統對應的git版本安裝,安裝成功後執行git version
後,輸出git版本則安裝正確。
git 官方: git-scm.com/downloads
配置使用者資訊
使用git config
命令來配置使用者名稱和郵箱
git config --global user.name "pzqu"
git config --global user.email pzqu@example.com
複製程式碼
如果用了 --global 選項,那麼更改的配置檔案就是位於你使用者主目錄下的那個,以後你所有的專案都會預設使用這裡配置的使用者資訊。如果要在某個特定的專案中使用其他名字或者電郵,只要去掉 --global選項重新配置即可,新的設定儲存在當前專案的 .git/config 檔案裡。
使用git config user.name
和git config user.email
來檢查是否成功,也可以直接用git config --list
來列出全部git配置資訊來檢視
建立git託管的專案
假如我們建立一個專案叫make_money,先建立一個資料夾叫make_money,再使用git init
命令建立git專案。
# pzqu @ pzqu-pc in ~/Documents/code/test [0:05:29]
$ mkdir make_money
# pzqu @ pzqu-pc in ~/Documents/code/test [0:06:24]
$ ls
make_money
# pzqu @ pzqu-pc in ~/Documents/code/test [0:06:29]
$ cd make_money
# pzqu @ pzqu-pc in ~/Documents/code/test/make_money [0:07:10]
$ git init
Initialized empty Git repository in /Users/pzqu/Documents/code/test/make_money/.git/
# pzqu @ pzqu-pc in ~/Documents/code/test/make_money on git:master o [0:07:12]
$ ls -al
total 0
drwxr-xr-x 3 pzqu staff 96 11 7 00:07 .
drwxr-xr-x 3 pzqu staff 96 11 7 00:06 ..
drwxr-xr-x 9 pzqu staff 288 11 7 00:07 .git
複製程式碼
建立成功以後,會出現一個叫.git的隱藏資料夾,這個就是你的git倉庫,以後所有的git操作歷史提交記錄資訊就全部記錄在此了,只要這個資料夾在就可以記住我們的全部git操作
工作區和暫存區
在使用git的時候還要清楚暫存區和工作區的含義,參考廖雪峰的官方網站-git篇-工作區和暫存區
常見情況
提交程式碼
新檔案與修改
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master o [11:37:50]
$ ls
README.md
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master o [11:42:02]
$ touch file1.txt
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master x [11:42:15]
$ git add file1.txt
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master x [11:42:23]
$ git status
On branch master
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: file1.txt
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master x [11:56:38]
$ git commit -m "[+]add new file1.txt"
[master 66cc488] [+]add new file1.txt
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 file1.txt
複製程式碼
上圖操作包含:
- 建立新檔案file1.txt
- add 新增修改的內容到索引
- status 檢視修改的內容
- commit 把索引提交到本地分支
git add .
:監控工作區的狀態樹,此命令會把工作時的所有變化提交到暫存區,包括檔案內容修改(modified)以及新檔案(new),但不包括被刪除的檔案。
git add -u
:他僅監控已經被add的檔案(即tracked file),他會將被修改的檔案提交到暫存區。add -u 不會提交新檔案(untracked file)。(git add --update的縮寫)
git add -A
:是上面兩個功能的合集(git add --all的縮寫)
git show 列出最近一次的提交
複製程式碼
對於commit:像這樣,你不斷對檔案進行修改,然後不斷提交修改到版本庫裡,就好比玩RPG遊戲時,每通過一關就會自動把遊戲狀態存檔,如果某一關沒過去,你還可以選擇讀取前一關的狀態。有些時候,在打Boss之前,你會手動存檔,以便萬一打Boss失敗了,可以從最近的地方重新開始。Git也是一樣,每當你覺得檔案修改到一定程度的時候,就可以“儲存一個快照”,這個快照在Git中被稱為commit。一旦你把檔案改亂了,或者誤刪了檔案,還可以從最近的一個commit恢復,然後繼續工作,而不是把幾個月的工作成果全部丟失。
刪除檔案
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master o [12:55:24]
$ ls
README.md file1.txt
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master o [12:55:25]
$ git rm file1.txt
rm 'file1.txt'
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master x [12:55:30]
$ ls
README.md
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master x [12:55:32]
$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
deleted: file1.txt
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master x [12:55:40] C:128
$ git commit -m "[-]delete file1.txt"
[master e278392] [-]delete file1.txt
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 file1.txt
複製程式碼
上圖操作包含:
- 建立新檔案file1.txt
- git rm 刪除file1.txt檔案
- status 檢視修改的內容
- commit 把索引提交到本地分支
tip1: 如果沒有用git rm刪除檔案,在本地刪除檔案後,git add一下再提交可以達到同樣的效果
tip2: 要是你加班太晚,頭暈不小心刪除了不想刪除的檔案怎麼辦?見 版本控制工具——Git常用操作(下)-後悔藥
拉程式碼
方法一 pull
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master o [17:01:13]
$ git pull
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
From github.com:pzqu/git_test
5fd4d8f..7b54a8a master -> origin/master
Merge made by the 'recursive' strategy.
share_file.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 share_file.txt
複製程式碼
上圖命令:
- git pull
檢視本地倉庫變化git log
上圖可以看到向遠端倉庫pull的時候,出現了兩個新的commit,commit 7b54a8ae74...
的提交資訊為Create share_file.txt
,另一個commit fdbb19cf4c51770
的提交資訊為Merge branch 'master' of github.com:pzqu/git_test
。事實上主線只有一個提交,為什麼會出現這種情況? 是因為pull其實會做兩個操作
- 拉遠端倉庫程式碼到本地
- 自動與當前分支合併並生成一個合併成功的提交
注意這裡的第二個個步驟如果遠端有人和你改了同一個檔案就會出現一個衝突,這個時候git會提示你哪些檔案有衝突,手動改了再提交一次就可以了。詳情見合併衝突
方法二 fetch
我在遠端修改了檔案,向share_file.txt
加了一行內容tom modify
,此時拉程式碼。
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master o [21:07:21]
$ git fetch
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:master o [21:08:43]
$ git rebase origin/master
First, rewinding head to replay your work on top of it...
Applying: [+]add new file1.txt
Applying: [-]delete file1.txt
複製程式碼
上圖所示有以下兩個操作
- fetch 拉取遠端程式碼到本地
- rebase 把原生程式碼提交基於遠端分支重新replay
效果如下:
上圖是git log
所輸出的提交內容,剛剛pull的時候忘記把pull自動產生的merge提交到遠端,rebase的時候把本地的提交放到了遠端提交之後,看起來就是一條直線,比較優雅,也是推薦的方式。
同樣的,如果產生了衝突,詳情見合併衝突
分支操作
建立分支
分支是多人協同最經典的地方所在,我們來建立一個分支
$ git checkout -b dev/pzqu origin/master
Branch 'dev/pzqu' set up to track remote branch 'master' from 'origin'.
Switched to a new branch 'dev/pzqu'
$ git branch
* dev/pzqu
master
複製程式碼
git checkout -b 分支名 其他分支
,-b
代表建立並切換到新建的分支,分支名
代表新建立的分支叫什麼名字,這裡叫dev/pzqu
,其他分支
代表基於哪一個分支來建立,這裡基於遠端的master分支origin/master
,如果省略則代表基於當前分支git branch
展示本地的分支情況,加-a
引數可以展示全部的分支,包括遠端分支*
在分支前,指明瞭現在所在的分支是dev/pzqu
切換分支
$ git checkout -b dev/pzqu2
Switched to a new branch 'dev/pzqu2'
$ git branch
dev/pzqu
* dev/pzqu2
master
$ git checkout dev/pzqu
Switched to branch 'dev/pzqu'
Your branch is up to date with 'origin/master'.
$ git branch
* dev/pzqu
dev/pzqu2
master
複製程式碼
- 基於當前分支建立了一個新的分支並自動切換過去
dev/pzqu2
git checkout 已存在的分支名
切換分支回到dev/pzqu
刪除分支
$ git branch
* dev/pzqu
dev/pzqu2
master
$ git branch -D dev/pzqu2
Deleted branch dev/pzqu2 (was 7c9be37).
$ git branch
* dev/pzqu
master
複製程式碼
- 位於
dev/pzqu
,刪除了dev/pzqu2
分支
合併衝突
合併同一個分支的衝突(常見)
為了產生一個衝突,我在另一個地方向遠端倉庫提交了程式碼,更改share_file.txt
檔案,加了一行內容tom add for merge
,
本地修改同一個檔案加了一行pzqu add for merge
,並提交到本地,這樣一來,本地和遠端倉庫的同一個檔案就不一樣了,一會拉程式碼一定會產生一個衝突。效果如下:
- 一般rebase或pull衝突的時候,都會出現提示,然後git status會出現上圖圖示
- 這個時候不可以進行任何分支切換和commit操作,按照他提示進行處理
- git status提示哪個檔案是都被修改的,both modified,然後使用編輯器修改該檔案,解決衝突
- 解決完成後,git add 新增該衝突檔案
- git rebase --continue,並更新commit message,完成整個rebase流程 我們來看看這個衝突的檔案:
Git用<<<<<<<
,=======
,>>>>>>>
標記出不同分支的內容,我們修改如下後儲存:
git add
再git rebase --continue
後完成rebase,效果如下,再push
的遠端倉庫即可
合併不同分支的程式碼產生衝突
關於怎麼建立分支與切換分支見建立分支和切換分支,這裡只討論合併時產生的衝突的情況,我們已經基於master
分支建立了一個dev/pzqu
分支
$ git branch
* dev/pzqu
master
複製程式碼
切換到master
分支,加一行master add for merge
並提交,檔案內容如下:
$ cat share_file.txt
tom add
tom modify
tom add for merge
pzqu add for merge
master add for merge
複製程式碼
切換到dev/pzqu
分支,向share_file.txt
加入一行dev/pzqu add for merge
並提交,現在share_file.txt
內容如下:
$ cat share_file.txt
tom add
tom modify
tom add for merge
pzqu add for merge
dev/pzqu add for merge
複製程式碼
現在兩個分支的同一個檔案內容不一樣了,現在我們在dev/pzqu
分支上進行合併:
$ git merge master
Auto-merging share_file.txt
CONFLICT (content): Merge conflict in share_file.txt
Automatic merge failed; fix conflicts and then commit the result.
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:dev/pzqu x [11:17:31] C:1
$ git status
On branch dev/pzqu
Your branch is ahead of 'origin/master' by 1 commit.
(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: share_file.txt
no changes added to commit (use "git add" and/or "git commit -a")
$ cat share_file.txt
tom add
tom modify
tom add for merge
pzqu add for merge
<<<<<<< HEAD
dev/pzqu add for merge
=======
master add for merge
>>>>>>> master
複製程式碼
上圖出現了一個衝突,是我們意料之中的,修改share_file.txt
檔案,解決此衝突:
$ cat share_file.txt
tom add
tom modify
tom add for merge
pzqu add for merge
dev/pzqu add for merge
master add for merge
$ git add share_file.txt
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:dev/pzqu x [11:22:40]
$ git commit -m "[*]merge master to dev/pzqu"
[dev/pzqu d9e018e] [*]merge master to dev/pzqu
# pzqu @ pzqu-pc in ~/Documents/code/test/git_test on git:dev/pzqu o [11:23:00]
$ git status
On branch dev/pzqu
Your branch is ahead of 'origin/master' by 3 commits.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
複製程式碼
衝突解決也提交了,看看我們現在的分支內容:
上圖我們可以看到:
-
master
分支比遠端origin/master
分支多一次提交,dev/pzqu
分支由於是基於origin/master
分支,合併了master
分支的提交和當前dev/pzqu
分支的提交,超出本地master
兩個提交,致此我們把master
合併到dev/pzqu
的操作就完成了。 -
通常我們開一個新的開發分支是為了在自己的分支上寫程式碼,方便提交也不會把主線弄亂,現在我們用同樣的方法將
dev/pzqu
合併到master
分支,然後把兩個分支都提交到遠端。
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
$ git merge dev/pzqu
Updating 58f047a..d9e018e
Fast-forward
share_file.txt | 1 +
1 file changed, 1 insertion(+)
$ git push origin master
Total 0 (delta 0), reused 0 (delta 0)
To github.com:pzqu/git_test.git
7c9be37..d9e018e master -> master
$ git push origin dev/pzqu
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 887 bytes | 887.00 KiB/s, done.
Total 9 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), done.
remote:
remote: Create a pull request for 'dev/pzqu' on GitHub by visiting:
remote: https://github.com/pzqu/git_test/pull/new/dev/pzqu
remote:
To github.com:pzqu/git_test.git
* [new branch] dev/pzqu -> dev/pzqu
複製程式碼
- 切換到
master
分支 - 合併
dev/pzqu
到master
分支 master
推到遠端倉庫- 如果
dev/pzqu
要保留,就可以推送到遠端倉庫。
- 現在我們可以看到全部的分支都在一起了,強迫症都舒服了。
暫存程式碼儲存現場
這種情況一般是出現在你正在完成一個功能,但是忽然線上發現了一個Bug,必須馬上開一個新的分支來修復bug,但是現在的功能沒寫完不打算提交(commit),現在怎麼辦??不用怕暫存程式碼來幫助你。
$ git status
On branch dev/pzqu
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: need_stash.txt
modified: share_file.txt
$ git stash
Saved working directory and index state WIP on dev/pzqu: d9e018e [*]merge master to dev/pzqu
$ git stash list
stash@{0}: WIP on dev/pzqu: d9e018e [*]merge master to dev/pzqu
$ git status
On branch dev/pzqu
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
//省略操作:去建立一個Bug分支,修復他並完成與主線的合併,刪除Bug分支。
//省略操作:切回來當前分支繼續開發
//下面來恢復現場
$ git stash apply stash@{0}
On branch dev/pzqu
Your branch is up to date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: need_stash.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: share_file.txt
複製程式碼
status
檢視到有2個檔案修改沒有提交stash
把修改放到暫存區,並生成一個idstash list
列出暫存區所有內容stash apply
重新把暫存區內容放到本地
這裡的stash apply
成功的把暫存區的一次暫存恢復到了本地,但是暫存區還有會儲存這次暫存,如果想刪除這次暫存要用git stash drop
來刪除;也可以用git stash pop
,恢復最後一次暫存的同時把stash內容也刪了。
$ git stash drop stash@{0}
Dropped stash@{0} (bfdc065df8adc44c8b69fa6826e75c5991e6cad0)
$ git stash list
複製程式碼
好了,暫存區清乾淨了。
注意:要放到暫存區的檔案一定要先通過git add加到index
小結
本文閱讀結束以後,我們學會了
- Git的基本概念,知道git的作用、歷史;學會安裝配置Git,使用Git建立專案託管以及工作區和暫存區的概念
- 學會Git的本地操作,提交、拉程式碼、建立切換刪除分支操作,
- 多人合作時的程式碼版本控制,學會了不同情況下的合併衝突、暫存程式碼操作
下集預告
Git常用操作(下)我計劃給大家介紹以下點:
- 後悔藥-各種後悔操作(撤消commit,回滾,回退遠端倉庫等)
- 哎呀,提交的時候漏了檔案
- tag操作
- git忽略不想提交的檔案
下集傳送門: 版本控制工具——Git常用操作(下)
注意事項
理論上,git日常用到的命令是 diff show fetch rebase pull push checkout commit status 等,這些命令都不會導致程式碼丟失,假如害怕程式碼丟失,可以預先commit一次,再進行修改,但切記
不可使用自己不熟悉的命令 任何命令,不要加上-f的強制引數,否則可能導致程式碼丟失 建議多使用命令列,不要使用圖形介面操作