Git的理解和使用

chenyu_insist發表於2015-09-28

Git介紹

Git(the stupid content tracker)是一個源自Linux核心專案的原始碼管理工具。和傳統的CVS、SVN不同,git是一個分散式原始碼管理工具。

Git命令 簡單說明
git init 初始化一個本地的程式碼倉庫。
git clone 從遠端複製一個程式碼倉庫。
git config git選項設定。
git add 新增檔案/目錄。
git commit 提交修改。
git status 顯示工作目錄的狀態以及緩衝區內的快照。
git log 已提交快照的日誌。
git branch 建立分支。
git checkout 遷出/拉出/切換到一個分支。
git merge 合併分支。
git revert 撤銷commit快照。
git reset 撤銷本地工作目錄的修改。
git clean 刪除程式碼倉庫以外的檔案。
git remote 管理遠端git。
git fetch 從遠端獲取分支。
git pull 從遠端獲取分支。
git push 把程式碼推到遠端分支。

這裡列舉幾個常見的git命令,讓大家過過目 
基本操作 

git init //初始化版本庫 

git clone //克隆版本庫 

07 >git add //新增新檔案 

git commit //提交到本地版本庫 

git checkout //檢出(分支、標籤) 

分支 

git branch //列出分支 

git branch -r //列出遠端分支 

git branch -a //列出所有分支 

git branch newBranch //基於當前分支建立新分支newBranch 

git branch -D branchName //刪除分支branchName 

git branch -d branchName //僅刪除已合併分支branchName 

git merge <--> //合併分支<br>>git tag  
歷史 

git log //顯示全部歷史 

git log -p //顯示版本歷史,以及版本間的內容差異 

git log -5 //顯示最近的5個提交 

git log -5 -p //顯示最近的5個提交,以及版本間的內容差異 

...(很多很多引數...) 


git diff 112 115 //顯示112和115版本的差別 

基本概念

檔案狀態

Git倉庫中的檔案有幾種狀態:

  • untracked - 還沒新增到倉庫中。
  • unmodified - 自上次提交以來,檔案未曾修改過。
  • modified - 檔案修改了還沒提交。
  • staged - 檔案提交到了暫存區中。一旦執行git commit就會轉換為unmodified狀態。

當我們往工作目錄新增一個檔案的時候,這個檔案預設是未跟蹤狀態的,我們肯定不希望編譯生成的一大堆臨時檔案預設被跟蹤還要我們每次手動將這些檔案清除出去。用以下命令可以跟蹤檔案:

git add <file>

Git暫存區(Staged Area)的意思是:你把一個檔案託付給Git跟蹤(git add),然後又修改了它,此時這個檔案就位於暫存區了。暫存區內的檔案幾乎只做一件事:等待你執行git commit,把它提交。

上圖中右邊3個狀態都是已跟蹤狀態,其中的灰色箭頭只表示untracked<-->tracked的轉換而不是untracked<-->unmodified的轉換,新新增的檔案肯定算是被修改過的。那麼,staged狀態又是什麼呢?這就要搞清楚GIT的三個工作區域:本地資料(倉庫)目錄,工作目錄,暫存區,如下圖所示:



git directory就是我們的本地倉庫.git目錄,裡面儲存了所有的版本資訊等內容。

  working driectory,工作目錄,就是我們的工作目錄,其中包括未跟蹤檔案及已跟蹤檔案,而已跟蹤檔案都是從git directory取出來的檔案的某一個版本或新跟蹤的檔案。

  staging area,暫存區,不對應一個具體目錄,其時只是git directory中的一個特殊檔案。

  當我們修改了一些檔案後,要將其放入暫存區然後才能提交,每次提交時其實都是提交暫存區的檔案到git倉庫,然後清除暫存區。而checkout某一版本時,這一版本的檔案就從git倉庫取出來放到了我們的工作目錄。

快照(snapshot)

Git與其他版本控制系統的區別在於:Git只關心檔案是否變化,而不關心檔案內容的變化。大多數版本控制系統都會忠實地記錄版本間的檔案差異(diff),但Git不關心這些具體差異(哪一行有什麼變動),Git只關心哪些檔案修改了哪些沒有修改,修改了的檔案直接複製形成新的blob(這就是所謂的快照snapshot)。當你需要切換到或拉出一個分支時,Git就直接載入當時的檔案快照即可,這就是Git快的原因。說起來,這也是用空間換取時間的經典案例。

從這個角度看,Git更像是一個小型檔案系統,並在這個系統上提供一系列的工具來輔助開發。

Git的地理觀

Git是一個分散式的版本控制系統,因此沒有所謂的中心。粗略來看Git可分為本地庫(local repository)和遠端庫(remote repository),細緻地看可分為以下幾個部分:

  • Working Directory - 工作目錄。Git倉庫位於工作目錄之下,工作目錄下的檔案有加入Git倉庫(tracked)和沒加入Git倉庫(untracked)的區別。
  • Stage Area - 暫存區。如上所述,已加入Git倉庫並被修改(尚未提交)的檔案。
  • Local Repository - 本地倉庫。
  • Remote Repository - 遠端倉庫。

檔案通常是:加入Git倉庫(git add)-> 修改後即位於暫存區 -> 提交到本地庫(git commit) -> 推送到遠端庫(git push)。



origin/master

這裡主要筆記一些在Git上下文中經常遇見的術語。origin/master指遠端倉庫origin的master分支。

遠端倉庫/分支

這樣的形式。雖然Git是分散式的系統,但通常把git clone的源頭叫做origin,origin也被視為中心倉庫(Central Repository)。

git入門

建立目錄,並用git init初始化:

$ mkdir learn-git && cd learn-git
$ git init
Initialized empty Git repository in /tmp/learn-git/.git/

git init輸出可知,git建立了一個名為.git的隱藏目錄。

建立一個檔案,並用git add新增到倉庫,用git commit提交:

$ echo "hello git" > README.txt
$ git add .
$ git commit -m "readme file"
[master (root-commit) cd27ac1] readme file
 1 file changed, 1 insertion(+)
 create mode 100644 README.txt

接下來對已提交檔案做一些修改,並新新增一個檔案:

$ echo "learn files here" >> README.txt
$ cp ~/.vimrc .

git diff檢視檔案差異(每次commit前應該先diff對比差異詳情):

$ git diff
diff --git a/README.txt b/README.txt
index 8d0e412..0219596 100644
--- a/README.txt
+++ b/README.txt
@@ -1 +1,2 @@
 hello git
+learn files here

差異對比可以用git diff --cached儲存下來(如此差異則不輸出到螢幕)。

git status檢視git倉庫狀態:

$ 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)
#
#       .vimrc
no changes added to commit (use "git add" and/or "git commit -a")

這裡顯示README.txt被修改了,而.vimrc則等待新增。接下來,我們將.vimrc新增到git倉庫中,且將所有修改一併提交(git commit -a):

$ git commit -a -m "update readme && add vimrc"
[master f6162f0] update readme && add vimrc
 2 files changed, 123 insertions(+)
 create mode 100755 .vimrc

git log輸出git日誌,包括提交編號(如"f6162f04170e3665bc03744e43f764c903e4e38d"這樣的字串)、提交者、提交日期和提交日誌。

git log的其他輸出:

$ git log -p                    # 詳細日誌,並輸出到分頁程式
$ git log --stat --summary

美化git log輸出

$ git log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --

修改全域性配置:

$ git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --"
$ git lg

管理分支

建立一個分支:

$ git branch exp

檢視當前git倉庫的所有分支:

$ git branch
  exp
* master

切換到exp分支:

$ git checkout exp
Switched to branch 'exp'

修改檔案,並提交:

$ echo "start branch: exp" >> README.txt 
$ git commit -a -m "modified readme in exp branch"
[exp 2e825a4] modified readme in exp branch
 1 file changed, 1 insertion(+)

切換回master分支:

$ git checkout master
Switched to branch 'master'

master分支檢查檔案,可見exp分支的修改並沒影響到master分支(注意,在exp分支的修改都已提交;如果沒有提交,則切換回master分支會看到檔案已變)。接下來我們製造一個衝突:

$ echo "return branch: master" >> README.txt                    
$ git commit -a -m "modified readme in master branch"
[master 8dd9fb2] modified readme in master branch
 1 file changed, 1 insertion(+)

git merge exp合併分支:

$ git merge exp
Auto-merging README.txt
CONFLICT (content): Merge conflict in README.txt
Automatic merge failed; fix conflicts and then commit the result.

從git輸出可見,git嘗試自動合併但失敗了,因此提示需要解決衝突再提交

git diff檢視差異,且差異檔案被修改:

