GIT 學習手冊簡介
本站爲 Git 學習參考手冊。目的是爲學習與記憶 Git 使用中最重要、最廣泛的命令提供快速翻閱。 這些命令以你可能須要的操做類型劃分,而且將提供平常使用中須要的一些經常使用的命令以及參數。html
1、獲取與建立項目
在學習和使用git以前,咱們得先了解一下git的一些概念,在瞭解了這些概念和關係以後,對於後面的學習相當重要!java
上面展現的是git的幾個關鍵的專用名詞,以及6個常見命令。git
Workspace:工做區 Index / Stage:暫存區 Repository:倉庫區(或本地倉庫) Remote:遠程倉庫
請務必理解這幾者之間的做用和關係!!而後纔開始咱們學習。github
而後,咱們開始學習之路~shell
倉庫是 Git 存放你要保存的快照的數據的地方。你得先有一個 Git 倉庫,才能用它進行操做。數據庫
擁有一個 Git 倉庫的途徑有兩種。在已有的目錄中,初始化一個新的,其一。 好比一個新的項目,或者一個已存在的項目,但該項目還沒有有版本控制。若是你想要複製一份別人的項目, 或者與別人合做某個項目,也能夠從一個公開的 Git 倉庫克隆,其二。本章將對二者都作介紹。編程
Git使用前配置
若是設置了,在輸入命令示界面能夠很方便的使用複製和粘貼(用左鍵選取要複製的,點右鍵直接就能夠複製,粘貼時只需點一下右鍵。)設置方法:Git Bash快捷圖標(桌面圖標)右鍵屬性-選項,把快速編輯模式勾上就能夠,以下圖:swift
設置Git本地項目開發庫默認路徑
若是設置了,就不用每次打開Git再cd打開目錄了。方法:右鍵Git Bash快捷圖標(桌面圖標)屬性,找到快捷方式-起始位置,把你的項目地址放在這裏就能夠了。以下圖:vim
配置本地用戶和郵箱
用戶名郵箱做用 : 咱們須要設置一個用戶名 和 郵箱, 這是用來上傳本地倉庫到GitHub中,在GitHub中顯示代碼上傳者。它能夠在用戶主目錄下(全局配置),也能夠在項目目錄下(項目配置)。
使用命令 :緩存
git config [--global] user.name "HanShuliang" //設置用戶名 git config [--global] user.email "13241153187@163.com" //設置郵箱
到此Git客戶端已安裝及GitHub配置完成,如今能夠從GitHub傳輸代碼了。
git init 將一個目錄初始化爲 Git 倉庫
在目錄中執行 git init
,就能夠建立一個 Git 倉庫了。好比,咱們剛好有個目錄,裏頭有些許文件,以下:
$ cd konnichiwa
$ ls
README hello.rb
在這個項目裏頭,咱們會用各類編程語言寫 「Hello World」 實例。 到目前爲止,咱們只有 Ruby 的,不過,這纔剛上路嘛。爲了開始用 Git 對這個項目做版本控制,咱們執行一下 git init
。
$ git init Initialized empty Git repository in /opt/konnichiwa/.git/ # 在 /opt/konnichiwa/.git 目錄初始化空 Git 倉庫完畢。
如今你能夠看到在你的項目目錄中有個 .git
的子目錄。 這就是你的 Git 倉庫了,全部有關你的此項目的快照數據都存放在這裏。
$ ls -a
. .. .git README hello.rb
恭喜,如今你就有了一個 Git 倉庫的架子,能夠開始快照你的項目了。
簡而言之,用
git init
來在目錄中建立新的 Git 倉庫。 你能夠在任什麼時候候、任何目錄中這麼作,徹底是本地化的。
git clone 複製一個 Git 倉庫,以上下其手
若是你須要與他人合做一個項目,或者想要複製一個項目,看看代碼,你就能夠克隆那個項目。 執行 git clone [url]
,[url] 爲你想要複製的項目,就能夠了。
$ git clone git://github.com/schacon/simplegit.git Initialized empty Git repository in /private/tmp/simplegit/.git/ remote: Counting objects: 100, done. remote: Compressing objects: 100% (86/86), done. remote: Total 100 (delta 35), reused 0 (delta 0) Receiving objects: 100% (100/100), 9.51 KiB, done. Resolving deltas: 100% (35/35), done. $ cd simplegit/ $ ls README Rakefile lib
上述操做將複製該項目的所有記錄,讓你本地擁有這些。而且該操做將拷貝該項目的主分支, 使你可以查看代碼,或編輯、修改。進到該目錄中,你會看到 .git
子目錄。 全部的項目數據都存在那裏。
$ ls -a . .. .git README Rakefile lib $ cd .git $ ls HEAD description info packed-refs branches hooks logs refs config index objects
默認狀況下,Git 會按照你提供的 URL 所指示的項目的名稱建立你的本地項目目錄。 一般就是該 URL 最後一個 /
以後的任何東西。若是你想要一個不同的名字, 你能夠在該命令後加上它,就在那個 URL 後面。
簡而言之,使用
git clone
拷貝一個 Git 倉庫到本地,讓本身可以查看該項目,或者進行修改。
2、基本快照
Git 的工做就是建立和保存你的項目的快照及與以後的快照進行對比。本章將對有關建立與提交你的項目的快照的命令做介紹。
這裏有個重要的概念,Git 有一個叫作「索引」的東東,有點像是你的快照的緩存區。這就使你可以從更改的文件中建立出一系列組織良好的快照,而不是一次提交全部的更改。
簡而言之,使用
git add
添加須要追蹤的新文件和待提交的更改, 而後使用git status
和git diff
查看有何改動, 最後用git commit
將你的快照記錄。這就是你要用的基本流程,絕大部分時候都是這樣的。
git add 添加文件到緩存
在 Git 中,在提交你修改的文件以前,你須要把它們添加到緩存。若是該文件是新建立的,你能夠執行 git add
將該文件添加到緩存,可是,即便該文件已經被追蹤了 —— 也就是說,曾經提交過了 —— 你仍然須要執行 git add 將新更改的文件添加到緩存去。讓咱們看幾個例子:
回到咱們的 Hello World 示例,初始化該項目以後,咱們就要用 git add
將咱們的文件添加進去了。 咱們能夠用 git status
看看咱們的項目的當前狀態。
$ git status -s ?? README ?? hello.rb
咱們有倆還沒有被追蹤的文件,得添加一下。
$ git add README hello.rb
如今咱們再執行 git status
,就能夠看到這倆文件已經加上去了。
$ git status -s
A README
A hello.rb
新項目中,添加全部文件很廣泛,能夠在當前工做目錄執行命令:git add .
。 由於 Git 會遞歸地將你執行命令時所在的目錄中的全部文件添加上去,因此若是你將當前的工做目錄做爲參數, 它就會追蹤那兒的全部文件了。如此,git add .
就和 git add README hello.rb
有同樣的效果。 此外,效果一致的還有 git add *
,不過那只是由於咱們這還木有子目錄,不須要遞歸地添加新文件。
好了,如今咱們改個文件,再跑一下 git status
,有點古怪。
$ vim README $ git status -s AM README A hello.rb
「AM」 狀態的意思是,這個文件在咱們將它添加到緩存以後又有改動。這意味着若是咱們如今提交快照, 咱們記錄的將是上次跑 git add
的時候的文件版本,而不是如今在磁盤中的這個。 Git 並不認爲磁盤中的文件與你想快照的文件必須是一致的 —— (若是你須要它們一致,)得用 git add
命令告訴它。
一言以蔽之, 當你要將你的修改包含在即將提交的快照裏的時候,執行
git add
。 任何你沒有添加的改動都不會被包含在內 —— 這意味着你能夠比絕大多數其餘源代碼版本控制系統更精確地歸置你的快照。
git status 查看你的文件在工做目錄與緩存的狀態
正如你在 git add
小節中所看到的,你能夠執行 git status
命令查看你的代碼在緩存與當前工做目錄的狀態。我演示該命令的時候加了 -s
參數,以得到簡短的結果輸出。 若沒有這個標記,命令 git status
將告訴你更多的提示與上下文欣喜。 如下即是一樣狀態下,有跟沒有 -s
參數的輸出對比。簡短的輸出以下:
$ git status -s
AM README
A hello.rb
而一樣的狀態,詳細的輸出看起來是這樣的:
$ git status # On branch master # # Initial commit # # Changes to be committed: # (use "git rm --cached <file>..." to unstage) # # new file: README # new file: hello.rb # # Changed but not updated: # (use "git add <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) # # modified: README #
你很容易發現簡短的輸出看起來很緊湊。而詳細輸出則頗有幫助,提示你能夠用何種命令完成你接下來可能要作的事情。
Git 還會告訴你在你上次提交以後,有哪些文件被刪除、修改或者存入緩存了。
$ git status -s
M README
D hello.rb
你能夠看到,在簡短輸出中,有兩欄。第一欄是緩存的,第二欄則是工做目錄的。 因此假設你臨時提交了 README 文件,而後又改了些,而且沒有執行 git add
,你會看到這個:
$ git status -s
MM README
D hello.rb
一言以蔽之,執行
git status
以查看在你上次提交以後有啥被修改或者臨時提交了, 從而決定本身是否須要提交一次快照,同時也能知道有什麼改變被記錄進去了。
git diff 顯示已寫入緩存與已修改但還沒有寫入緩存的改動的區別
git diff
有兩個主要的應用場景。咱們將在此介紹其一, 在 檢閱與對照 一章中,咱們將介紹其二。 咱們這裏介紹的方式是用此命令描述已臨時提交的或者已修改但還沒有提交的改動。
git diff #還沒有緩存的改動
若是沒有其餘參數,git diff
會以規範化的 diff 格式(一個補丁)顯示自從你上次提交快照以後還沒有緩存的全部更改。
$ vim hello.rb $ git status -s M hello.rb $ git diff diff --git a/hello.rb b/hello.rb index d62ac43..8d15d50 100644 --- a/hello.rb +++ b/hello.rb @@ -1,7 +1,7 @@ class HelloWorld def self.hello - puts "hello world" + puts "hola mundo" end end
因此,git status
顯示你上次提交更新至後所更改或者寫入緩存的改動, 而 git diff
一行一行地顯示這些改動具體是啥。 一般執行完 git status
以後接着跑一下 git diff
是個好習慣。
git diff –cached #查看已緩存的改動
git diff --cached
命令會告訴你有哪些內容已經寫入緩存了。 也就是說,此命令顯示的是接下來要寫入快照的內容。因此,若是你將上述示例中的 hello.rb
寫入緩存,由於 git diff
顯示的是還沒有緩存的改動,因此在此執行它不會顯示任何信息。
$ git status -s M hello.rb $ git add hello.rb $ git status -s M hello.rb $ git diff $
若是你想看看已緩存的改動,你須要執行的是 git diff --cached
。
$ git status -s M hello.rb $ git diff $ $ git diff --cached diff --git a/hello.rb b/hello.rb index d62ac43..8d15d50 100644 --- a/hello.rb +++ b/hello.rb @@ -1,7 +1,7 @@ class HelloWorld def self.hello - puts "hello world" + puts "hola mundo" end end
git diff HEAD 查看已緩存的與未緩存的全部改動
若是你想一併查看已緩存的與未緩存的改動,能夠執行 git diff HEAD
—— 也就是說你要看到的是工做目錄與上一次提交的更新的區別,無視緩存。 假設咱們又改了些 ruby.rb
的內容,那緩存的與未緩存的改動咱們就都有了。 以上三個 diff
命令的結果以下:
$ vim hello.rb $ git diff diff --git a/hello.rb b/hello.rb index 4f40006..2ae9ba4 100644 --- a/hello.rb +++ b/hello.rb @@ -1,7 +1,7 @@ class HelloWorld + # says hello def self.hello puts "hola mundo" end end $ git diff --cached diff --git a/hello.rb b/hello.rb index 2aabb6e..4f40006 100644 --- a/hello.rb +++ b/hello.rb @@ -1,7 +1,7 @@ class HelloWorld def self.hello - puts "hello world" + puts "hola mundo" end end $ git diff HEAD diff --git a/hello.rb b/hello.rb index 2aabb6e..2ae9ba4 100644 --- a/hello.rb +++ b/hello.rb @@ -1,7 +1,8 @@ class HelloWorld + # says hello def self.hello - puts "hello world" + puts "hola mundo" end end
git diff –stat 顯示摘要而非整個 diff
若是咱們不想要看整個 diff 輸出,可是又想比 git status
詳細點, 就能夠用 --stat
選項。該選項使它顯示摘要而非全文。上文示例在使用 --stat
選項時,輸出以下:
$ git status -s MM hello.rb $ git diff --stat hello.rb | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) $ git diff --cached --stat hello.rb | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-) $ git diff HEAD --stat hello.rb | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-)
你還能夠在上述命令後面制定一個目錄,從而只查看特定文件或子目錄的 diff
輸出。
簡而言之, 執行
git diff
來查看執行git status
的結果的詳細信息 —— 一行一行地顯示這些文件是如何被修改或寫入緩存的。
git commit 記錄緩存內容的快照
如今你使用 git add
命令將想要快照的內容寫入了緩存, 執行 git commit
就將它實際存儲快照了。 Git 爲你的每個提交都記錄你的名字與電子郵箱地址,因此第一步是告訴 Git 這些都是啥。
$ git config --global user.name 'Your Name'
$ git config --global user.email you@somedomain.com
讓咱們寫入緩存,並提交對 hello.rb
的全部改動。在首個例子中,咱們使用 -m
選項以在命令行中提供提交註釋。
$ git add hello.rb $ git status -s M hello.rb $ git commit -m 'my hola mundo changes' [master 68aa034] my hola mundo changes 1 files changed, 2 insertions(+), 1 deletions(-)
如今咱們已經記錄了快照。若是咱們再執行 git status
,會看到咱們有一個「乾淨的工做目錄」。 這意味着咱們在最近一次提交以後,沒有作任何改動 —— 在咱們的項目中沒有未快照的工做。
$ git status
# On branch master
nothing to commit (working directory clean)
若是你漏掉了 -m
選項,Git 會嘗試爲你打開一個編輯器以填寫提交信息。 若是 Git 在你對它的配置中找不到相關信息,默認會打開 vim
。屏幕會像這樣:
# Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # modified: hello.rb # ~ ~ ".git/COMMIT_EDITMSG" 9L, 257C
在此,你在文件頭部添加實際的提交信息。以「#」開頭的行都會被無視 ——Git 將 git status
的輸出結果放在那兒以提示你都改了、緩存了啥。
一般,撰寫良好的提交信息是很重要的。以開放源代碼項目爲例,多多少少以如下格式寫你的提示消息是個不成文的規定:
簡短的關於改動的總結(25個字或者更少) 若是有必要,更詳細的解釋文字。約 36 字時換行。在某些狀況下, 第一行會被做爲電子郵件的開頭,而剩餘的則會做爲郵件內容。 將小結從內容隔開的空行是相當重要的(除非你沒有內容); 若是這兩個待在一塊兒,有些 git 工具會犯迷糊。 空行以後是更多的段落。 - 列表也能夠 - 一般使用連字符(-)或者星號(*)來標記列表,前面有個空格, 在列表項之間有空行,不過這些約定也會有些變化。 # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # modified: hello.rb # ~ ~ ~ ".git/COMMIT_EDITMSG" 25L, 884C written
提交註解是很重要的。由於 Git 很大一部分能耐就是它在組織本地提交和與他人分享的彈性, 它很給力地可以讓你爲邏輯獨立的改變寫三到四條提交註解,以便你的工做被同仁審閱。由於提交與推送改動是有區別的, 請務必花時間將各個邏輯獨立的改動放到另一個提交,並附上一份良好的提交註解, 以使與你合做的人可以方便地瞭解你所作的,以及你爲什麼要這麼作。
git commit -a 自動將在提交前將已記錄、修改的文件放入緩存區
若是你以爲 git add
提交緩存的流程太過繁瑣,Git 也容許你用 -a
選項跳過這一步。 基本上這句話的意思就是,爲任何已有記錄的文件執行 git add
—— 也就是說,任何在你最近的提交中已經存在,而且以後被修改的文件。 這讓你可以用更 Subversion 方式的流程,修改些文件,而後想要快照全部所作的改動的時候執行 git commit -a
。 不過你仍然須要執行 git add
來添加新文件,就像 Subversion 同樣。
$ vim hello.rb $ git status -s M hello.rb $ git commit -m 'changes to hello file' # On branch master # Changed but not updated: # (use "git add <file>..." to update what will be committed) # (use "git checkout -- <file>..." to discard changes in working directory) # # modified: hello.rb # no changes added to commit (use "git add" and/or "git commit -a") $ git commit -am 'changes to hello file' [master 78b2670] changes to hello file 1 files changed, 2 insertions(+), 1 deletions(-)
注意,若是你不緩存改動,直接執行 git commit
,Git 會直接給出 git status
命令的輸出,提醒你啥也沒緩存。我已將該消息中的重要部分高亮,它說沒有添加須要提交的緩存。 若是你使用 -a
,它會緩存並提交每一個改動(不含新文件)。
如今你就完成了整個快照的流程 ——改些文件,而後用 git add
將要提交的改動提交到緩存, 用 git status
和 git diff
看看你都改了啥,最後 git commit
永久地保存快照。
簡而言之,執行
git commit
記錄緩存區的快照。若是須要的話,這個快照能夠用來作比較、共享以及恢復。
git reset HEAD 取消緩存已緩存的內容
git reset
多是人類寫的最費解的命令了。 我用 Git 有些年頭了,甚至還寫了本書,但有的時候仍是會搞不清它會作什麼。 因此,我只說三個明確的,一般有用的調用。請你跟我同樣儘管用它 —— 由於它能夠頗有用。
在此例中,咱們能夠用它來將不當心緩存的東東取消緩存。假設你修改了兩個文件,想要將它們記錄到兩個不一樣的提交中去。 你應該緩存並提交一個,再緩存並提交另一個。若是你不當心兩個都緩存了,那要如何才能取消緩存呢? 你能夠用 git reset HEAD -- file
。 技術上說,在這裏你不須要使用 --
—— 它用來告訴 Git 這時你已經再也不列選項,剩下的是文件路徑了。 不過養成使用它分隔選項與路徑的習慣很重要,即便在你可能並不須要的時候。
好,讓咱們看看取消緩存是什麼樣子的。這裏咱們有兩個最近提交以後又有所改動的文件。咱們將兩個都緩存,並取消緩存其中一個。
$ git status -s M README M hello.rb $ git add . $ git status -s M README M hello.rb $ git reset HEAD -- hello.rb Unstaged changes after reset: M hello.rb $ git status -s M README M hello.rb
如今你執行 git commit
將只記錄 README
文件的改動,並不含如今並不在緩存中的 hello.rb
。
若是你好奇,它實際的操做是將該文件在「索引」中的校驗和重置爲最近一次提交中的值。 git add
會計算一個文件的校驗和,將它添加到「索引」中, 而 git reset HEAD
將它改寫回原先的,從而取消緩存操做。
若是你想直接執行 git unstage
,你能夠在 Git 中配置個別名。 執行 git config --global alias.unstage "reset HEAD"
便可。 一旦執行完它,你就能夠直接用 git unstage [file]
做爲代替了。
若是你忘了取消緩存的命令,Git 的常規 git status
輸出的提示會頗有幫助。 例如,在你有已緩存的文件時,若是你不帶 -s
執行 git status
,它將告訴你怎樣取消緩存:
$ git status # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # modified: README # modified: hello.rb #
簡而言之,執行
git reset HEAD
以取消以前git add
添加,但不但願包含在下一提交快照中的緩存。
git rm 將文件從緩存區移除
git rm
會將條目從緩存區中移除。這與 git reset HEAD
將條目取消緩存是有區別的。 「取消緩存」的意思就是將緩存區恢復爲咱們作出修改以前的樣子。 在另外一方面,git rm
則將該文件完全從緩存區踢出,所以它再也不下一個提交快照以內,進而有效地刪除它。
默認狀況下,git rm file
會將文件從緩存區和你的硬盤中(工做目錄)刪除。 若是要在工做目錄中留着該文件,可使用 git rm --cached
git mv git rm –cached orig; mv orig new; git add new
不像絕大多數其餘版本控制系統,Git 並不記錄記錄文件重命名。它反而只記錄快照,並對比快照以找到有啥文件可能被重命名了。 若是一個文件從更新中刪除了,而在下次快照中新添加的另外一個文件的內容與它很類似,Git 就知道這極有多是個重命名。 所以,雖然有 git mv
命令,但它有點多餘 —— 它作得全部事情就是 git rm --cached
, 重命名磁盤上的文件,而後再執行 git add
把新文件添加到緩存區。 你並不須要用它,不過若是以爲這樣容易些,儘管用吧。
我本身並不使用此命令的普通形式 —— 刪除文件。一般直接從硬盤刪除文件,而後執行 git commit -a
會簡單些。 它會自動將刪除的文件從索引中移除。
簡而言之, 執行
git rm
來刪除 Git 追蹤的文件。它還會刪除你的工做目錄中的相應文件。
3、分支與合併
分支是我最喜歡的 Git 特性之一。若是你用過其餘版本控制系統,把你所知的分支給忘記,倒可能更有幫助些 —— 事實上,以咱們使用分支的方式,把 Git 的分支看做 上下文 反而更合適。 當你檢出分支時,你能夠在兩三個不一樣的分支之間來回切換。
簡而言之,你能夠執行
git branch (branchname)
來建立分支, 使用git checkout (branchname)
命令切換到該分支,在該分支的上下文環境中, 提交快照等,以後能夠很容易地來回切換。當你切換分支的時候,Git 會用該分支的最後提交的快照替換你的工做目錄的內容, 因此多個分支不須要多個目錄。使用git merge
來合併分支。你能夠屢次合併到統一分支, 也能夠選擇在合併以後直接刪除被併入的分支。
git branch 列出、建立與管理工做上下文 git checkout 切換到新的分支上下文
git branch
命令是 Git 中的通用分支管理工具,能夠經過它完成多項任務。 咱們先說你會用到的最多的命令 —— 列出分支、建立分支和刪除分支。 咱們還會介紹用來切換分支的 git checkout
命令。
git branch 列出可用的分支
沒有參數時,git branch
會列出你在本地的分支。你所在的分支的行首會有個星號做標記。 若是你開啓了彩色模式,當前分支會用綠色顯示。
$ git branch
* master
此例的意思就是,咱們有一個叫作「master」的分支,而且該分支是當前分支。 當你執行 git init
的時候,缺省狀況下 Git 就會爲你建立「master」分支。 可是這名字一點特殊意味都沒有 —— 事實上你並不非得要一個叫作「master」的分支。 不過因爲它是缺省分支名的緣故,絕大部分項目都有這個分支。
git branch (branchname) 建立新分支
咱們動手建立一個分支,並切換過去。執行 git branch (branchname)
便可。
$ git branch testing $ git branch * master testing
如今咱們能夠看到,有了一個新分支。當你以此方式在上次提交更新以後建立了新分支,若是後來又有更新提交, 而後又切換到了「testing」分支,Git 將還原你的工做目錄到你建立分支時候的樣子 —— 你能夠把它看做一個記錄你當前進度的書籤。讓咱們實際運用看看 —— 咱們用 git checkout (branch)
切換到咱們要修改的分支。
$ ls README hello.rb $ echo 'test content' > test.txt $ echo 'more content' > more.txt $ git add *.txt $ git commit -m 'added two files' [master 8bd6d8b] added two files 2 files changed, 2 insertions(+), 0 deletions(-) create mode 100644 more.txt create mode 100644 test.txt $ ls README hello.rb more.txt test.txt $ git checkout testing Switched to branch 'testing' $ ls README hello.rb
當咱們切換到「測試」分支的時候,咱們添加的新文件被移除了。切換回「master」分支的時候,它們有從新出現了。
$ ls README hello.rb $ git checkout master Switched to branch 'master' $ ls README hello.rb more.txt test.txt
git checkout -b (branchname) 建立新分支,並當即切換到它
一般狀況下,你會更但願當即切換到新分支,從而在該分支中操做,而後當此分支的開發日趨穩定時, 將它合併到穩定版本的分支(例如「master」)中去。 執行 git branch newbranch; git checkout newbranch
也很簡單, 不過 Git 還爲你提供了快捷方式:git checkout -b newbranch
。
$ git branch * master $ ls README hello.rb more.txt test.txt $ git checkout -b removals Switched to a new branch 'removals' $ git rm more.txt rm 'more.txt' $ git rm test.txt rm 'test.txt' $ ls README hello.rb $ git commit -am 'removed useless files' [removals 8f7c949] removed useless files 2 files changed, 0 insertions(+), 2 deletions(-) delete mode 100644 more.txt delete mode 100644 test.txt $ git checkout master Switched to branch 'master' $ ls README hello.rb more.txt test.txt
如你所見,咱們建立了一個分支,在該分支的上下文中移除了一些文件,而後切換回咱們的主分支,那些文件又回來了。 使用分支將工做切分開來,從而讓咱們可以在不一樣上下文中作事,並來回切換。
建立新分支,在其中完成一部分工做,完成以後將它合併到主分支並刪除。你會以爲這很方便,由於這麼作很快很容易。 如此,當你以爲這部分工做並不靠譜,捨棄它很容易。而且,若是你必須回到穩定分支作些事情, 也能夠很方便地這個獨立分支的工做先丟在一邊,完成要事以後再切換回來。
git branch -d (branchname) 刪除分支
假設咱們要刪除一個分支(好比上例中的「testing」分支,該分支沒啥特殊的內容了), 能夠執行 git branch -d (branch)
把它刪掉。
$ git branch * master testing $ git branch -d testing Deleted branch testing (was 78b2670). $ git branch * master
簡而言之 使用
git branch
列出現有的分支、建立新分支以及刪除沒必要要或者已合併的分支。
git merge 將分支合併到你的當前分支
一旦某分支有了獨立內容,你終究會但願將它合併回到你的主分支。 你可使用 git merge
命令將任何分支合併到當前分支中去。 咱們那上例中的「removals」分支爲例。假設咱們建立了一個分支,移除了一些文件,並將它提交到該分支, 其實該分支是與咱們的主分支(也就是「master」)獨立開來的。 要想將這些移除操做包含在主分支中,你能夠將「removals」分支合併回去。
$ git branch * master removals $ ls README hello.rb more.txt test.txt $ git merge removals Updating 8bd6d8b..8f7c949 Fast-forward more.txt | 1 - test.txt | 1 - 2 files changed, 0 insertions(+), 2 deletions(-) delete mode 100644 more.txt delete mode 100644 test.txt $ ls README hello.rb
更多複雜合併
固然,合併並不只僅是簡單的文件添加、移除的操做,Git 也會合並修改 —— 事實上,它很會合並修改。 舉例,咱們看看在某分支中編輯某個文件,而後在另外一個分支中把它的名字改掉再作些修改, 最後將這倆分支合併起來。你以爲會變成一坨 shi?咱們試試看。
$ git branch * master $ cat hello.rb class HelloWorld def self.hello puts "Hello World" end end HelloWorld.hello
首先,咱們建立一個叫作「change_class」的分支,切換過去,從而將重命名類等操做獨立出來。咱們將類名從 「HelloWorld」 改成 「HiWorld」。
$ git checkout -b change_class M hello.rb Switched to a new branch 'change_class' $ vim hello.rb $ head -1 hello.rb class HiWorld $ git commit -am 'changed the class name' [change_class 3467b0a] changed the class name 1 files changed, 2 insertions(+), 4 deletions(-)
而後,將重命名類操做提交到 「change_class」 分支中。 如今,假如切換回 「master」 分支咱們能夠看到類名恢復到了咱們切換到 「change_class」 分支以前的樣子。 如今,再作些修改(即代碼中的輸出),同時將文件名從 hello.rb
改成 ruby.rb
。
$ git checkout master Switched to branch 'master' $ git mv hello.rb ruby.rb $ vim ruby.rb $ git diff diff --git a/ruby.rb b/ruby.rb index 2aabb6e..bf64b17 100644 --- a/ruby.rb +++ b/ruby.rb @@ -1,7 +1,7 @@ class HelloWorld def self.hello - puts "Hello World" + puts "Hello World from Ruby" end end $ git commit -am 'added from ruby' [master b7ae93b] added from ruby 1 files changed, 1 insertions(+), 1 deletions(-) rename hello.rb => ruby.rb (65%)
如今這些改變已經記錄到個人 「master」 分支了。請注意,這裏類名仍是 「HelloWorld」,而不是 「HiWorld」。 而後我想將類名的改變合併過來,我把 「change_class」 分支合併過來就好了。 可是,我已經將文件名都改掉了,Git 知道該怎麼辦麼?
$ git branch change_class * master $ git merge change_class Renaming hello.rb => ruby.rb Auto-merging ruby.rb Merge made by recursive. ruby.rb | 6 ++---- 1 files changed, 2 insertions(+), 4 deletions(-) $ cat ruby.rb class HiWorld def self.hello puts "Hello World from Ruby" end end HiWorld.hello
不錯,它就是發現了。請注意,在這部操做,我沒有遇到合併衝突,而且文件已經重命名、類名也換掉了。挺酷。
合併衝突
那麼,Git 合併頗有魔力,咱們不再用處理合並衝突了,對嗎?不太確切。 不一樣分支中修改了相同區塊的代碼,電腦本身猜不透神馬的狀況下,衝突就擺在咱們面前了。 咱們看看兩個分支中改了同一行代碼的例子。
$ git branch * master $ git checkout -b fix_readme Switched to a new branch 'fix_readme' $ vim README $ git commit -am 'fixed readme title' [fix_readme 3ac015d] fixed readme title 1 files changed, 1 insertions(+), 1 deletions(-)
咱們在某分支中修改了 README 文件中的一行,並提交了。咱們再在 「master」 分支中對同個文件的同一行內容做不一樣的修改。
$ git checkout master Switched to branch 'master' $ vim README $ git commit -am 'fixed readme title differently' [master 3cbb6aa] fixed readme title differently 1 files changed, 1 insertions(+), 1 deletions(-)
有意思的來了 —— 咱們將前一個分支合併到 「master」 分支,一個合併衝突就出現了。
$ git merge fix_readme Auto-merging README CONFLICT (content): Merge conflict in README Automatic merge failed; fix conflicts and then commit the result. $ cat README <<<<<<< HEAD Many Hello World Examples ======= Hello World Lang Examples >>>>>>> fix_readme This project has examples of hello world in nearly every programming language.
你能夠看到,Git 在產生合併衝突的地方插入了標準的與 Subversion 很像的合併衝突標記。 輪到咱們去解決這些衝突了。在這裏咱們就手動把它解決。若是你要 Git 打開一個圖形化的合併工具, 能夠看看 git 合併工具 (好比 kdiff三、emerge、p4merge 等)。
$ vim README here I'm fixing the conflict
$ git diff
diff --cc README
index 9103e27,69cad1a..0000000
--- a/README
+++ b/README
@@@ -1,4 -1,4 +1,4 @@@
- Many Hello World Examples
-Hello World Lang Examples
++Many Hello World Lang Examples
This project has examples of hello world in
在 Git 中,處理合並衝突的時候有個很酷的提示。 若是你執行 git diff
,就像我演示的這樣,它會告訴你衝突的兩方,和你是如何解決的。 如今是時候把它標記爲已解決了。在 Git 中,咱們能夠用 git add
—— 要告訴 Git 文件衝突已經解決,你必須把它寫入緩存區。
$ git status -s UU README $ git add README $ git status -s M README $ git commit [master 8d585ea] Merge branch 'fix_readme'
如今咱們成功解決了合併中的衝突,並提交告終果
簡而言之 使用
git merge
將另外一個分支併入當前的分支中去。 Git 會自動以最佳方式將兩個不一樣快照中獨特的工做合併到一個新快照中去。
git log 顯示一個分支中提交的更改記錄
到目前爲止,咱們已經提交快照到項目中,在不一樣的各自分離的上下文中切換, 但假如咱們忘了本身是如何到目前這一步的那該怎麼辦?或者假如咱們想知道此分支與彼分支到底有啥區別? Git 提供了一個告訴你使你達成當前快照的全部提交消息的工具,叫作 git log
。
要理解日誌(log)命令,你須要瞭解當執行 git commit
以存儲一個快照的時候,都有啥信息被保存了。 除了文件詳單、提交消息和提交者的信息,Git 還保存了你的這次提交所基於的快照。 也就是,假如你克隆了一個項目,你是在什麼快照的基礎上作的修改而獲得新保存的快照的? 這有益於爲項目進程提供上下文,使 Git 可以弄明白誰作了什麼改動。 若是 Git 有你的快照所基於的快照的話,它就能自動判斷你都改變了什麼。而新提交所基於的提交,被稱做新提交的「父親」。
某分支的按時間排序的「父親」列表,當你在該分支時,能夠執行 git log
以查看。 例如,若是咱們在本章中操做的 Hello World 項目中執行 git log
,咱們能夠看到已提交的消息。
$ git log commit 8d585ea6faf99facd39b55d6f6a3b3f481ad0d3d Merge: 3cbb6aa 3ac015d Author: Scott Chacon <schacon@gmail.com> Date: Fri Jun 4 12:59:47 2010 +0200 Merge branch 'fix_readme' Conflicts: README commit 3cbb6aae5c0cbd711c098e113ae436801371c95e Author: Scott Chacon <schacon@gmail.com> Date: Fri Jun 4 12:58:53 2010 +0200 fixed readme title differently commit 3ac015da8ade34d4c7ebeffa2053fcac33fb495b Author: Scott Chacon <schacon@gmail.com> Date: Fri Jun 4 12:58:36 2010 +0200 fixed readme title commit 558151a95567ba4181bab5746bc8f34bd87143d6 Merge: b7ae93b 3467b0a Author: Scott Chacon <schacon@gmail.com> Date: Fri Jun 4 12:37:05 2010 +0200 Merge branch 'change_class' ...
咱們能夠用 --oneline
選項來查看歷史記錄的緊湊簡潔的版本。
$ git log --oneline 8d585ea Merge branch 'fix_readme' 3cbb6aa fixed readme title differently 3ac015d fixed readme title 558151a Merge branch 'change_class' b7ae93b added from ruby 3467b0a changed the class name 17f4acf first commit
這告訴咱們的是,此項目的開發歷史。若是提交消息描述性很好,這就能爲咱們提供關於有啥改動被應用、或者影響了當前快照的狀態、以及這快照裏頭都有啥。
咱們還能夠用它的十分有幫助的 --graph
選項,查看歷史中何時出現了分支、合併。如下爲相同的命令,開啓了拓撲圖選項:
$ git log --oneline --graph * 8d585ea Merge branch 'fix_readme' |\ | * 3ac015d fixed readme title * | 3cbb6aa fixed readme title differently |/ * 558151a Merge branch 'change_class' |\ | * 3467b0a changed the class name * | b7ae93b added from ruby |/ * 17f4acf first commit
如今咱們能夠更清楚明瞭地看到什麼時候工做分叉、又什麼時候歸併。 這對查看發生了什麼、應用了什麼改變頗有幫助,而且極大地幫助你管理你的分支。 讓咱們建立一個分支,在裏頭作些事情,而後切回到主分支,也作點事情,而後看看 log
命令是如何幫助咱們理清這倆分支上都發生了啥的。
首先咱們建立一個分支,來添加 Erlang 編程語言的 Hello World 示例 —— 咱們想要在一個分支裏頭作這個,以免讓可能還不能工做的代碼弄亂咱們的穩定分支。 這樣就能夠切來切去,片葉不沾身。
$ git checkout -b erlang Switched to a new branch 'erlang' $ vim erlang_hw.erl $ git add erlang_hw.erl $ git commit -m 'added erlang' [erlang ab5ab4c] added erlang 1 files changed, 5 insertions(+), 0 deletions(-) create mode 100644 erlang_hw.erl
因爲咱們玩函數式編程很開心,以致於沉迷其中,又在「erlang」分支中添加了一個 Haskell 的示例程序。
$ vim haskell.hs $ git add haskell.hs $ git commit -m 'added haskell' [erlang 1834130] added haskell 1 files changed, 4 insertions(+), 0 deletions(-) create mode 100644 haskell.hs
最後,咱們決定仍是把 Ruby 程序的類名改回原先的樣子。與其建立另外一個分支,咱們能夠返回主分支,改變它,而後直接提交。
$ git checkout master Switched to branch 'master' $ ls README ruby.rb $ vim ruby.rb $ git commit -am 'reverted to old class name' [master 594f90b] reverted to old class name 1 files changed, 2 insertions(+), 2 deletions(-)
如今假設咱們有段時間不作這個項目了,咱們作別的去了。 當咱們回來的時候,咱們想知道「erlang」分支都是啥,而主分支的進度又是怎樣。 僅僅看分支的名字,咱們是無從知道本身還在裏面有 Haskell 的改動的,可是用 git log
咱們就能夠。 若是你在命令行中提供一個分支名字,它就會顯示該分支歷史中「可及」的提交,即從該分支創立起可追溯的影響了最終的快照的提交。
$ git log --oneline erlang 1834130 added haskell ab5ab4c added erlang 8d585ea Merge branch 'fix_readme' 3cbb6aa fixed readme title differently 3ac015d fixed readme title 558151a Merge branch 'change_class' b7ae93b added from ruby 3467b0a changed the class name 17f4acf first commit
如此,咱們很容易就看到分支裏頭還包括了 Haskell 代碼(高亮顯示了)。 更酷的是,咱們很容易地告訴 Git,咱們只對某個分支中可及的提交感興趣。換句話說,某分支中與其餘分支相比惟一的提交。
在此例中,若是咱們想要合併「erlang」分支,咱們須要看當合並的時候,都有啥提交會做用到咱們的快照上去。 咱們告訴 Git 的方式是,在不想要看到的分支前放一個 ^
。 例如,若是咱們想要看「erlang」分支中但不在主分支中的提交,咱們能夠用 erlang ^master
,或者反之。
$ git log --oneline erlang ^master 1834130 added haskell ab5ab4c added erlang $ git log --oneline master ^erlang 594f90b reverted to old class name
這爲咱們提供了一個良好的、簡易的分支管理工具。它使咱們可以很是容易地查看對某個分支惟一的提交,從而知道咱們缺乏什麼,以及當咱們要合併時,會有什麼被合併進去。
簡而言之 使用
git log
列出促成當前分支目前的快照的提交歷史記錄。這使你可以看到項目是如何到達如今的情況的。
git tag 給歷史記錄中的某個重要的一點打上標籤
若是你達到一個重要的階段,並但願永遠記住那個特別的提交快照,你可使用 git tag
給它打上標籤。 該 tag
命令基本上會給該特殊提交打上永久的書籤,從而使你在未來可以用它與其餘提交比較。 一般,你會在切取一個發佈版本或者交付一些東西的時候打個標籤。
好比說,咱們想爲咱們的 Hello World 項目發佈一個「1.0」版本。 咱們能夠用 git tag -a v1.0
命令給最新一次提交打上(HEAD
)「v1.0」的標籤。 -a
選項意爲「建立一個帶註解的標籤」,從而使你爲標籤添加註解。絕大部分時候都會這麼作的。 不用 -a
選項也能夠執行的,但它不會記錄這標籤是啥時候打的,誰打的,也不會讓你添加個標籤的註解。 我推薦一直建立帶註解的標籤。
$ git tag -a v1.0
當你執行 git tag -a
命令時,Git 會打開你的編輯器,讓你寫一句標籤註解,就像你給提交寫註解同樣。
如今,注意當咱們執行 git log --decorate
時,咱們能夠看到咱們的標籤了:
$ git log --oneline --decorate --graph * 594f90b (HEAD, tag: v1.0, master) reverted to old class name * 8d585ea Merge branch 'fix_readme' |\ | * 3ac015d (fix_readme) fixed readme title * | 3cbb6aa fixed readme title differently |/ * 558151a Merge branch 'change_class' |\ | * 3467b0a changed the class name * | b7ae93b added from ruby |/ * 17f4acf first commit
若是咱們有新提交,該標籤依然會待在該提交的邊上,因此咱們已經給那個特定快照永久打上標籤,而且可以將它與將來的快照作比較。
不過咱們並不須要給當前提交打標籤。若是咱們忘了給某個提交打標籤,又將它發佈了,咱們能夠給它追加標籤。 在相同的命令末尾加上提交的 SHA,執行,就能夠了。 例如,假設咱們發佈了提交 558151a
(幾個提交以前的事情了),可是那時候忘了給它打標籤。 咱們如今也能夠:
$ git tag -a v0.9 558151a $ git log --oneline --decorate --graph * 594f90b (HEAD, tag: v1.0, master) reverted to old class name * 8d585ea Merge branch 'fix_readme' |\ | * 3ac015d (fix_readme) fixed readme title * | 3cbb6aa fixed readme title differently |/ * 558151a (tag: v0.9) Merge branch 'change_class' |\ | * 3467b0a changed the class name * | b7ae93b added from ruby |/ * 17f4acf first commit
4、分享與更新項目
Git 並不像 Subversion 那樣有個中心服務器。 目前爲止全部的命令都是本地執行的,更新的知識本地的數據庫。 要經過 Git 與其餘開發者合做,你須要將數據放到一臺其餘開發者可以鏈接的服務器上。 Git 實現此流程的方式是將你的數據與另外一個倉庫同步。在服務器與客戶端之間並無實質的區別 —— Git 倉庫就是 Git 倉庫,你能夠很容易地在二者之間同步。
一旦你有了個 Git 倉庫,無論它是在你本身的服務器上,或者是由 GitHub 之類的地方提供, 你均可以告訴 Git 推送你擁有的遠端倉庫尚未的數據,或者叫 Git 從別的倉庫把差異取過來。
聯網的時候你能夠隨時作這個,它並不須要對應一個 commit
或者別的什麼。 通常你會本地提交幾回,而後從你的項目克隆自的線上的共享倉庫提取數據以保持最新,將新完成的合併到你完成的工做中去,而後推送你的改動會服務器。
簡而言之 使用
git fetch
更新你的項目,使用git push
分享你的改動。 你能夠用git remote
管理你的遠程倉庫。
git remote 羅列、添加和刪除遠端倉庫別名
不像中心化的版本控制系統(客戶端與服務端很不同),Git 倉庫基本上都是一致的,而且並能夠同步他們。 這使得擁有多個遠端倉庫變得容易 —— 你能夠擁有一些只讀的倉庫,另外的一些也可寫的倉庫。
當你須要與遠端倉庫同步的時候,不須要使用它詳細的連接。Git 儲存了你感興趣的遠端倉庫的連接的別名或者暱稱。 你可使用 git remote
命令管理這個遠端倉庫列表。
git remote 列出遠端別名
若是沒有任何參數,Git 會列出它存儲的遠端倉庫別名了事。默認狀況下,若是你的項目是克隆的(與本地建立一個新的相反), Git 會自動將你的項目克隆自的倉庫添加到列表中,並取名「origin」。 若是你執行時加上 -v
參數,你還能夠看到每一個別名的實際連接地址。
$ git remote origin $ git remote -v origin git@github.com:github/git-reference.git (fetch) origin git@github.com:github/git-reference.git (push)
在此你看到了該連接兩次,是由於 Git 容許你爲每一個遠端倉庫添加不一樣的推送與獲取的連接,以備你讀寫時但願使用不一樣的協議。
git remote add 爲你的項目添加一個新的遠端倉庫
若是你但願分享一個本地建立的倉庫,或者你想要獲取別人的倉庫中的貢獻 —— 若是你想要以任何方式與一個新倉庫溝通,最簡單的方式一般就是把它添加爲一個遠端倉庫。 執行 git remote add [alias] [url]
就能夠。 此命令將 [url]
以 [alias]
的別名添加爲本地的遠端倉庫。
例如,假設咱們想要與整個世界分享咱們的 Hello World 程序。 咱們能夠在一臺服務器上建立一個新倉庫(我以 GitHub 爲例子)。 它應該會給你一個連接,在這裏就是「git@github.com:schacon/hw.git」。 要把它添加到咱們的項目以便咱們推送以及獲取更新,咱們能夠這樣:
$ git remote $ git remote add github git@github.com:schacon/hw.git $ git remote -v github git@github.com:schacon/hw.git (fetch) github git@github.com:schacon/hw.git (push)
像分支的命名同樣,遠端倉庫的別名是強制的 —— 就像「master」,沒有特別意義,但它廣爲使用, 由於 git init
默認用它;「origin」常常被用做遠端倉庫別名,就由於 git clone
默認用它做爲克隆自的連接的別名。此例中,我決定給個人遠端倉庫取名「github」,但我叫它隨便什麼均可以。
git remote rm 刪除現存的某個別名
Git addeth and Git taketh away. 若是你須要刪除一個遠端 —— 再也不須要它了、項目已經沒了,等等 —— 你可使用 git remote rm [alias]
把它刪掉。
$ git remote -v github git@github.com:schacon/hw.git (fetch) github git@github.com:schacon/hw.git (push) $ git remote add origin git://github.com/pjhyett/hw.git $ git remote -v github git@github.com:schacon/hw.git (fetch) github git@github.com:schacon/hw.git (push) origin git://github.com/pjhyett/hw.git (fetch) origin git://github.com/pjhyett/hw.git (push) $ git remote rm origin $ git remote -v github git@github.com:schacon/hw.git (fetch) github git@github.com:schacon/hw.git (push)
簡而言之 你能夠用
git remote
列出你的遠端倉庫和那些倉庫的連接。 你可使用git remote add
添加新的遠端倉庫,用git remote rm
刪掉已存在的那些。
git fetch 從遠端倉庫下載新分支與數據 git pull 從遠端倉庫提取數據並嘗試合併到當前分支
Git 有兩個命令用來從某一遠端倉庫更新。 git fetch
會使你與另外一倉庫同步,提取你本地所沒有的數據,爲你在同步時的該遠端的每一分支提供書籤。 這些分支被叫作「遠端分支」,除了 Git 不容許你檢出(切換到該分支)以外,跟本地分支沒區別 —— 你能夠將它們合併到當前分支,與其餘分支做比較差別,查看那些分支的歷史日誌,等等。同步以後你就能夠在本地操做這些。
第二個會從遠端服務器提取新數據的命令是 git pull
。 基本上,該命令就是在 git fetch
以後緊接着 git merge
遠端分支到你所在的任意分支。 我我的不太喜歡這命令 —— 我更喜歡fetch
和 merge
分開來作。少點魔法,少點問題。 不過,若是你喜歡這主意,你能夠看一下 git pull
的 官方文檔。
假設你配置好了一個遠端,而且你想要提取更新,你能夠首先執行 git fetch [alias]
告訴 Git 去獲取它有你沒有的數據,而後你能夠執行 git merge [alias]/[branch]
以將服務器上的任何更新(假設有人這時候推送到服務器了)合併到你的當前分支。 那麼,若是我是與兩三個其餘人合做 Hello World 項目,而且想要將我最近鏈接以後的全部改動拿過來,我能夠這麼作:
$ git fetch github remote: Counting objects: 4006, done. remote: Compressing objects: 100% (1322/1322), done. remote: Total 2783 (delta 1526), reused 2587 (delta 1387) Receiving objects: 100% (2783/2783), 1.23 MiB | 10 KiB/s, done. Resolving deltas: 100% (1526/1526), completed with 387 local objects. From github.com:schacon/hw 8e29b09..c7c5a10 master -> github/master 0709fdc..d4ccf73 c-langs -> github/c-langs 6684f82..ae06d2b java -> github/java * [new branch] ada -> github/ada * [new branch] lisp -> github/lisp
能夠看到自從上一次與遠端倉庫同步之後,又新贈或更新了五個分支。 「ada」與「lisp」分支是新的,而「master」、「clang」與「java」分支則被更新了。 在此例中,個人團隊在合併入主分支以前,將提議的更新推送到遠端分支以審覈。
你能夠看到 Git 作的映射。遠端倉庫的主分支成爲了本地的一個叫作「github/master」的分支。 這樣我就能夠執行 git merge github/master
將遠端的主分支和併入個人本地主分支。 或者,我能夠 git log github/master ^master
看看該分支上的新提交。 若是你的遠端倉庫叫作「origin」,那遠端主分支就會叫作 origin/master
。幾乎全部能在本地分支上執行的命令均可以在遠端分支上用。
若是你有多個遠端倉庫,你能夠執行 git fetch [alias]
提取特定的遠端倉庫, 或者執行 git fetch --all
告訴 Git 同步全部的遠端倉庫。
簡而言之 執行
git fetch [alias]
來將你的倉庫與遠端倉庫同步,提取全部它獨有的數據到本地分支以合併或者怎樣。
git push 推送你的新分支與數據到某個遠端倉庫
想要與他人分享你牛鼻的提交,你須要將改動推送到遠端倉庫。 執行 git push [alias] [branch]
,就會將你的 [branch] 分支推送成爲 [alias] 遠端上的 [branch] 分支。 讓咱們試試推送咱們的主分支到先前添加的「github」遠端倉庫上去。
$ git push github master Counting objects: 25, done. Delta compression using up to 2 threads. Compressing objects: 100% (25/25), done. Writing objects: 100% (25/25), 2.43 KiB, done. Total 25 (delta 4), reused 0 (delta 0) To git@github.com:schacon/hw.git * [new branch] master -> master
挺簡單。如今若是有人從該倉庫克隆,他會獲得我提交的完徹底全的一份歷史記錄了。
若是有個像以前建立的「erlang」分支那樣的主題分支,想只分享這個,該怎麼辦呢?你能夠相應的只推送該分支。
$ git push github erlang Counting objects: 7, done. Delta compression using up to 2 threads. Compressing objects: 100% (6/6), done. Writing objects: 100% (6/6), 652 bytes, done. Total 6 (delta 1), reused 0 (delta 0) To git@github.com:schacon/hw.git * [new branch] erlang -> erlang
如今當人們從該倉庫克隆時,他們就會獲得一個「erlang」分支以查閱、合併。 用這種方式,你能夠推送任何分支到任何你有寫權限的倉庫。 若是你的分支已經在該倉庫中了,它會試着去更新,若是它再也不,Git 會把它加上。
最後一個當你推送到遠端分支時會碰到的主要問題是,其餘人在此期間也推送了的狀況。 若是你和另外一個開發者同時克隆了,又都有提交,那麼當她推送後你也想推送時,默認狀況下 Git 不會讓你覆蓋她的改動。 相反的,它會在你試圖推送的分支上執行 git log
,肯定它可以在你的推送分支的歷史記錄中看到服務器分支的當前進度。 若是它在在你的歷史記錄中看不到,它就會下結論說你過期了,並打回你的推送。 你須要正式提取、合併,而後再次推送 —— 以肯定你把她的改動也考慮在內了。
當你試圖推送到某個以被更新的遠端分支時,會出現下面這種狀況:
$ git push github master To git@github.com:schacon/hw.git ! [rejected] master -> master (non-fast-forward) error: failed to push some refs to 'git@github.com:schacon/hw.git' To prevent you from losing history, non-fast-forward updates were rejected Merge the remote changes before pushing again. See the 'Note about fast-forwards' section of 'git push --help' for details.
你能夠修正這個問題。執行 git fetch github; git merge github/master
,而後再推送
簡而言之 執行
git push [alias] [branch]
將你的本地改動推送到遠端倉庫。 若是能夠的話,它會依據你的 [branch] 的樣子,推送到遠端的 [branch] 去。 若是在你上次提取、合併以後,另有人推送了,Git 服務器會拒絕你的推送,知道你是最新的爲止。
5、檢查與比較
如今你有了一堆分支,短時間的主題、長期的特性或者其它。怎樣追蹤他們呢? Git 有一組工具,能夠幫助你弄明白工做是在哪兒完成的,兩個分支間的區別是啥,等等。
簡而言之 執行
git log
找到你的項目歷史中的特定提交 —— 按做者、日期、內容或者歷史記錄。執行git diff
比較歷史記錄中的兩個不一樣的點 —— 一般是爲了看看兩個分支有啥區別,或者從某個版本到另外一個版本,你的軟件都有啥變化。
git log 過濾你的提交歷史記錄
經過查看分支中另外一分支看不到的提交記錄,咱們已經看到如何用 git log
來比較分支。 (若是你不記得了,它看起來是這樣的:git log branchA ^branchB
)。 並且,你也能夠用git log
去尋找特定的提交。 在此,咱們會看到一些更廣爲使用的 git log
選項,不過哪有不少。 完整的清單能夠看看官方文檔。
git log –author 只尋找某個特定做者的提交
要過濾你的提交歷史,只尋找某個特定做者的提交,你可使用 --author
選項。 例如,比方說咱們要找 Git 源碼中 Linus 提交的部分。 咱們能夠執行相似 git log --author=Linus
的命令。 這個查找是大小寫敏感的,而且也會檢索電子郵箱地址。 我在此例中使用 -[number]
選項,以限制結果爲最近 [number] 次的提交。
$ git log --author=Linus --oneline -5 81b50f3 Move 'builtin-*' into a 'builtin/' subdirectory 3bb7256 make "index-pack" a built-in 377d027 make "git pack-redundant" a built-in b532581 make "git unpack-file" a built-in 112dd51 make "mktag" a built-in
git log –since –before 根據日期過濾提交記錄
若是你要指定一個你感興趣的日期範圍以過濾你的提交,能夠執行幾個選項 —— 我用 --since
和 --before
,可是你也能夠用 --until
和 --after
。 例如,若是我要看 Git 項目中三週前且在四月十八日以後的全部提交,我能夠執行這個(我還用了 --no-merges
選項以隱藏合併提交):
$ git log --oneline --before={3.weeks.ago} --after={2010-04-18} --no-merges 5469e2d Git 1.7.1-rc2 d43427d Documentation/remote-helpers: Fix typos and improve language 272a36b Fixup: Second argument may be any arbitrary string b6c8d2d Documentation/remote-helpers: Add invocation section 5ce4f4e Documentation/urls: Rewrite to accomodate transport::address 00b84e9 Documentation/remote-helpers: Rewrite description 03aa87e Documentation: Describe other situations where -z affects git diff 77bc694 rebase-interactive: silence warning when no commits rewritten 636db2c t3301: add tests to use --format="%N"
git log –grep 根據提交註釋過濾提交記錄
你或許還想根據提交註釋中的某個特定短語查找提交記錄。能夠用 --grep
選項。 好比說我知道有個提交是有關使用 P4EDITOR 環境變量,又想回憶起那個改動是啥樣子的 —— 我能夠用--grep
選項找到該提交。
$ git log --grep=P4EDITOR --no-merges commit 82cea9ffb1c4677155e3e2996d76542502611370 Author: Shawn Bohrer Date: Wed Mar 12 19:03:24 2008 -0500 git-p4: Use P4EDITOR environment variable when set Perforce allows you to set the P4EDITOR environment variable to your preferred editor for use in perforce. Since we are displaying a perforce changelog to the user we should use it when it is defined. Signed-off-by: Shawn Bohrer <shawn.bohrer@gmail.com> Signed-off-by: Simon Hausmann <simon@lst.de>
Git 會對全部的 --grep
和 --author
參數做邏輯或。 若是你用 --grep
和 --author
時,想看的是某人寫做的而且有某個特殊的註釋內容的提交記錄, 你須要加上 --all-match
選項。 在這些例子中,我會用上 --format
選項,這樣咱們就能夠看到每一個提交的做者是誰了。
若是我查找註釋內容含有 「p4 depo」的提交,我獲得了三個提交:
$ git log --grep="p4 depo" --format="%h %an %s" ee4fd1a Junio C Hamano Merge branch 'master' of git://repo.or.cz/git/fastimport da4a660 Benjamin Sergeant git-p4 fails when cloning a p4 depo. 1cd5738 Simon Hausmann Make incremental imports easier to use by storing the p4 d
若是我加上 --author=Hausmann
參數,與進一步過濾上述結果到 Simon 的惟一提交相反, 它會告訴我全部 Simon 的提交,或者註釋中有「p4 demo」的提交:
$ git log --grep="p4 depo" --format="%h %an %s" --author="Hausmann" cdc7e38 Simon Hausmann Make it possible to abort the submission of a change to Pe f5f7e4a Simon Hausmann Clean up the git-p4 documentation 30b5940 Simon Hausmann git-p4: Fix import of changesets with file deletions 4c750c0 Simon Hausmann git-p4: git-p4 submit cleanups. 0e36f2d Simon Hausmann git-p4: Removed git-p4 submit --direct. edae1e2 Simon Hausmann git-p4: Clean up git-p4 submit's log message handling. 4b61b5c Simon Hausmann git-p4: Remove --log-substitutions feature. 36ee4ee Simon Hausmann git-p4: Ensure the working directory and the index are cle e96e400 Simon Hausmann git-p4: Fix submit user-interface. 38f9f5e Simon Hausmann git-p4: Fix direct import from perforce after fetching cha 2094714 Simon Hausmann git-p4: When skipping a patch as part of "git-p4 submit" m 1ca3d71 Simon Hausmann git-p4: Added support for automatically importing newly ap ...
不過,若是加上 --all-match
,結果就是我想要的了:
$ git log --grep="p4 depo" --format="%h %an %s" --author="Hausmann" --all-match
1cd5738 Simon Hausmann Make incremental imports easier to use by storing the p4 d
git log -S 依據所引入的差值過濾
若是你寫的提交註釋都極度糟糕怎麼辦?或者,若是你要找某個函數是什麼時候引入的,某些變量是在哪裏開始被使用的? 你能夠告訴 Git 在每一個提交之間的差值中查找特定字符串。 例如,若是咱們想要找出哪一個提交修改出了相似函數名「userformat_find_requirements」, 咱們能夠執行(注意在「-S」與你要找的東東之間沒有「=」):
$ git log -Suserformat_find_requirements commit 5b16360330822527eac1fa84131d185ff784c9fb Author: Johannes Gilger Date: Tue Apr 13 22:31:12 2010 +0200 pretty: Initialize notes if %N is used When using git log --pretty='%N' without an explicit --show-notes, git would segfault. This patches fixes this behaviour by loading the needed notes datastructures if --pretty is used and the format contains %N. When --pretty='%N' is used together with --no-notes, %N won't be expanded. This is an extension to a proposed patch by Jeff King. Signed-off-by: Johannes Gilger Signed-off-by: Junio C Hamano
git log -p 顯示每一個提交引入的補丁
每一個提交都是項目的一個快照。因爲每一個提交都記錄它所基於的快照,Git 可以常常對它們求差值,並以補丁形式向你展現。 這意味着,對任意提交,你均可以獲取該提交給項目引入補丁。 你能夠用 git show [SHA]
加上某個特定的提交 SHA 獲取,或者執行 git log -p
, 它會告訴 Git 輸出每一個提交以後的補丁。這是個總結某一分支或者兩個提交之間都發生了神馬的好途徑。
$ git log -p --no-merges -2 commit 594f90bdee4faf063ad07a4a6f503fdead3ef606 Author: Scott Chacon <schacon@gmail.com> Date: Fri Jun 4 15:46:55 2010 +0200 reverted to old class name diff --git a/ruby.rb b/ruby.rb index bb86f00..192151c 100644 --- a/ruby.rb +++ b/ruby.rb @@ -1,7 +1,7 @@ -class HiWorld +class HelloWorld def self.hello puts "Hello World from Ruby" end end -HiWorld.hello +HelloWorld.hello commit 3cbb6aae5c0cbd711c098e113ae436801371c95e Author: Scott Chacon <schacon@gmail.com> Date: Fri Jun 4 12:58:53 2010 +0200 fixed readme title differently diff --git a/README b/README index d053cc8..9103e27 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -Hello World Examples +Many Hello World Examples ====================== This project has examples of hello world in
這是個總結改動,以及合併或發佈以前重審一系列提交的好方式。
git log –stat 顯示每一個提交引入的改動的差值統計
若是 -p
選項對你來講太詳細了,你能夠用 --stat
總結這些改動。 這是不用 -p
,而用 --stat
選項時,同一份日誌的輸出。
$ git log --stat --no-merges -2 commit 594f90bdee4faf063ad07a4a6f503fdead3ef606 Author: Scott Chacon <schacon@gmail.com> Date: Fri Jun 4 15:46:55 2010 +0200 reverted to old class name ruby.rb | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) commit 3cbb6aae5c0cbd711c098e113ae436801371c95e Author: Scott Chacon <schacon@gmail.com> Date: Fri Jun 4 12:58:53 2010 +0200 fixed readme title differently README | 2 +- 1 files changed, 1 insertions(+), 1 deletions(-)
一樣的基本信息,但更緊湊 —— 它仍然讓你看到相對改動,和改動了哪些文件。
git diff
最後,要查看兩個提交快照的絕對改動,你能夠用 git diff
命令。 這在兩個主要狀況中廣爲使用 —— 查看兩個分支彼此之間的差值,和查看自發布或者某個舊歷史點以後都有啥變了。讓咱們看看這倆狀況。
你僅需執行 git diff [version]
(或者你給該發佈打的任何標籤)就能夠查看自最近發佈以後的改動。 例如,若是咱們想要看看自 v0.9 發佈以後咱們的項目改變了啥,咱們能夠執行 git diff v0.9
$ git diff v0.9 diff --git a/README b/README index d053cc8..d4173d5 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -Hello World Examples +Many Hello World Lang Examples ====================== This project has examples of hello world in diff --git a/ruby.rb b/ruby.rb index bb86f00..192151c 100644 --- a/ruby.rb +++ b/ruby.rb @@ -1,7 +1,7 @@ -class HiWorld +class HelloWorld def self.hello puts "Hello World from Ruby" end end -HiWorld.hello +HelloWorld.hello
正如 git log
,你能夠給它加上 --stat
參數。
$ git diff v0.9 --stat README | 2 +- ruby.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-)
要比較兩個不一樣的分支,你能夠執行相似 git diff branchA branchB
的命令。 不過它的問題在於它會完徹底全按你說的做 —— 它會直接給你個補丁文件,該補丁可以將甲分支的最新快照變成乙分支的最新快照的樣子。 這意味着若是兩個分支已經產生分歧 —— 奔往兩個不一樣方向了 —— 它會移除甲分支中引入的全部工做,而後累加乙分支中的全部工做。 這大概不是你要的吧 —— 你想要不在甲分支中的乙分支的改動。因此你真的須要的是兩個分支叉開去時,和最新的乙分支的差異。 因此,若是咱們的歷史記錄看起來像這樣:
$ git log --graph --oneline --decorate --all * 594f90b (HEAD, tag: v1.0, master) reverted to old class name | * 1834130 (erlang) added haskell | * ab5ab4c added erlang |/ * 8d585ea Merge branch 'fix_readme' ...
而且,咱們想要看「erlang」分支與主分支相比的查別。執行 git diff master erlang
會給咱們錯誤的結果。
$ git diff --stat master erlang erlang_hw.erl | 5 +++++ haskell.hs | 4 ++++ ruby.rb | 4 ++-- 3 files changed, 11 insertions(+), 2 deletions(-)
你能夠看到,它加上了 erlang 和 haskell 文件,這確實是咱們在該分支中作的, 可是它同時恢復了咱們在主分支中改動的 ruby 文件。咱們真心想要的只是「erlang」分支中的改動(添加兩個文件)。 咱們能夠經過求兩個分支分歧時的共同提交與該分支的差值獲得想要的結果:
$ git diff --stat 8d585ea erlang erlang_hw.erl | 5 +++++ haskell.hs | 4 ++++ 2 files changed, 9 insertions(+), 0 deletions(-)
這纔是咱們在找的,可是咱們可不想要每次都要找出兩個分支分歧時的那次提交。 幸運的是,Git 爲此提供了一個快捷方式。 若是你執行 git diff master...erlang
(在分支名之間有三個半角的點), Git 就會自動找出兩個分支的共同提交(也被成爲合併基礎),並求差值。
$ git diff --stat master erlang erlang_hw.erl | 5 +++++ haskell.hs | 4 ++++ ruby.rb | 4 ++-- 3 files changed, 11 insertions(+), 2 deletions(-) $ git diff --stat master...erlang erlang_hw.erl | 5 +++++ haskell.hs | 4 ++++ 2 files changed, 9 insertions(+), 0 deletions(-)
幾乎每一次你要對比兩個分支的時候,你都會想用三個點的語法,由於它一般會給你你想要的。
順帶提一句,你還可讓 Git 手工計算兩次提交的合併基礎(第一個共同的祖提交),即 git merge-base
命令:
$ git merge-base master erlang
8d585ea6faf99facd39b55d6f6a3b3f481ad0d3d
因此你執行下面這個也跟 git diff master...erlang
同樣:
$ git diff --stat $(git merge-base master erlang) erlang erlang_hw.erl | 5 +++++ haskell.hs | 4 ++++ 2 files changed, 9 insertions(+), 0 deletions(-)
固然,我會推薦簡單點的那個。
簡而言之 使用
git diff
查看某一分支自它偏離出來起與過去某一點之間項目的改動。 老是使用git diff branchA...branchB
來查看 branchB 與 branchA 的相對差值,這會讓事情簡單點。
最後,羅列一下git的經常使用命令供你們學習和概括。
· 新建代碼庫
# 在當前目錄新建一個Git代碼庫 $ git init # 新建一個目錄,將其初始化爲Git代碼庫 $ git init [project-name] # 下載一個項目和它的整個代碼歷史 $ git clone [url]
· 配置
Git的設置文件爲.gitconfig
,它能夠在用戶主目錄下(全局配置),也能夠在項目目錄下(項目配置)。
# 顯示當前的Git配置 $ git config --list # 編輯Git配置文件 $ git config -e [--global] # 設置提交代碼時的用戶信息 $ git config [--global] user.name "[name]" $ git config [--global] user.email "[email address]"
· 增長/刪除文件
# 添加指定文件到暫存區 $ git add [file1] [file2] ... # 添加指定目錄到暫存區,包括子目錄 $ git add [dir] # 添加當前目錄的全部文件到暫存區 $ git add . # 添加每一個變化前,都會要求確認 # 對於同一個文件的多處變化,能夠實現分次提交 $ git add -p # 刪除工做區文件,而且將此次刪除放入暫存區 $ git rm [file1] [file2] ... # 中止追蹤指定文件,但該文件會保留在工做區 $ git rm --cached [file] # 更名文件,而且將這個更名放入暫存區 $ git mv [file-original] [file-renamed]
· 代碼提交
# 提交暫存區到倉庫區 $ git commit -m [message] # 提交暫存區的指定文件到倉庫區 $ git commit [file1] [file2] ... -m [message] # 提交工做區自上次commit以後的變化,直接到倉庫區 $ git commit -a # 提交時顯示全部diff信息 $ git commit -v # 使用一次新的commit,替代上一次提交 # 若是代碼沒有任何新變化,則用來改寫上一次commit的提交信息 $ git commit --amend -m [message] # 重作上一次commit,幷包括指定文件的新變化 $ git commit --amend [file1] [file2] ...
· 分支
# 列出全部本地分支 $ git branch # 列出全部遠程分支 $ git branch -r # 列出全部本地分支和遠程分支 $ git branch -a # 新建一個分支,但依然停留在當前分支 $ git branch [branch-name] # 新建一個分支,並切換到該分支 $ git checkout -b [branch] # 新建一個分支,指向指定commit $ git branch [branch] [commit] # 新建一個分支,與指定的遠程分支創建追蹤關係 $ git branch --track [branch] [remote-branch] # 切換到指定分支,並更新工做區 $ git checkout [branch-name] # 切換到上一個分支 $ git checkout - # 創建追蹤關係,在現有分支與指定的遠程分支之間 $ git branch --set-upstream [branch] [remote-branch] # 合併指定分支到當前分支 $ git merge [branch] # 選擇一個commit,合併進當前分支 $ git cherry-pick [commit] # 刪除分支 $ git branch -d [branch-name] # 刪除遠程分支 $ git push origin --delete [branch-name] $ git branch -dr [remote/branch]
· 標籤
# 列出全部tag $ git tag # 新建一個tag在當前commit $ git tag [tag] # 新建一個tag在指定commit $ git tag [tag] [commit] # 刪除本地tag $ git tag -d [tag] # 刪除遠程tag $ git push origin :refs/tags/[tagName] # 查看tag信息 $ git show [tag] # 提交指定tag $ git push [remote] [tag] # 提交全部tag $ git push [remote] --tags # 新建一個分支,指向某個tag $ git checkout -b [branch] [tag]
· 查看信息
# 顯示有變動的文件 $ git status # 顯示當前分支的版本歷史 $ git log # 顯示commit歷史,以及每次commit發生變動的文件 $ git log --stat # 搜索提交歷史,根據關鍵詞 $ git log -S [keyword] # 顯示某個commit以後的全部變更,每一個commit佔據一行 $ git log [tag] HEAD --pretty=format:%s # 顯示某個commit以後的全部變更,其"提交說明"必須符合搜索條件 $ git log [tag] HEAD --grep feature # 顯示某個文件的版本歷史,包括文件更名 $ git log --follow [file] $ git whatchanged [file] # 顯示指定文件相關的每一次diff $ git log -p [file] # 顯示過去5次提交 $ git log -5 --pretty --oneline # 顯示全部提交過的用戶,按提交次數排序 $ git shortlog -sn # 顯示指定文件是什麼人在什麼時間修改過 $ git blame [file] # 顯示暫存區和工做區的差別 $ git diff # 顯示暫存區和上一個commit的差別 $ git diff --cached [file] # 顯示工做區與當前分支最新commit之間的差別 $ git diff HEAD # 顯示兩次提交之間的差別 $ git diff [first-branch]...[second-branch] # 顯示今天你寫了多少行代碼 $ git diff --shortstat "@{0 day ago}" # 顯示某次提交的元數據和內容變化 $ git show [commit] # 顯示某次提交發生變化的文件 $ git show --name-only [commit] # 顯示某次提交時,某個文件的內容 $ git show [commit]:[filename] # 顯示當前分支的最近幾回提交 $ git reflog
· 遠程同步
# 下載遠程倉庫的全部變更 $ git fetch [remote] # 顯示全部遠程倉庫 $ git remote -v # 顯示某個遠程倉庫的信息 $ git remote show [remote] # 增長一個新的遠程倉庫,並命名 $ git remote add [shortname] [url] # 取回遠程倉庫的變化,並與本地分支合併 $ git pull [remote] [branch] # 上傳本地指定分支到遠程倉庫 $ git push [remote] [branch] # 強行推送當前分支到遠程倉庫,即便有衝突 $ git push [remote] --force # 推送全部分支到遠程倉庫 $ git push [remote] --all
· 撤銷
# 恢復暫存區的指定文件到工做區 $ git checkout [file] # 恢復某個commit的指定文件到暫存區和工做區 $ git checkout [commit] [file] # 恢復暫存區的全部文件到工做區 $ git checkout . # 重置暫存區的指定文件,與上一次commit保持一致,但工做區不變 $ git reset [file] # 重置暫存區與工做區,與上一次commit保持一致 $ git reset --hard # 重置當前分支的指針爲指定commit,同時重置暫存區,但工做區不變 $ git reset [commit] # 重置當前分支的HEAD爲指定commit,同時重置暫存區和工做區,與指定commit一致 $ git reset --hard [commit] # 重置當前HEAD爲指定commit,但保持暫存區和工做區不變 $ git reset --keep [commit] # 新建一個commit,用來撤銷指定commit # 後者的全部變化都將被前者抵消,而且應用到當前分支 $ git revert [commit] # 暫時將未提交的變化移除,稍後再移入 $ git stash $ git stash pop
· 其餘
# 生成一個可供發佈的壓縮包
$ git archive
參考文章:
1. Git版本控制軟件結合GitHub從入門到精一般用命令學習手冊 http://www.ihref.com/read-16369.html
2. 經常使用 Git 命令清單 http://www.ruanyifeng.com/blog/2015/12/git-cheat-sheet.html