Git常用操作指南


前言

因為工作需求,最近又重新溫習了一下Git操作,遂總結了一篇Git常用操作指南,方便日后學習查閱,本博客精簡提煉了在開發過程中Git經常用到的核心命令,主要參考了《廖雪峰老師的Git教程》,希望對大家學習使用Git能帶來幫助。

Git簡介

Git是Linux之父Linus的第二個偉大的作品,它最早是在Linux上開發的,被用來管理Linux核心的源代碼。后來慢慢地有人將其移植到了Unix、Windows、Max OS等操作系統中。

Git是一個分布式的版本控制系統,與集中式的版本控制系統不同的是,每個人都工作在通過克隆建立的本地版本庫中。也就是說每個人都擁有一個完整的版本庫,查看提交日志、提交、創建里程碑和分支、合並分支、回退等所有操作都直接在本地完成而不需要網絡連接。

對於Git倉庫來說,每個人都有一個獨立完整的倉庫,所謂的遠程倉庫或是服務器倉庫其實也是一個倉庫,只不過這台主機24小時運行,它是一個穩定的倉庫,供他人克隆、推送,也從服務器倉庫中拉取別人的提交。

Git是目前世界上最先進的分布式版本控制系統。

安裝之后第一步

安裝完成后,還需要最后一步設置,在命令行輸入:

$ git config --global user.name "Your Name"
$ git config --global user.email "email@example.com"

因為Git是分布式版本控制系統,所以,每個機器都必須配置用戶信息:你的名字和Email地址。

注意git config命令的--global參數,用了這個參數,表示你這台機器上所有的Git倉庫都會使用這個配置,當然也可以對某個倉庫指定不同的用戶名和Email地址。

創建版本庫

本地倉庫

版本庫又名倉庫,英文名repository,你可以簡單理解成一個目錄,這個目錄里面的所有文件都可以被Git管理起來,每個文件的修改、刪除,Git都能跟蹤,以便任何時刻都可以追蹤歷史,或者在將來某個時刻可以“還原”。

所以,創建一個版本庫非常簡單,首先,選擇一個合適的地方,創建一個空目錄:

$ mkdir learngit
$ cd learngit
$ pwd
Path
----
D:\Blog\tmp\learngit

第二步,通過git init命令把這個目錄變成Git可以管理的倉庫:

$ git init
Initialized empty Git repository in D:/Blog/tmp/learngit/.git/

遠程倉庫

創建SSH Key

Git支持多種協議,包括https,但通過ssh支持的原生git協議速度最快。由於本地Git倉庫和GitHub倉庫之間的傳輸是通過SSH加密的,所以,需要在關聯遠程倉庫前需要配置SSH Key至Github設置中,這樣遠程倉庫才允許本機對遠程倉庫的拉去/推送操作。

打開Shell,進入到"~/.ssh"目錄下,運行"ls"命令看看這個目錄下有沒有id_rsaid_rsa.pub這兩個文件,如果已經有了,可直接跳到下一步。

如果沒有,則執行:

$ ssh-keygen -t rsa -C "youremail@example.com"

一路回車即可。執行命令后,我們再進入到"~/.ssh"目錄下,運行"ls"命令,可以看到里面有id_rsaid_rsa.pub兩個文件,這兩個就是SSH Key的秘鑰對,id_rsa是私鑰,不能泄露出去,id_rsa.pub是公鑰,可以放心地告訴任何人。

1563105584864

打開“Account settings”,“SSH Keys”頁面,然后,點“New SSH Key”,填上任意Title,在Key文本框里粘貼id_rsa.pub文件的內容(Win 10 下可使用"type ~/.ssh/id_rsa.pub"命令查看公鑰文件內容):

1563105973152

點擊“Add SSH Key”之后,就可以看到你的公鑰已經加入到了你的Github倉庫配置中。

1563106045201

添加遠程庫

首先,登陸GitHub,然后,在右上角找到“Create a new repo”按鈕,創建一個新的倉庫:

1563103995786

在Repository name填入learngit,其他保持默認設置,點擊“Create repository”按鈕,就成功地創建了一個新的Git倉庫:

1563104100618

這樣就成功創建了一個空白的遠程倉庫,那么如何將這個遠程倉庫與本地倉庫進行關聯呢?

我們根據Git所給出的提示可知,可以在本地創建一個新倉庫對遠程倉庫進行關聯,也可以對本地已有倉庫進行關聯。

關聯新倉庫
echo "# learngit" >> README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin git@github.com:guoyaohua/learngit.git
git push -u origin master
關聯已有倉庫
git remote add origin git@github.com:guoyaohua/learngit.git
git push -u origin master

我們可以使用上文在本地初始化的“learngit”倉庫。(注意:本地倉庫和遠程倉庫可以不同名,本文只是為了寫教程設置為相同名稱。)

1563104810075

我們再刷新下Github Code界面,發現新加入的README.md文件已經推送到了遠程倉庫中。

1563106284174

版本控制

工作區和暫存區

工作區(Working Directory)

就是你在電腦里能看到的目錄,比如我們剛剛創建的learngit文件夾就是一個工作區:

1563106681604

版本庫(Repository)

工作區有一個隱藏目錄.git,這個不算工作區,而是Git的版本庫。

Git的版本庫里存了很多東西,其中最重要的就是稱為Stage(或者叫Index)的暫存區,還有Git為我們自動創建的第一個分支master,以及指向master的一個指針叫HEAD

Repository

分支和HEAD的概念本文后面再詳細說明。

我們把文件往Git版本庫里添加的時候,是分兩步執行的:

第一步是用git add把文件添加進去,實際上就是把文件修改添加到暫存區

第二步是用git commit提交更改,實際上就是把暫存區的所有內容提交到當前分支。

因為我們創建Git版本庫時,Git自動為我們創建了唯一一個master分支,所以現在,git commit就是往master分支上提交更改。

你可以簡單理解為,需要提交的文件修改通通放到暫存區,然后,一次性提交暫存區的所有修改。

使用git status命令可以查看當前倉庫的狀態。

版本回退

Git版本控制可以理解為,我們再編寫代碼的過程中,會對code進行多次修改,每當你覺得文件修改到一定程度的時候,就可以“保存一個快照”,這個快照在Git中被稱為commit。一旦你把文件改亂了,或者誤刪了文件,還可以從最近的一個commit恢復,然后繼續工作,而不是把幾個月的工作成果全部丟失。