$ git diff
...
$ cat README.txt 
hello git
learn files here
<<<<<<< HEAD
return branch: master
=======
start branch: exp
>>>>>>> exp

手工解決衝突並再次提交:

(edit file)
$ git commit -a -m "do merge"

接下來,可以刪除exp分支:

$ git branch -d exp
Deleted branch exp (was 2e825a4).

git branch -d刪除分支時會檢查分支是否完全合併到主幹,如果不是,則會刪除失敗,並提示需要合併:

$ git branch exp                        # 建立exp分支
$ git checkout exp                      # 切換到exp分支
$ echo "exp again" >> README.txt        # 修改並提交
$ git commit -a -m "exp again"
[exp 868e68c] exp again
 1 file changed, 1 insertion(+)
$ git checkout master                   # 切換回master
Switched to branch 'master'
$ git branch -d exp                     # 刪除失敗
error: The branch 'exp' is not fully merged.
If you are sure you want to delete it, run 'git branch -D exp'.

可以用git branch -D exp忽略修改,完全刪除分支:

$ git branch -D exp
Deleted branch exp (was 868e68c).

檢視遠端git

基礎命令是:git remote show, git remote show X。

$ git remote show
origin
web

檢視GitHub預設設定的origin

$ git remote show origin
* remote origin
  Fetch URL: git@github.com:berlinix/blog.git
  Push  URL: git@github.com:berlinix/blog.git
  HEAD branch: master
  Remote branch:
    master tracked
  Local branch configured for 'git pull':
    master merges with remote master
  Local ref configured for 'git push':
    master pushes to master (fast-forwardable)

git命令快查

以下列出一些常用的git命令

命令 說明
基礎操作
git init 初始化git倉庫
git add X 新增X檔案/路徑到git倉庫
git commit -m "COMMENTS" 提交更新
分支管理
git branch X 建立一個名為X的分支
git checkout X 切換到X分支
git merge X 自動合併X分支
git branch -d X 刪除X分支,需要先merge
git branch -D X 強制刪除X分支,忽略其修改,無須先merge
與遠端git互動
git remote show 顯示遠端git倉庫
git remote show X 顯示遠端git一個名為X的倉庫
git push origin master 更新提交到GitHub

Git日常問題

撤銷commit

剛與master合併並提交後就後悔了現在要做的是撤銷commit(revoke/undo merge/commit)。

檢視當前所在分支:

$ git branch
  bs3
* coin
  dev
  master

檢視日誌:

$ git log --oneline
9b7ba39 merged with master
73a66e8 update FAQ

用以下2個命令來撤銷提交(把COMMIT_SHA替換為實際的SHA值;把HEAD~N中的N替換為一個數字,表示回退幾步):

$ git reset --hard COMMIT_SHA
$ git reset --hard HEAD~N

例如回退到合併前:

$ git reset --hard 73a66e8
HEAD is now at 73a66e8 update FAQ

回退後發現不對,因為現在這個commit還是在master中的(在merge之前master已經走的太遠),趕緊再次reset到merge時的狀態:

$ git reset --hard 9b7ba39
HEAD is now at 9b7ba39 merged with master

如此一來就是就是merge後commit之前的狀態。接下來就是要完成undo merge(已經undo commit了):

$ git revert -m 1 9b7ba39

這下就徹底回到merge前了,以防萬一再次檢查:

$ git diff --name-status master..coin

看起來沒什麼問題了,檢查下日誌:

$ git log --oneline
2691516 Revert "merged with master"
9b7ba39 merged with master
73a66e8 update FAQ

用git找回已刪除檔案

首先找到與目標檔案相關的最後一次commit。如果目標檔案沒有出現在HEAD commit中,那麼在這次commit時,檔案就被刪除了:

$ git rev-list -n 1 HEAD -- htdocs/myfile.php
1e8182f58dc038c8e6bc2025e8430f463d372030

接下來就是恢復工作了:

$ git checkout 1e8182f58dc038c8e6bc2025e8430f463d372030^ -- htdocs/myfile.php

合併分支的部分檔案

有時候只想合併分支裡的部分檔案,而不是整個分支,可以用這個命令:

git checkout BRANCH FILE ...

例如,從test_branch分支中合併file_modified檔案:

$ git checkout test_branch file_modified

參考Git Tip: How to "Merge" Specific Files from Another Branch



相關文章