如果你想獲得一份已經存在了的 Git 倉庫的拷貝,比如說,你想爲某個開源項目貢獻自己的一份力,這時就要用到 `git clone` 命令。 如果你對其它的 VCS 系統(比如說Subversion)很熟悉,請留心一下你所使用的命令是"clone"而不是"checkout"。 這是 Git 區別於其它版本控制系統的一個重要特性,Git 克隆的是該 Git 倉庫服務器上的幾乎所有數據,而不是僅僅複製完成你的工作所需要文件。 當你執行 `git clone` 命令的時候,默認配置下遠程 Git 倉庫中的每一個文件的每一個版本都將被拉取下來。 事實上,如果你的服務器的磁盤壞掉了,你通常可以使用任何一個克隆下來的用戶端來重建服務器上的倉庫(雖然可能會丟失某些服務器端的掛鉤設置,但是所有版本的數據仍在.
克隆倉庫的命令格式是 git clone [url]
。 比如,要克隆 Git 的可鏈接庫 libgit2,可以用下面的命令:
$ git clone https://github.com/libgit2/libgit2
這會在當前目錄下創建一個名爲 “libgit2” 的目錄,並在這個目錄下初始化一個 .git
文件夾,從遠程倉庫拉取下所有數據放入 .git
文件夾,然後從中讀取最新版本的文件的拷貝。 如果你進入到這個新建的 libgit2
文件夾,你會發現所有的項目文件已經在裏面了,準備就緒等待後續的開發和使用。 如果你想在克隆遠程倉庫的時候,自定義本地倉庫的名字,你可以使用如下命令:
$ git clone https://github.com/libgit2/libgit2 mylibgit
這將執行與上一個命令相同的操作,不過在本地創建的倉庫名字變爲 mylibgit
。
Git 支持多種數據傳輸協議。 上面的例子使用的是 https://
協議,不過你也可以使用 git://
協議或者使用 SSH 傳輸協議,比如 user@server:path/to/repo.git
.
在上一節我們看到了,多人在同一個分支上協作時,很容易出現衝突。即使沒有衝突,後push的童鞋不得不先pull,在本地合併,然後才能push成功。
每次合併再push後,分支變成了這樣:
$ git log --graph --pretty=oneline --abbrev-commit
* d1be385 (HEAD -> master, origin/master) init hello
* e5e69f1 Merge branch 'dev'
|\
| * 57c53ab (origin/dev, dev) fix env conflict
| |\
| | * 7a5e5dd add env
| * | 7bd91f1 add new env
| |/
* | 12a631b merged bug fix 101
|\ \
| * | 4c805e2 fix bug 101
|/ /
* | e1e9c68 merge with no-ff
|\ \
| |/
| * f52c633 add merge
|/
* cf810e4 conflict fixed
總之看上去很亂,有強迫症的童鞋會問:爲什麼Git的提交歷史不能是一條幹淨的直線?
其實是可以做到的!
Git有一種稱爲rebase的操作,有人把它翻譯成“變基”。
先不要隨意展開想象。我們還是從實際問題出發,看看怎麼把分叉的提交變成直線。
在和遠程分支同步後,我們對hello.py
這個文件做了兩次提交。用git log
命令看看:
$ git log --graph --pretty=oneline --abbrev-commit
* 582d922 (HEAD -> master) add author
* 8875536 add comment
* d1be385 (origin/master) init hello
* e5e69f1 Merge branch 'dev'
|\
| * 57c53ab (origin/dev, dev) fix env conflict
| |\
| | * 7a5e5dd add env
| * | 7bd91f1 add new env
...
注意到Git用(HEAD -> master)
和(origin/master)
標識出當前分支的HEAD和遠程origin的位置分別是582d922 add author
和d1be385 init hello
,本地分支比遠程分支快兩個提交。
現在我們嘗試推送本地分支:
$ git push origin master
To github.com:michaelliao/learngit.git
! [rejected] master -> master (fetch first)
error: failed to push some refs to '[email protected]:michaelliao/learngit.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
很不幸,失敗了,這說明有人先於我們推送了遠程分支。按照經驗,先pull一下:
$ git pull
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 3 (delta 1), reused 3 (delta 1), pack-reused 0
Unpacking objects: 100% (3/3), done.
From github.com:michaelliao/learngit
d1be385..f005ed4 master -> origin/master
* [new tag] v1.0 -> v1.0
Auto-merging hello.py
Merge made by the 'recursive' strategy.
hello.py | 1 +
1 file changed, 1 insertion(+)
再用git status
看看狀態:
$ git status
On branch master
Your branch is ahead of 'origin/master' by 3 commits.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
加上剛纔合併的提交,現在我們本地分支比遠程分支超前3個提交。
用git log
看看:
$ git log --graph --pretty=oneline --abbrev-commit
* e0ea545 (HEAD -> master) Merge branch 'master' of github.com:michaelliao/learngit
|\
| * f005ed4 (origin/master) set exit=1
* | 582d922 add author
* | 8875536 add comment
|/
* d1be385 init hello
...
對強迫症童鞋來說,現在事情有點不對頭,提交歷史分叉了。如果現在把本地分支push到遠程,有沒有問題?
有!
什麼問題?
不好看!
有沒有解決方法?
有!
這個時候,rebase就派上了用場。我們輸入命令git rebase
試試:
$ git rebase
First, rewinding head to replay your work on top of it...
Applying: add comment
Using index info to reconstruct a base tree...
M hello.py
Falling back to patching base and 3-way merge...
Auto-merging hello.py
Applying: add author
Using index info to reconstruct a base tree...
M hello.py
Falling back to patching base and 3-way merge...
Auto-merging hello.py
輸出了一大堆操作,到底是啥效果?再用git log
看看:
$ git log --graph --pretty=oneline --abbrev-commit
* 7e61ed4 (HEAD -> master) add author
* 3611cfe add comment
* f005ed4 (origin/master) set exit=1
* d1be385 init hello
...
原本分叉的提交現在變成一條直線了!這種神奇的操作是怎麼實現的?其實原理非常簡單。我們注意觀察,發現Git把我們本地的提交“挪動”了位置,放到了f005ed4 (origin/master) set exit=1
之後,這樣,整個提交歷史就成了一條直線。rebase操作前後,最終的提交內容是一致的,但是,我們本地的commit修改內容已經變化了,它們的修改不再基於d1be385 init hello
,而是基於f005ed4 (origin/master) set exit=1
,但最後的提交7e61ed4
內容是一致的。
這就是rebase操作的特點:把分叉的提交歷史“整理”成一條直線,看上去更直觀。缺點是本地的分叉提交已經被修改過了。
最後,通過push操作把本地分支推送到遠程:
Mac:~/learngit michael$ git push origin master
Counting objects: 6, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (6/6), 576 bytes | 576.00 KiB/s, done.
Total 6 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 1 local object.
To github.com:michaelliao/learngit.git
f005ed4..7e61ed4 master -> master
再用git log
看看效果:
$ git log --graph --pretty=oneline --abbrev-commit
* 7e61ed4 (HEAD -> master, origin/master) add author
* 3611cfe add comment
* f005ed4 set exit=1
* d1be385 init hello
...
遠程分支的提交歷史也是一條直線。
遠程分支
遠程引用是對遠程倉庫的引用(指針),包括分支、標籤等等。 你可以通過 git ls-remote (remote)
來顯式地獲得遠程引用的完整列表,或者通過 git remote show (remote)
獲得遠程分支的更多信息。 然而,一個更常見的做法是利用遠程跟蹤分支。
遠程跟蹤分支是遠程分支狀態的引用。 它們是你不能移動的本地引用,當你做任何網絡通信操作時,它們會自動移動。 遠程跟蹤分支像是你上次連接到遠程倉庫時,那些分支所處狀態的書籤。
它們以 (remote)/(branch)
形式命名。 例如,如果你想要看你最後一次與遠程倉庫 origin
通信時 master
分支的狀態,你可以查看 origin/master
分支。 你與同事合作解決一個問題並且他們推送了一個 iss53
分支,你可能有自己的本地 iss53
分支;但是在服務器上的分支會指向 origin/iss53
的提交。
這可能有一點兒難以理解,讓我們來看一個例子。 假設你的網絡裏有一個在 git.ourcompany.com
的 Git 服務器。 如果你從這裏克隆,Git 的 clone
命令會爲你自動將其命名爲 origin
,拉取它的所有數據,創建一個指向它的 master
分支的指針,並且在本地將其命名爲 origin/master
。 Git 也會給你一個與 origin 的 master
分支在指向同一個地方的本地 master
分支,這樣你就有工作的基礎。
Note | “origin” 並無特殊含義遠程倉庫名字 “origin” 與分支名字 “master” 一樣,在 Git 中並沒有任何特別的含義一樣。 同時 “master” 是當你運行 git init 時默認的起始分支名字,原因僅僅是它的廣泛使用,“origin” 是當你運行 git clone 時默認的遠程倉庫名字。 如果你運行 git clone -o booyah ,那麼你默認的遠程分支名字將會是 booyah/master 。 |
---|---|
推送
當你想要公開分享一個分支時,需要將其推送到有寫入權限的遠程倉庫上。 本地的分支並不會自動與遠程倉庫同步 - 你必須顯式地推送想要分享的分支。 這樣,你就可以把不願意分享的內容放到私人分支上,而將需要和別人協作的內容推送到公開分支。
如果希望和別人一起在名爲 serverfix
的分支上工作,你可以像推送第一個分支那樣推送它。 運行 git push (remote) (branch)
:
$ git push origin serverfix
Counting objects: 24, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (15/15), done.
Writing objects: 100% (24/24), 1.91 KiB | 0 bytes/s, done.
Total 24 (delta 2), reused 0 (delta 0)
To https://github.com/schacon/simplegit
* [new branch] serverfix -> serverfix
這裏有些工作被簡化了。 Git 自動將 serverfix
分支名字展開爲 refs/heads/serverfix:refs/heads/serverfix
,那意味着,“推送本地的 serverfix 分支來更新遠程倉庫上的 serverfix 分支。” 我們將會詳細學習 refs/heads/
部分,但是現在可以先把它放在兒。 你也可以運行 git push origin serverfix:serverfix
,它會做同樣的事 - 相當於它說,“推送本地的 serverfix 分支,將其作爲遠程倉庫的 serverfix 分支” 可以通過這種格式來推送本地分支到一個命名不相同的遠程分支。 如果並不想讓遠程倉庫上的分支叫做 serverfix
,可以運行 git push origin serverfix:awesomebranch
來將本地的 serverfix
分支推送到遠程倉庫上的 awesomebranch
分支。
下一次其他協作者從服務器上抓取數據時,他們會在本地生成一個遠程分支 origin/serverfix
,指向服務器的 serverfix
分支的引用:
$ git fetch origin
remote: Counting objects: 7, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 3 (delta 0)
Unpacking objects: 100% (3/3), done.
From https://github.com/schacon/simplegit
* [new branch] serverfix -> origin/serverfix
要特別注意的一點是當抓取到新的遠程跟蹤分支時,本地不會自動生成一份可編輯的副本(拷貝)。 換一句話說,這種情況下,不會有一個新的 serverfix
分支 - 只有一個不可以修改的 origin/serverfix
指針。
可以運行 git merge origin/serverfix
將這些工作合併到當前所在的分支。 如果想要在自己的 serverfix
分支上工作,可以將其建立在遠程跟蹤分支之上:
$ git checkout -b serverfix origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch 'serverfix'
這會給你一個用於工作的本地分支,並且起點位於 origin/serverfix
。
跟蹤分支
從一個遠程跟蹤分支檢出一個本地分支會自動創建一個叫做 “跟蹤分支”(有時候也叫做 “上游分支”)。 跟蹤分支是與遠程分支有直接關係的本地分支。 如果在一個跟蹤分支上輸入 git pull
,Git 能自動地識別去哪個服務器上抓取、合併到哪個分支。
當克隆一個倉庫時,它通常會自動地創建一個跟蹤 origin/master
的 master
分支。 然而,如果你願意的話可以設置其他的跟蹤分支 - 其他遠程倉庫上的跟蹤分支,或者不跟蹤 master
分支。 最簡單的就是之前看到的例子,運行 git checkout -b [branch] [remotename]/[branch]
。 這是一個十分常用的操作所以 Git 提供了 --track
快捷方式:
$ git checkout --track origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch 'serverfix'
如果想要將本地分支與遠程分支設置爲不同名字,你可以輕鬆地增加一個不同名字的本地分支的上一個命令:
$ git checkout -b sf origin/serverfix
Branch sf set up to track remote branch serverfix from origin.
Switched to a new branch 'sf'
現在,本地分支 sf
會自動從 origin/serverfix
拉取。
設置已有的本地分支跟蹤一個剛剛拉取下來的遠程分支,或者想要修改正在跟蹤的上游分支,你可以在任意時間使用 -u
或 --set-upstream-to
選項運行 git branch
來顯式地設置。
$ git branch -u origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Note | 上游快捷方式當設置好跟蹤分支後,可以通過 @{upstream} 或 @{u} 快捷方式來引用它。 所以在 master 分支時並且它正在跟蹤 origin/master 時,如果願意的話可以使用 git merge @{u} 來取代 git merge origin/master 。 |
---|
如果想要查看設置的所有跟蹤分支,可以使用 git branch
的 -vv
選項。 這會將所有的本地分支列出來並且包含更多的信息,如每一個分支正在跟蹤哪個遠程分支與本地分支是否是領先、落後或是都有。
$ git branch -vv
iss53 7e424c3 [origin/iss53: ahead 2] forgot the brackets
master 1ae2a45 [origin/master] deploying index fix
* serverfix f8674d9 [teamone/server-fix-good: ahead 3, behind 1] this should do it
testing 5ea463a trying something new
這裏可以看到 iss53
分支正在跟蹤 origin/iss53
並且 “ahead” 是 2,意味着本地有兩個提交還沒有推送到服務器上。 也能看到 master
分支正在跟蹤 origin/master
分支並且是最新的。 接下來可以看到 serverfix
分支正在跟蹤 teamone
服務器上的 server-fix-good
分支並且領先 3 落後 1,意味着服務器上有一次提交還沒有合併入同時本地有三次提交還沒有推送。 最後看到 testing
分支並沒有跟蹤任何遠程分支。
需要重點注意的一點是這些數字的值來自於你從每個服務器上最後一次抓取的數據。 這個命令並沒有連接服務器,它只會告訴你關於本地緩存的服務器數據。 如果想要統計最新的領先與落後數字,需要在運行此命令前抓取所有的遠程倉庫。 可以像這樣做:$ git fetch --all; git branch -vv
拉取
當 git fetch
命令從服務器上抓取本地沒有的數據時,它並不會修改工作目錄中的內容。 它只會獲取數據然後讓你自己合併。 然而,有一個命令叫作 git pull
在大多數情況下它的含義是一個 git fetch
緊接着一個 git merge
命令。 如果有一個像之前章節中演示的設置好的跟蹤分支,不管它是顯式地設置還是通過 clone
或 checkout
命令爲你創建的,git pull
都會查找當前分支所跟蹤的服務器與分支,從服務器上抓取數據然後嘗試合併入那個遠程分支。
由於 git pull
的魔法經常令人困惑所以通常單獨顯式地使用 fetch
與 merge
命令會更好一些。
刪除遠程分支
假設你已經通過遠程分支做完所有的工作了 - 也就是說你和你的協作者已經完成了一個特性並且將其合併到了遠程倉庫的 master
分支(或任何其他穩定代碼分支)。 可以運行帶有 --delete
選項的 git push
命令來刪除一個遠程分支。 如果想要從服務器上刪除 serverfix
分支,運行下面的命令:
$ git push origin --delete serverfix
To https://github.com/schacon/simplegit
- [deleted] serverfix
基本上這個命令做的只是從服務器上移除這個指針。 Git 服務器通常會保留數據一段時間直到垃圾回收運行,所以如果不小心刪除掉了,通常是很容易恢復的。
遠程管理
- github,碼雲
- gitlab