在實際工作中,我們用git log命令查看我們提交的歷史記錄:

$ git log
commit 1094adb7b9b3807259d8cb349e7df1d4d6477073 (HEAD -> master)
Author: Yaohua Guo <guo.yaohua@foxmail.com>
Date:   Fri May 18 21:06:15 2018 +0800

    append GPL

commit e475afc93c209a690c39c13a46716e8fa000c366
Author: Yaohua Guo <guo.yaohua@foxmail.com>
Date:   Fri May 18 21:03:36 2018 +0800

    add distributed

commit eaadf4e385e865d25c48e7ca9c8395c3f7dfaef0
Author: Yaohua Guo <guo.yaohua@foxmail.com>
Date:   Fri May 18 20:59:18 2018 +0800

    wrote a readme file

Git中,commit id是一個使用SHA1計算出來的一個非常大的數字,用十六進制表示,commit后面的那一串十六進制數字就是每一次提交的版本號,我們可以通過git log命令看到每次提交的版本號、用戶名、日期以及版本描述等信息。

我們可以使用git reset命令進行版本回退操作。

$ git reset --hard HEAD^

在Git中,用HEAD表示當前版本,上一個版本就是HEAD^ ,上上一個版本就是HEAD^^ ,以此類推,如果需要回退幾十個版本,寫幾十個^容易數不過來,所以可以寫,例如回退30個版本為:HEAD~30。

如果回退完版本又后悔了,想恢復,也是可以的,使用如下即可:

$ git reset --hard commit_id 

不過當我們執行git reset進行版本回退之后,之前最新的版本號無法通過git log查詢到,此時需要使用git reflog命令查詢Git的操作記錄,我們可以從該記錄中找到之前的commit id信息。

$ git reflog
e475afc HEAD@{1}: reset: moving to HEAD^
1094adb (HEAD -> master) HEAD@{2}: commit: append GPL
e475afc HEAD@{3}: commit: add distributed
eaadf4e HEAD@{4}: commit (initial): wrote a readme file

在Git中,版本回退速度非常快,因為Git在內部有個指向當前版本的HEAD指針,當你回退版本的時候,Git僅僅是把HEAD從指向回退的版本,然后順便刷新工作區文件。

重置命令

重置命令的作用是將當前的分支重設(reset)到指定的<commit>或者HEAD(默認是HEAD,即最新的一次提交),並且根據[mode]有可能更新Index和Working directory(默認是mixed)。

$ git reset [--hard|soft|mixed|merge|keep] [commit|HEAD]
  1. –hard:重設“暫存區”和“工作區”,從<commit>以來在工作區中的任何改變都被丟棄,並把HEAD指向<commit>(徹底回退到某個版本,本地的源碼也會變為上一個版本的內容。)
  2. –soft:“工作區”中的內容不作任何改變,HEAD指向<commit>,自從<commit>以來的所有改變都會回退到“暫存區”中,顯示在git status“Changes to be committed”中。(回退到某個版本,只回退了commit的信息。如果還要提交,直接commit即可。)
  3. –mixed:僅重設“暫存區”,並把HEAD指向<commit>,但是不重設“工作區”,本地文件修改不受影響。這個模式是默認模式,即當不顯示告知git reset模式時,會使用mixed模式。這個模式的效果是,工作區中文件的修改都會被保留,不會丟棄,但是也不會被標記成“Changes to be committed”,但是會提示文件未被更新。(回退到某個版本,只保留源碼,回退commit和index信息)
文件粒度操作

需要注意的是在mixed模式下進行reset操作時可以是全局性重置,也可以是文件粒度重置,區別在於二者作用域不同,文件粒度只會使對應文件的暫存區狀態變為指定commit時該文件的暫存區狀態,並且不會改變版本庫狀態,即HEAD指針不會改變,我們看一下效果。

首先我們新建兩個文件進行兩次提交,可以看到目前HEAD指向最新一次提交“text2”。

1563611168654

我們對“file1.txt”進行reset操作,令其重置為“text1”狀態。

1563611993505

並且我們通過git log命令可發現,此時HEAD指針並沒有改變,還是指向最新一次提交“Text 2”,可知文件粒度的reset --mixed不改變版本庫HEAD指針狀態。

1563612094551

對於soft和hard模式則無法進行文件粒度操作。

1563609363728

Reset 常用示例

  1. 回退add操作

    $ git add test
    $ git reset HEAD test  
    # HEAD指的是當前指向的版本號,可以將HEAD還成任意想回退的版本號
    

    可以將test從“已暫存”狀態(Index區)回滾到指定Commit時暫存區的狀態。

  2. 回退最后一次提交

    $ git add test
    $ git commit -m "Add test"
    $ git reset --soft HEAD^
    

    可以將test從“已提交”狀態變為“已暫存”狀態。

  3. 回退最近幾次提交,並把這幾次提交放到新分支上

    $ git branch topic # 已當前分支為基礎,新建分支topic
    $ git reset --hard HEAD~2 # 在當前分支上回滾提交
    $ git checkout topic
    

    通過臨時分支來保留提交,然后在當前分支上做硬回滾。

  4. 將本地的狀態回退到和遠程一樣

    $ git reset --hard origin/devlop
    
  5. 回退到某個版本提交

    $ git reset 497e350
    

    當前HEAD會指向“497e350”,暫存區中的狀態會恢復到提交“497e350”時暫存區的狀態。

撤銷修改

當我們因為一些原因想要丟棄工作區某些文件修改時,可以使用“git checkout -- <file>”命令,該命令僅會恢復工作區文件狀態,不會對版本庫有任何改動。

1563613938574

命令git checkout -- file1.txt意思就是,把file1.txt文件在工作區的修改全部撤銷,這里有兩種情況:

  • 一種是file1.txt自修改后還沒有被放到暫存區,現在,撤銷修改就回到和版本庫一模一樣的狀態;
  • 一種是file1.txt已經添加到暫存區后,又作了修改,現在,撤銷修改就回到添加到暫存區后的狀態。

總之,就是讓這個文件回到最近一次git commitgit add時的狀態。

刪除文件

在Git中,刪除也是一個修改操作,我們實戰一下,先添加一個新文件test.txt到Git並且提交:

一般情況下,你通常直接在文件管理器中把沒用的文件刪了,或者用rm命令刪了:

$ rm test.txt

這個時候,Git知道你刪除了文件,因此,工作區和版本庫就不一致了,git status命令會立刻告訴你哪些文件被刪除了:

$ git status
On branch master
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	deleted:    test.txt

no changes added to commit (use "git add" and/or "git commit -a")

現在你有兩個選擇,一是確實要從版本庫中刪除該文件,那就用命令git rm刪掉,並且git commit

$ git rm test.txt
rm 'test.txt'

$ git commit -m "remove test.txt"
[master d46f35e] remove test.txt
 1 file changed, 1 deletion(-)
 delete mode 100644 test.txt

現在,文件就從版本庫中被刪除了。

提示:先手動刪除文件,然后使用git rm <file>git add <file>效果是一樣的。

另一種情況是刪錯了,因為版本庫里還有呢,所以可以很輕松地把誤刪的文件恢復到最新版本:

$ git checkout -- test.txt

git checkout其實是用版本庫里的版本替換工作區的版本,無論工作區是修改還是刪除,都可以“一鍵還原”。

注意:從來沒有被添加到版本庫就被刪除的文件,是無法恢復的!

分支管理

創建與合並分支

在上文“版本回退”里,我們已經知道,每次提交,Git都把它們串成一條時間線,這條時間線就是一個分支。截止到目前,只有一條時間線,在Git里,這個分支叫主分支,即master分支。HEAD嚴格來說不是指向提交,而是指向mastermaster才是指向提交的,所以,HEAD指向的就是當前分支。

一開始的時候,master分支是一條線,Git用master指向最新的提交,再用HEAD指向master,就能確定當前分支,以及當前分支的提交點:

每次提交,master分支都會向前移動一步,這樣,隨着你不斷提交,master分支的線也越來越長。

當我們創建新的分支,例如dev時,Git新建了一個指針叫dev,指向master相同的提交,再把HEAD指向dev,就表示當前分支在dev上:

Git創建一個分支很快,因為除了增加一個dev指針,改改HEAD的指向,工作區的文件都沒有任何變化。

不過,從現在開始,對工作區的修改和提交就是針對dev分支了,比如新提交一次后,dev指針往前移動一步,而master指針不變:

假如我們在dev上的工作完成了,就可以把dev合並到master上。Git怎么合並呢?最簡單的方法,就是直接把master指向dev的當前提交,就完成了合並:

所以Git合並分支也很快!就改改指針,工作區內容也不變!

合並完分支后,甚至可以刪除dev分支。刪除dev分支就是把dev指針給刪掉,刪掉后,我們就剩下了一條master分支:

下面開始實戰。

首先,我們創建dev分支,然后切換到dev分支:

$ git checkout -b dev
Switched to a new branch 'dev'

git checkout命令加上-b參數表示創建並切換,相當於以下兩條命令:

$ git branch dev # 創建dev分支
$ git checkout dev # 切換到dev分支
Switched to branch 'dev'

然后,用git branch命令查看當前分支:

$ git branch
* dev
  master

git branch命令會列出所有分支,當前分支前面會標一個*號。

然后,我們就可以在dev分支上正常提交,比如對readme.txt做個修改,加上一行:

Creating a new branch is quick.

然后提交:

$ git add readme.txt 
$ git commit -m "branch test"
[dev b17d20e] branch test
 1 file changed, 1 insertion(+)

現在,dev分支的工作完成,我們就可以切換回master分支:

$ git checkout master
Switched to branch 'master'

切換回master分支后,再查看一個readme.txt文件,剛才添加的內容不見了!因為那個提交是在dev分支上,而master分支此刻的提交點並沒有變:

現在,我們把dev分支的工作成果合並到master分支上:

$ git merge dev
Updating d46f35e..b17d20e
Fast-forward
 readme.txt | 1 +
 1 file changed, 1 insertion(+)

git merge命令用於合並指定分支到當前分支。合並后,再查看readme.txt的內容,就可以看到,和dev分支的最新提交是完全一樣的。

注意到上面的Fast-forward信息,Git告訴我們,這次合並是“快進模式”,也就是直接把master指向dev的當前提交,所以合並速度非常快。

當然,也不是每次合並都能Fast-forward,我們后面會講其他方式的合並。

合並完成后,就可以放心地刪除dev分支了:

$ git branch -d dev
Deleted branch dev (was b17d20e).

刪除后,查看branch,就只剩下master分支了:

$ git branch
* master

因為創建、合並和刪除分支非常快,所以Git鼓勵你使用分支完成某個任務,合並后再刪掉分支,這和直接在master分支上工作效果是一樣的,但過程更安全。

解決沖突

在真正開發過程中,合並分支經常會遇到分支沖突的情況,無法直接合並,我們來模擬一下這個場景。

准備新的feature1分支,繼續我們的新分支開發:

$ git checkout -b feature1
Switched to a new branch 'feature1'

修改readme.txt最后一行,改為:

Creating a new branch is quick AND simple.

feature1分支上提交:

$ git add readme.txt

$ git commit -m "AND simple"
[feature1 14096d0] AND simple
 1 file changed, 1 insertion(+), 1 deletion(-)

切換到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還會自動提示我們當前master分支比遠程的master分支要超前1個提交。

master分支上把readme.txt文件的最后一行改為:

Creating a new branch is quick & simple.

提交:

$ git add readme.txt 
$ git commit -m "& simple"
[master 5dc6824] & simple
 1 file changed, 1 insertion(+), 1 deletion(-)

現在,master分支和feature1分支各自都分別有新的提交,變成了這樣:

這種情況下,Git無法執行“快速合並(Fast-forward)”,只能試圖把各自的修改合並起來,但這種合並就可能會有沖突,我們試試看:

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

Git告訴我們,readme.txt文件存在沖突,必須手動解決沖突后再提交。git status也可以告訴我們沖突的文件:

$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
  (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:   readme.txt

no changes added to commit (use "git add" and/or "git commit -a")

我們可以直接查看readme.txt的內容:

Git is a distributed version control system.
Git is free software distributed under the GPL.
Git has a mutable index called stage.
Git tracks changes of files.
<<<<<<< HEAD
Creating a new branch is quick & simple.
=======
Creating a new branch is quick AND simple.
>>>>>>> feature1

Git用<<<<<<<=======>>>>>>>標記出不同分支的內容,我們修改如下后保存:

Creating a new branch is quick and simple.

再提交:

$ git add readme.txt 
$ git commit -m "conflict fixed"
[master cf810e4] conflict fixed

現在,master分支和feature1分支變成了下圖所示:

用帶參數的git log也可以看到分支的合並情況:

$ git log --graph --pretty=oneline --abbrev-commit
*   cf810e4 (HEAD -> master) conflict fixed
|\  
| * 14096d0 (feature1) AND simple
* | 5dc6824 & simple
|/  
* b17d20e branch test
* d46f35e (origin/master) remove test.txt
* b84166e add test.txt
* 519219b git tracks changes
* e43a48b understand how stage works
* 1094adb append GPL
* e475afc add distributed
* eaadf4e wrote a readme file

最后,刪除feature1分支:

$ git branch -d feature1
Deleted branch feature1 (was 14096d0).

工作完成。

分支管理策略

通常,合並分支時,如果可能,Git會用Fast forward模式,但這種模式下,刪除分支后,會丟掉分支信息。

如果要強制禁用Fast forward模式,Git就會在merge時生成一個新的commit,這樣,從分支歷史上就可以看出分支信息。

下面我們實戰一下--no-ff方式的git merge

首先,仍然創建並切換dev分支:

$ git checkout -b dev
Switched to a new branch 'dev'

修改readme.txt文件,並提交一個新的commit:

$ git add readme.txt 
$ git commit -m "add merge"
[dev f52c633] add merge
 1 file changed, 1 insertion(+)

現在,我們切換回master

$ git checkout master
Switched to branch 'master'

准備合並dev分支,請注意--no-ff參數,表示禁用Fast forward

$ git merge --no-ff -m "merge with no-ff" dev
Merge made by the 'recursive' strategy.
 readme.txt | 1 +
 1 file changed, 1 insertion(+)

因為本次合並要創建一個新的commit,所以加上-m參數,把commit描述寫進去。

合並后,我們用git log看看分支歷史:

$ git log --graph --pretty=oneline --abbrev-commit
*   e1e9c68 (HEAD -> master) merge with no-ff
|\  
| * f52c633 (dev) add merge
|/  
*   cf810e4 conflict fixed
...

可以看到,不使用Fast forward模式,merge后就像這樣:

分支策略

在實際開發中,我們應該按照幾個基本原則進行分支管理:

首先,master分支應該是非常穩定的,也就是僅用來發布新版本,平時不能在上面干活;

那在哪干活呢?干活都在dev分支上,也就是說,dev分支是不穩定的,到某個時候,比如1.0版本發布時,再把dev分支合並到master上,在master分支發布1.0版本;

你和團隊同事每個人都在dev分支上干活,每個人都有自己的分支,時不時地往dev分支上合並就可以了。

所以,團隊合作的分支看起來就像這樣:

狀態存儲

當我們在開發過程中,經常遇到這樣的情況,我們需要暫時放下手中的工作,切換到其他分支進行開發,例如當我們在dev分支進行程序2.0版本開發時,發現1.0版本的程序出現了bug,必須立刻進行修復,但是在目前的dev分支我們可能已經做了很多修改,暫存區可能有了暫存狀態,甚至可能在開發過程中在dev分支進行了多次commit,這時如果我們想切換回master分支,進行bug修復,這時就需要使用到git stash命令存儲原分支當前的狀態。

在講解git stash之前,我們先考慮兩種場景:

第一種就是我們未在dev分支進行任何提交,此時HEAD指針指向dev,dev和master指向同一次commit,如下圖:

1563631355797

我們可能在dev的工作區做了很多修改,也將部分修改狀態加入了暫存區(即進行了git add操作),這時我們嘗試一下直接使用git checkout命令切換分支。

此時,Git狀態如下:

1563631585919

我們修改“file1.txt”和“file2.txt”的內容,並將“file1.txt”的改動加入暫存區。

1563631787056

此時可看出工作區和暫存區就都有改變,但HEAD指針指向的dev與master為同一個commit節點。

這時我們執行git checkout master命令嘗試切換分支。

1563631937563

可以看出,成功切換到了master分支上,而且工作區和暫存區的狀態依舊保留。

我們再考慮一個場景,在dev分支開發時,進行了一次提交,此時HEAD指向dev分支,dev分支超前master分支一次commit,具體見下圖:

1563632158366

如果此時我們工作區或暫存區有未提交更改時,就無法進行分支切換操作(如果沒有未提交修改的話當然可以進行分支切換操作)。

1563632645976

我想這時大家就會有一個疑問,為什么兩種狀態下我們都修改了暫存區和工作區的狀態,但是一個可以切換分支並且保留工作區、暫存區狀態,而另一種狀態就無法切換分支呢?

我起初在遇到這個問題的時候也是很詫異,在網上搜索了好多資料,依舊沒有查到有價值的信息。

這時我們就應該從Git的原理來進行分析了,Git在進行版本控制時,記錄的並不是文件本身的信息,而是文件的修改狀態,例如我們再一個10000行代碼的文件中,新加入了一行代碼進行,Git並不是將最新的10001行代碼作為備份,而是僅僅記錄了新舊文件之間的差異,即在哪個位置修改了什么內容(修改包括:增加、刪除、修改等)。

我們來分析一下上問題到的第一種場景:我們未在dev分支進行任何提交,此時HEAD指針指向dev,dev和master指向同一次commit。

雖然我們再dev分支的工作區和暫存區做了修改,這些修改都是基於dev指向的commit而言的,而且此時dev和master指向同一個commit,所以,該場景下,dev分支工作區和暫存區的修改依舊適用於master分支,所以可以成功切換分支。

而第二種場景:在dev分支開發時,進行了一次提交,此時HEAD指向dev分支,dev分支超前master分支一次commit。

這時,dev工作區和暫存區的狀態是基於最新的dev指向的commit而言的,已經不能應用於master指向的commit了,所以在進行切換分支時,提示報錯。

應用實例

軟件開發中,bug就像家常便飯一樣。有了bug就需要修復,在Git中,由於分支是如此的強大,所以,每個bug都可以通過一個新的臨時分支來修復,修復后,合並分支,然后將臨時分支刪除。

當你接到一個修復一個代號101的bug的任務時,很自然地,你想創建一個分支issue-101來修復它,但是,當前正在dev上進行的工作還沒有提交:

$ git status
On branch dev
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	new file:   hello.py

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

並不是你不想提交,而是工作只進行到一半,還沒法提交,預計完成還需1天時間。但是,必須在兩個小時內修復該bug,怎么辦?

幸好,Git還提供了一個stash功能,可以把當前工作現場“儲藏”起來,等以后恢復現場后繼續工作:

$ git stash
Saved working directory and index state WIP on dev: f52c633 add merge

現在,用git status查看工作區,就是干凈的(除非有沒有被Git管理的文件),因此可以放心地創建分支來修復bug。

首先確定要在哪個分支上修復bug,假定需要在master分支上修復,就從master創建臨時分支:

$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 6 commits.
  (use "git push" to publish your local commits)

$ git checkout -b issue-101
Switched to a new branch 'issue-101'

現在修復bug,需要把“Git is free software ...”改為“Git is a free software ...”,然后提交:

$ git add readme.txt 
$ git commit -m "fix bug 101"
[issue-101 4c805e2] fix bug 101
 1 file changed, 1 insertion(+), 1 deletion(-)

修復完成后,切換到master分支,並完成合並,最后刪除issue-101分支:

$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 6 commits.
  (use "git push" to publish your local commits)

$ git merge --no-ff -m "merged bug fix 101" issue-101
Merge made by the 'recursive' strategy.
 readme.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

修復好BUG之后,就可以返回原分支繼續之前的工作了。

$ git checkout dev
Switched to branch 'dev'

$ git status
On branch dev
nothing to commit, working tree clean

工作區是干凈的,剛才的工作現場存到哪去了?用git stash list命令看看:

$ git stash list
stash@{0}: WIP on dev: f52c633 add merge

工作現場還在,Git把stash內容存在某個地方了,但是需要恢復一下,有兩個辦法:

一是用git stash apply恢復,但是恢復后,stash內容並不刪除,你需要用git stash drop來刪除;

另一種方式是用git stash pop,恢復的同時把stash內容也刪了:

$ git stash pop
On branch dev
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	new file:   hello.py

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

Dropped refs/stash@{0} (5d677e2ee266f39ea296182fb2354265b91b3b2a)

再用git stash list查看,就看不到任何stash內容了:

$ git stash list

你可以多次stash,恢復的時候,先用git stash list查看,然后恢復指定的stash,用命令:

$ git stash apply stash@{0}

多人協作

當你從遠程倉庫克隆時,實際上Git自動把本地的master分支和遠程的master分支對應起來了,並且,遠程倉庫的默認名稱是origin

git remote -v查看遠程庫的詳細信息:

$ git remote -v
origin  git@github.com:guoyaohua/learngit.git (fetch)
origin  git@github.com:guoyaohua/learngit.git (push)

上面顯示了可以抓取和推送的origin的地址。如果沒有推送權限,就看不到push的地址。

推送分支

推送分支,就是把該分支上的所有本地提交推送到遠程庫。推送時,要指定本地分支,這樣,Git就會把該分支推送到遠程庫對應的遠程分支上:

$ git push origin master

如果要推送其他分支,比如dev,就改成:

$ git push origin dev

但是,並不是一定要把本地分支往遠程推送,那么,哪些分支需要推送,哪些不需要呢?

  • master分支是主分支,因此要時刻與遠程同步;
  • dev分支是開發分支,團隊所有成員都需要在上面工作,所以也需要與遠程同步;
  • bug分支只用於在本地修復bug,就沒必要推到遠程了,除非老板要看看你每周到底修復了幾個bug;
  • feature分支是否推到遠程,取決於你是否和你的小伙伴合作在上面開發。

總之,就是在Git中,分支完全可以在本地自己藏着玩,是否推送,視你的心情而定!

抓取分支

多人協作時,大家都會往masterdev分支上推送各自的修改。

現在,模擬一個你的同事,可以在另一台電腦(注意要把SSH Key添加到GitHub)或者同一台電腦的另一個目錄下克隆:

$ git clone git@github.com:guoyaohua/learngit.git
Cloning into 'learngit'...
remote: Counting objects: 40, done.
remote: Compressing objects: 100% (21/21), done.
remote: Total 40 (delta 14), reused 40 (delta 14), pack-reused 0
Receiving objects: 100% (40/40), done.
Resolving deltas: 100% (14/14), done.

當你的同事從遠程庫clone時,默認情況下,你的同事只能看到本地的master分支。不信可以用git branch命令看看:

$ git branch
* master

現在,你的同事要在dev分支上開發,就必須創建遠程origindev分支到本地,於是他用這個命令創建本地dev分支:

$ git checkout -b dev origin/dev

現在,他就可以在dev上繼續修改,然后,時不時地把dev分支push到遠程:

$ git add env.txt

$ git commit -m "add env"
[dev 7a5e5dd] add env
 1 file changed, 1 insertion(+)
 create mode 100644 env.txt

$ git push origin dev
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 308 bytes | 308.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To github.com:michaelliao/learngit.git
   f52c633..7a5e5dd  dev -> dev

你的同事已經向origin/dev分支推送了他的提交,而碰巧你也對同樣的文件作了修改,並試圖推送:

$ type env.txt
env

$ git add env.txt

$ git commit -m "add new env"
[dev 7bd91f1] add new env
 1 file changed, 1 insertion(+)
 create mode 100644 env.txt

$ git push origin dev
To github.com:michaelliao/learngit.git
 ! [rejected]        dev -> dev (non-fast-forward)
error: failed to push some refs to 'git@github.com:guoyaohua/learngit.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

推送失敗,因為你的同事的最新提交和你試圖推送的提交有沖突,解決辦法也很簡單,Git已經提示我們,先用git pull把最新的提交從origin/dev抓下來,然后,在本地合並,解決沖突,再推送:

$ git pull
There is no tracking information for the current branch.
Please specify which branch you want to merge with.
See git-pull(1) for details.

    git pull <remote> <branch>

If you wish to set tracking information for this branch you can do so with:

    git branch --set-upstream-to=origin/<branch> dev

git pull也失敗了,原因是沒有指定本地dev分支與遠程origin/dev分支的鏈接,根據提示,設置devorigin/dev的鏈接:

$ git branch --set-upstream-to=origin/dev dev
Branch 'dev' set up to track remote branch 'dev' from 'origin'.

再pull:

$ git pull
Auto-merging env.txt
CONFLICT (add/add): Merge conflict in env.txt
Automatic merge failed; fix conflicts and then commit the result.

這回git pull成功,但是合並有沖突,需要手動解決,解決的方法和分支管理中的解決沖突完全一樣。解決后,提交,再push:

$ git commit -m "fix env conflict"
[dev 57c53ab] fix env conflict

$ git push origin dev
Counting objects: 6, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 621 bytes | 621.00 KiB/s, done.
Total 6 (delta 0), reused 0 (delta 0)
To git@github.com:guoyaohua/learngit.git
   7a5e5dd..57c53ab  dev -> dev

因此,多人協作的工作模式通常是這樣:

  1. 首先,可以試圖用git push origin <branch-name>推送自己的修改;
  2. 如果推送失敗,則因為遠程分支比你的本地更新,需要先用git pull試圖合並;
  3. 如果合並有沖突,則解決沖突,並在本地提交;
  4. 沒有沖突或者解決掉沖突后,再用git push origin <branch-name>推送就能成功!

如果git pull提示no tracking information,則說明本地分支和遠程分支的鏈接關系沒有創建,用命令git branch --set-upstream-to <branch-name> origin/<branch-name>

這就是多人協作的工作模式,一旦熟悉了,就非常簡單。

Rebase

git rebasegit merge做的事其實是一樣的。它們都被設計來將一個分支的更改並入另一個分支,只不過方式有些不同。

git rebase用於把一個分支的修改合並到當前分支。

假設你現在基於遠程分支"origin",創建一個叫"mywork"的分支。

$ git checkout -b mywork origin

假設遠程分支"origin"已經有了2個提交,如圖:

現在我們在這個分支做一些修改,然后生成兩個提交(commit)。

但是與此同時,有些人也在"origin"分支上做了一些修改並且做了提交了. 這就意味着"origin"和"mywork"這兩個分支各自"前進"了,它們之間"分叉"了。

在這里,你可以用“pull"命令把“origin”分支上的修改拉下來並且和你的修改合並; 結果看起來就像一個新的"合並的提交"(merge commit):

但是,如果你想讓“mywork”分支歷史看起來像沒有經過任何合並一樣,你也許可以用 git rebase

$ git checkout mywork
$ git rebase origin

這些命令會把你的"mywork"分支里的每個提交(commit)取消掉,並且把它們臨時保存為補丁(patch)(這些補丁放到".git/rebase"目錄中),然后把"mywork"分支更新為最新的"origin"分支,最后把保存的這些補丁應用到"mywork"分支上。

當"mywork"分支更新之后,它會指向這些新創建的提交(commit),而那些老的提交會被丟棄。 如果運行垃圾收集命令(pruning garbage collection),這些被丟棄的提交就會刪除。

現在我們可以看一下用merge和用rebase所產生的歷史的區別:

1563677573806

當我們使用git log來參看commit時,其commit的順序也有所不同。

假設C3提交於9:00AM,C5提交於10:00AM,C4提交於11:00AM,C6提交於12:00AM

對於使用git merge來合並所看到的commit的順序(從新到舊)是:

C7,C6,C4,C5,C3,C2,C1

對於使用git rebase來合並所看到的commit的順序(從新到舊)是:

C7,C6',C5',C4,C3,C2,C1

因為C6'提交只是C6提交的克隆,C5'提交只是C5提交的克隆,

從用戶的角度看使用git rebase來合並后所看到的commit的順序(從新到舊)是:

C7,C6,C5,C4,C3,C2,C1

另外,我們在使用git pull命令的時候,可以使用--rebase參數,即git pull --rebase,這里Git會把你的本地當前分支里的每個提交(commit)取消掉,並且把它們臨時保存為補丁(patch)(這些補丁放到".git/rebase"目錄中),然后把分支更新 為最新的"origin"分支,最后把保存的這些補丁應用到分支上。

解決沖突

在rebase的過程中,也許會出現沖突(conflict)。在這種情況,Git會停止rebase並會讓你去解決沖突。rebase和merge的另一個區別是rebase的沖突是一個一個解決,如果有十個沖突,在解決完第一個沖突后,用"git add"命令去更新這些內容的索引(index),然后,你無需執行 git-commit,只要執行:

$ git add -u 
$ git rebase --continue

繼續后才會出現第二個沖突,直到所有沖突解決完,而merge是所有的沖突都會顯示出來。

在任何時候,你可以用--abort參數來終止rebase的行動,並且"mywork" 分支會回到rebase開始前的狀態。

$ git rebase --abort

所以rebase的工作流就是

git rebase 
while(存在沖突) {
    git status
    # 找到當前沖突文件,編輯解決沖突
    git add -u
    git rebase --continue
    if( git rebase --abort )
        break; 
}

最后沖突全部解決,rebase成功。

標簽管理

發布一個版本時,我們通常先在版本庫中打一個標簽(tag),這樣,就唯一確定了打標簽時刻的版本。將來無論什么時候,取某個標簽的版本,就是把那個打標簽的時刻的歷史版本取出來。所以,標簽也是版本庫的一個快照。

Git的標簽雖然是版本庫的快照,但其實它就是指向某個commit的指針(跟分支很像,但是分支可以移動,標簽不能移動),所以,創建和刪除標簽都是瞬間完成的。

Git有commit,為什么還要引入tag?

“請把上周一的那個版本打包發布,commit號是6a5819e...”

“一串亂七八糟的數字不好找!”

如果換一個辦法:

“請把上周一的那個版本打包發布,版本號是v1.2”

“好的,按照tag v1.2查找commit就行!”

所以,tag就是一個讓人容易記住的有意義的名字,它跟某個commit綁在一起。

創建標簽

在Git中打標簽非常簡單,首先,切換到需要打標簽的分支上:

$ git branch
* dev
  master
$ git checkout master
Switched to branch 'master'

然后,敲命令git tag <name>就可以打一個新標簽:

$ git tag v1.0

可以用命令git tag查看所有標簽:

$ git tag
v1.0

默認標簽是打在最新提交的commit上的。有時候,如果忘了打標簽,比如,現在已經是周五了,但應該在周一打的標簽沒有打,怎么辦?

方法是找到歷史提交的commit id,然后打上就可以了:

$ git log --pretty=oneline --abbrev-commit
12a631b (HEAD -> master, tag: v1.0, origin/master) merged bug fix 101
4c805e2 fix bug 101
e1e9c68 merge with no-ff
f52c633 add merge
cf810e4 conflict fixed
5dc6824 & simple
14096d0 AND simple
b17d20e branch test
d46f35e remove test.txt
b84166e add test.txt
519219b git tracks changes
e43a48b understand how stage works
1094adb append GPL
e475afc add distributed
eaadf4e wrote a readme file

比方說要對add merge這次提交打標簽,它對應的commit id是f52c633,敲入命令:

$ git tag v0.9 f52c633

再用命令git tag查看標簽:

$ git tag
v0.9
v1.0

注意,標簽不是按時間順序列出,而是按字母排序的。可以用git show <tagname>查看標簽信息:

$ git show v0.9
commit f52c63349bc3c1593499807e5c8e972b82c8f286 (tag: v0.9)
Author: Yaohua Guo <guo.yaohua@foxmail.com>
Date:   Fri May 18 21:56:54 2018 +0800

    add merge

diff --git a/readme.txt b/readme.txt
...

可以看到,v0.9確實打在add merge這次提交上。

還可以創建帶有說明的標簽,用-a指定標簽名,-m指定說明文字:

$ git tag -a v0.1 -m "version 0.1 released" 1094adb

用命令git show <tagname>可以看到說明文字:

$ git show v0.1
tag v0.1
Tagger: Yaohua Guo <guo.yaohua@foxmail.com>
Date:   Fri May 18 22:48:43 2018 +0800

version 0.1 released

commit 1094adb7b9b3807259d8cb349e7df1d4d6477073 (tag: v0.1)
Author: Yaohua Guo <guo.yaohua@foxmail.com>
Date:   Fri May 18 21:06:15 2018 +0800

    append GPL

diff --git a/readme.txt b/readme.txt
...

操作標簽

如果標簽打錯了,也可以刪除:

$ git tag -d v0.1
Deleted tag 'v0.1' (was f15b0dd)

因為創建的標簽都只存儲在本地,不會自動推送到遠程。所以,打錯的標簽可以在本地安全刪除。

如果要推送某個標簽到遠程,使用命令git push origin <tagname>

$ git push origin v1.0
Total 0 (delta 0), reused 0 (delta 0)
To git@github.com:guoyaohua/learngit.git
 * [new tag]         v1.0 -> v1.0

或者,一次性推送全部尚未推送到遠程的本地標簽:

$ git push origin --tags
Total 0 (delta 0), reused 0 (delta 0)
To git@github.com:guoyaohua/learngit.git
 * [new tag]         v0.9 -> v0.9

如果標簽已經推送到遠程,要刪除遠程標簽就麻煩一點,先從本地刪除:

$ git tag -d v0.9
Deleted tag 'v0.9' (was f52c633)

然后,從遠程刪除。刪除命令也是push,但是格式如下:

$ git push origin :refs/tags/v0.9
To git@github.com:guoyaohua/learngit.git
 - [deleted]         v0.9

要看看是否真的從遠程庫刪除了標簽,可以登陸GitHub查看。

自定義Git

忽略特殊文件

有些時候,你必須把某些文件放到Git工作目錄中,但又不能提交它們,比如保存了數據庫密碼的配置文件啦,等等,每次git status都會顯示Untracked files ...,有強迫症的朋友心里肯定不爽。

好在Git考慮到了大家的感受,這個問題解決起來也很簡單,在Git工作區的根目錄下創建一個特殊的.gitignore文件,然后把要忽略的文件名填進去,Git就會自動忽略這些文件。

不需要從頭寫.gitignore文件,GitHub已經為我們准備了各種配置文件,只需要組合一下就可以使用了。所有配置文件可以直接在線瀏覽:https://github.com/github/gitignore

忽略文件的原則是:

  1. 忽略操作系統自動生成的文件,比如縮略圖等;
  2. 忽略編譯生成的中間文件、可執行文件等,也就是如果一個文件是通過另一個文件自動生成的,那自動生成的文件就沒必要放進版本庫,比如Java編譯產生的.class文件;
  3. 忽略你自己的帶有敏感信息的配置文件,比如存放口令的配置文件。

舉個例子:

假設你在Windows下進行Python開發,Windows會自動在有圖片的目錄下生成隱藏的縮略圖文件,如果有自定義目錄,目錄下就會有Desktop.ini文件,因此你需要忽略Windows自動生成的垃圾文件:

# Windows:
Thumbs.db
ehthumbs.db
Desktop.ini

然后,繼續忽略Python編譯產生的.pyc.pyodist等文件或目錄:

# Python:
*.py[cod]
*.so
*.egg
*.egg-info
dist
build

加上你自己定義的文件,最終得到一個完整的.gitignore文件,內容如下:

# Windows:
Thumbs.db
ehthumbs.db
Desktop.ini

# Python:
*.py[cod]
*.so
*.egg
*.egg-info
dist
build

# My configurations:
db.ini
deploy_key_rsa

最后一步就是把.gitignore也提交到Git,就完成了!當然檢驗.gitignore的標准是git status命令是不是說working directory clean

使用Windows的朋友注意了,如果你在資源管理器里新建一個.gitignore文件,它會非常弱智地提示你必須輸入文件名,但是在文本編輯器里“保存”或者“另存為”就可以把文件保存為.gitignore了。

有些時候,你想添加一個文件到Git,但發現添加不了,原因是這個文件被.gitignore忽略了:

$ git add App.class
The following paths are ignored by one of your .gitignore files:
App.class
Use -f if you really want to add them.

如果你確實想添加該文件,可以用-f強制添加到Git:

$ git add -f App.class

或者你發現,可能是.gitignore寫得有問題,需要找出來到底哪個規則寫錯了,可以用git check-ignore命令檢查:

$ git check-ignore -v App.class
.gitignore:3:*.class	App.class

Git會告訴我們,.gitignore的第3行規則忽略了該文件,於是我們就可以知道應該修訂哪個規則。

配置別名

有沒有經常敲錯命令?比如git statusstatus這個單詞真心不好記。

如果敲git st就表示git status那就簡單多了,當然這種偷懶的辦法我們是極力贊成的。

我們只需要敲一行命令,告訴Git,以后st就表示status

$ git config --global alias.st status

好了,現在敲git st看看效果。

當然還有別的命令可以簡寫,很多人都用co表示checkoutci表示commitbr表示branch

$ git config --global alias.co checkout
$ git config --global alias.ci commit
$ git config --global alias.br branch

以后提交就可以簡寫成:

$ git ci -m "bala bala bala..."

--global參數是全局參數,也就是這些命令在這台電腦的所有Git倉庫下都有用。

在撤銷修改一節中,我們知道,命令git reset HEAD file可以把暫存區的修改撤銷掉(unstage),重新放回工作區。既然是一個unstage操作,就可以配置一個unstage別名:

$ git config --global alias.unstage 'reset HEAD'

當你敲入命令:

$ git unstage test.py

實際上Git執行的是:

$ git reset HEAD test.py

配置一個git last,讓其顯示最后一次提交信息:

$ git config --global alias.last 'log -1'

這樣,用git last就能顯示最近一次的提交:

$ git last
commit adca45d317e6d8a4b23f9811c3d7b7f0f180bfe2
Merge: bd6ae48 291bea8
Author: Yaohua Guo <Guo.Yaohua@foxmail.com>
Date:   Thu Aug 22 22:49:22 2013 +0800

    merge & fix hello.py

甚至可以進一步美化把lg配置成:

$ 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的時候,加上--global是針對當前用戶起作用的,如果不加,那只針對當前的倉庫起作用。

配置文件放哪了?每個倉庫的Git配置文件都放在.git/config文件中:

$ type .git/config 
[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
    logallrefupdates = true
    ignorecase = true
    precomposeunicode = true
[remote "origin"]
    url = git@github.com:michaelliao/learngit.git
    fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
    remote = origin
    merge = refs/heads/master
[alias]
    last = log -1

別名就在[alias]后面,要刪除別名,直接把對應的行刪掉即可。

而當前用戶的Git配置文件放在用戶主目錄下的一個隱藏文件.gitconfig中:

$ type .gitconfig
[alias]
    co = checkout
    ci = commit
    br = branch
    st = status
[user]
    name = Your Name
    email = your@email.com

配置別名也可以直接修改這個文件,如果改錯了,可以刪掉文件重新通過命令配置。

總結

  1. Git記錄的是文件的修改狀態,而不是文件本身。
  2. 初始化一個Git倉庫,使用git init命令。
  3. 添加文件到Git倉庫,分兩步:
    • 使用命令git add <file>,注意,可反復多次使用,添加多個文件;
    • 使用命令git commit -m <message>,完成。
  4. 每次修改,如果不用git add到暫存區,那就不會加入到commit中。
  5. 提交后,可用git diff HEAD -- <file_name>命令可以查看工作區和版本庫里面最新版本的區別。
  6. 要關聯一個遠程庫,使用命令git remote add origin git@server-name:path/repo-name.git,使用命令git push -u origin master第一次推送master分支的所有內容,此后,每次本地提交后,只要有必要,就可以使用命令git push origin master推送最新修改。
  7. 要克隆一個倉庫,首先必須知道倉庫的地址,然后使用git clone命令克隆。Git支持多種協議,包括https,但通過ssh支持的原生git協議速度最快。
  8. HEAD指向的版本就是當前版本,因此,Git允許我們在版本的歷史之間穿梭,使用命令git reset --hard commit_id`。
  9. 穿梭前,用git log可以查看提交歷史,以便確定要回退到哪個版本。
  10. 要重返未來,用git reflog查看命令歷史,以便確定要回到未來的哪個版本。
  11. 當你改亂了工作區某個文件的內容,想直接丟棄工作區的修改時,用命令git checkout -- file
  12. 當你不但改亂了工作區某個文件的內容,還添加到了暫存區時,想丟棄修改,分兩步,第一步用命令git reset HEAD <file>,第二步按上一條操作。
  13. 已經提交了不合適的修改到版本庫時,想要撤銷本次提交,參考版本回退,不過前提是沒有推送到遠程庫。
  14. 命令git rm用於刪除一個文件。如果一個文件已經被提交到版本庫,那么你永遠不用擔心誤刪,但是要小心,你只能恢復文件到最新版本,你會丟失最近一次提交后你修改的內容
  15. Git鼓勵大量使用分支:
    • 查看分支:git branch
    • 創建分支:git branch <name>
    • 切換分支:git checkout <name>
    • 創建+切換分支:git checkout -b <name>
    • 合並某分支到當前分支:git merge <name>
    • 刪除分支:git branch -d <name>
  16. 當Git無法自動合並分支時,就必須首先解決沖突。解決沖突后,再提交,合並完成。解決沖突就是把Git合並失敗的文件手動編輯為我們希望的內容,再提交。
  17. git log --graph命令可以看到分支合並圖。
  18. 合並分支時,加上--no-ff參數就可以用普通模式合並,合並后的歷史有分支,能看出來曾經做過合並,而fast forward合並就看不出來曾經做過合並。
  19. 切換分支使用git checkout <master> ,HEAD指向master,工作區也恢復到master的狀態。
  20. 開發一個新feature,最好新建一個分支。
  21. 如果要丟棄一個沒有被合並過的分支,可以通過git branch -D <name>強行刪除。
  22. 查看遠程庫信息,使用git remote -v
  23. 本地新建的分支如果不推送到遠程,對其他人就是不可見的。
  24. 從本地推送分支,使用git push origin branch-name,如果推送失敗,先用git pull抓取遠程的新提交。
  25. 在本地創建和遠程分支對應的分支,使用git checkout -b branch-name origin/branch-name,本地和遠程分支的名稱最好一致。
  26. 建立本地分支和遠程分支的關聯,使用git branch --set-upstream branch-name origin/branch-name
  27. 從遠程抓取分支,使用git pull,如果有沖突,要先處理沖突。
  28. 命令git tag <tagname>用於新建一個標簽,默認為HEAD,也可以指定一個commit id。
  29. 命令git tag -a <tagname> -m "blablabla..."可以指定標簽信息。
  30. 命令git tag可以查看所有標簽。
  31. 忽略某些文件時,需要編寫.gitignore
  32. .gitignore文件本身要放到版本庫里,並且可以對.gitignore做版本管理。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM