6. 分享与更新项目

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 远端分支到你所在的任意分支。 我个人不太喜欢这命令 —— 我更喜欢 fetchmerge 分开来做。少点魔法,少点问题。 不过,如果你喜欢这主意,你可以看一下 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 服务器会拒绝你的推送,知道你是最新的为止。