Git教程 · 版本库间的交换
- 1️⃣ 克隆版本库
- 2️⃣ 如何告知 Git 其他版本库的位置
- 3️⃣ 给版本库添加别名
- 4️⃣ 获取数据
- 5️⃣ 远程跟踪分支:监控其他分支
- 6️⃣ 利用本地分支操作别处的版本库
- 7️⃣ PULL操作
- 8️⃣ PUSH操作
- 9️⃣ 命名分支
- 🌾 总结
Git 是个分布系统,它的版本库可以有多个克隆体。因此,每个开发者都可以有一份属于自己的克隆版本库,甚至还会同时保有若干份。他们通常会设置一个用于存放中央版本库的项目服务器。这个中央版本库代表了该项目的“官方”状态,我们称之为项目版本库。该版本库往往会存在多个克隆体,例如,为了进行备份或者对服务器上的内容进行持续集成。由于这里的每个克隆体自身都是一个独立的、信息完整的版本库。我们可以在每一个克隆体中 创建新的提交和分支,这就使得它们之间的信息交换变得非常重要。为了实现这个目的,我们需要用到 fetch
、pull
和 push
这3个命令。
1️⃣ 克隆版本库
版本库的克隆在Git 中扮演着非常重要的角色,执行克隆的原因可以有很多,详列如下。
- 每个开发者都必须要有至少一个克隆版本库才能用Git开展工作。
- 通常情况下,我们需要用某一份克隆体来充当中央版本库的角色,以示项目的“官方” 状态。
- 在多点开发条件下,每个开发点都会有一份属于自己的主克隆版本库,用于定期与各点的克隆版本库进行比对。
- 对于那些采用与主项目不同方向的独立开发(例如,如果你正策划着要对该项目来个重大转向的话), 他们通常也必须要克隆一份版本库来用,这样的克隆我们称之为分叉 (
fork
)。 - 当我们在版本库上执行某些棘手操作时很可能会给该项目或版本库带来某些损坏,这时候单独为此创建一份克隆版本库往往是非常有用的。
- 那些与 Git 相关的操作工具通常也会要求使用独立的克隆版本库。
- 克隆版本库还可以充当主版本库的备份。
clone
命令的使用非常简单。我们只需要以参数形式指定原版本库的位置即可,Git 就会在当前工作目录中创建它的一份克隆体。
通常情况下,Git 在创建克隆版本库之后会直接签出工作区。如果你不希望如此,可以用 --bare
选项来创建一个不带工作区的版本库。这对于服务器端的版本库是非常有用的,方便开发者可以对其进行直接操作。
2️⃣ 如何告知 Git 其他版本库的位置
如果是位于本地的其他版本库,我们可以直接指定其目录路径,以/Users/stachi/git-book.git
这个版本库为例,我们可以用以下命令来克隆一个本地版本库。
> git clone /Users/stachi/git-book.git
但是,如果我们正在处理多个不同来源的版本库,就应该在之前的内容上冠上相关的文件传输协议,这样URL 会显示得更清晰一些(具体到这里,就是 file 协议)。
> git clone file:///Users/stachi/git-book.git
除了 file协议,我们还可以用其他协议来访问非本地的版本库。其中,ssh 可能是使用频率最高的一种协议了,因为它所需要的安全认证和基础架构在我们 Linux 或 Unix 服务器上操作时往往都已经准备就绪了。
> git clone ssh://stachi@server.de:git-book.git
除此之外,我们也可以通过 http、https、ftp、ftps 和 rsync 协议或者一个被称之为 git 的专有协议来访问这些版本库。
3️⃣ 给版本库添加别名
如果经常需要访问某个版本库,为了访问起来容易,我们可以给它一个名字。例如,你可以通过 remote add
命令给它一个这样的昵称。
> git remote add myclone file:///tmp/git-book-clone.git
现在,我们就可以在 Git 命令中使用这个简短的名字,即这里的 myClone
, 而不是版本库的URL 了。
当某个版本库被克隆时,Git 会自动将原版本库路径的路径存储为它的源版本库(origin)。 如果这时我们调用带-verbose
选项的 remote
命令,Git 就会列出一些链接,以显示那些可被用于获取或推送提交的路径。
> git remote --verbose
origin ssh://stachieserver.de:git-book.git(fetch)
origin git@github.com:rpreissel/git-workflows.git(push)
klon file:///tmp/git-book-clone.git(fetch)
klon file:///tmp/git-book-clone.git(push)
最后,我们也可以通过remote rm
命令来删除这些昵称。
> git remote rm myclone
4️⃣ 获取数据
如果我们的工作是在克隆版本库上展开的,那么源版本库与其克隆体就会拉开距离。新提交和分支可能在它们中的任一版本库中被创建。
fetch
命令可用于从另一个版本库中获取提交,这种获取操作会将其他版本库中所有分支中尚未在本地版本库中存在的提交。
> git fetch myclone
下面,我们来看看图中发生了些什么。
D 和 E
: 即用fetch
命令从远程版本库中所获取的、本地版本库中缺失的提交。A 、B 和 C
: 这些是本地版本库中已有的提交,自然不会被传送。F
:fetch
命令是个单向操作。提交只能从远程版本库被传送到本地版本库中。如果想 将本地提交传送到远程版本库中,就需要使用push
命令。
请注意,fetch
是单向操作。在上述例子中,那些提交从克隆版本库被传送到了本地版本库中。而本地新的提交并没有被传送给克隆版本库。
我们还可以通过参数指定某个分支,以便只捡取来自该特定分支的修改。如果不指定参数 ,fetch
命令就会去获取源版本库中所有分支的提交,而源版本库就是本地版本库最初所克隆的版本库。
5️⃣ 远程跟踪分支:监控其他分支
如你所见,存在着两种类型的分支,分别是本地的和被远程跟踪的。之前我们已经了解过了本地分支的情况,下面我们来看看远程跟踪分支。
在执行抓取操作的同时,Git 会自行设置一个书签,该书签会指向抓取目标分支在其他版本库中的位置。这些书签就被称之为远程跟踪分支。 一个远程跟踪分支主要有其他版本库的 短名称以及其中的分支名构成。在下图中,clone/feature-a
和 clone/master
都属于远程跟踪 分支。我们可以通过带-r
选项的 branch
命令来显示这些远程跟踪分支。
> git branch -r
clone/feature-a
clone/master
origin/HEAD -> origin/master
origin/feature-a
origin/master
在此,我们可以比对一下自己在本地分支上以及其他开发者在同一时期内各做了些什么。
diff
命令可以用来显示这些版本之间的不同。
> git diff feature-a clone/feature-a
通过log
命令,我们可以查看那些来自远程版本库的新增提交。
> git log --oneline feature-a..klon/feature-a
等我们下次再做获取操作时,该远程跟踪分支也会随之再次被更新。
请注意:Git 对远程跟踪分支的处理与本地分支不同。你可以像本地分支一样对远程跟踪分支进行迁出操作,但这时我们所获取的是一个离了线的HEAD 状态(这就等于我们签出的是一个旧版的提交)。所以我们不该那么做,而应该从远程跟踪分支中分岔出一个本地分支来,相关内容会在下一节中做介绍。
> git checkout -b feature-b clone/feature-b
6️⃣ 利用本地分支操作别处的版本库
我们也可以通过获取操作来创建一个本地分支。要想做到这一点,我们需要用到冒号(:
) 操作符。只需在冒号之前指定别处版本库的分支名,并在冒号后面指定本地分支名即可。
> git fetch clone feature-b:my-feature-b
该命令所获取的是 clone 版本库的 feature-b 从分支以及其中的内容。然后,如果本地不存在一个名为my-feature-b的分支,就创建它,如果已经存在,就对其进行更新。
7️⃣ PULL操作
Pull=Fetch+Merge
获取操作通常都会带来冲突,因为会有新的提交被添加到本地或别处版本库中。在大多数情况下,它们之间的都需要进行合并。
执行获取操作后项目中出现了双头局面,高亮部分即还尚未被挑选过的提交。而 pull
命令则是这么做的:它会从远程版本库中导入这些提交,然后在必要的情况下将它们合并到当前分支上 (见下图)。
> git pull
执行拉回操作后的情况,高亮部分即尚未被挑选的提交与合并提交。
如果你更喜欢线性发展的历史,也可以使用带 -rebase
选项的 pull
命令。然后,我们后面所要执行的操作就是变基,而不是合并了(见下图)。
> git pull --rebase
执行拉回操作后的情况,高亮部分即迁移后的提交。
8️⃣ PUSH操作
我们可以用 push
命令将本地版本库中的提交传送到远程版本库中。例如,使用以下命令,可以将 feature-a 分支下的新本地提交传送给 clone 所指向的远程版本库,并更新分支指针,使其指向 feature-a 所在的地方。
> git push clone feature-a
push
和pull
这两个操作之间存在着一些重要差异,这是我们需要考虑到的。
- 写访问:
push
只能用在我们对其他版本库有写访问权限时。 - 只针对快进合并:
push
操作通常不会带来合并(不像pull
命令)。push
操作只在快进提交模式下被允许。也就是远程版本库没有比本地更多更新的提交。 - 无远程跟踪分支。
- 无参数调用
push
: 在无参数的情况下,push
命令将只发送那些在其他版本库中有相 同名字匹配的本地分支。与之不同的是,pull
和fetch
所选取的都是全部分支。
需要注意的是, Git 会在快进合并不可行的时候拒绝推送。当然,你也可以通过-force
参数来强制推送。但不建议这样做,因为这有可能会导致其他版本库中的某个提交丢失。 这时往往更好的做法是在本地解决冲突,具体步骤见如下说明。
推送被拒绝后,下一步怎么做?
推送操作之所以会被拒绝,通常是因为相关的修改已经被添加到其他版本的同一分支 里了。因此在推送能够执行之前,我们必须要先在本地解决冲突。
- 找到冲突
push
命令会通过以下略显冗长的信息来报告这件事。> git push clone feature-a To /tmp/git-book-clone.git ! [rejected] feature-a -> feature-a(non-fast-forward) error:failed to push some refs to '/Users/stachi/Book/' To prevent you from losing history, non-fast-forward updates were rejected.Merge the remote changes(e.g.'git pull')before pushing again. See the 'Note about fast-forwards' section of 'git push --help' for details.
(为了防止历史记录被丢失,不属于快进合并的更新会被拒绝。因此在重新推送之前, 我们需要对远端版本库中的修改进行合并(例如用“
git pull
”)。相关细节参见“git push -help
”文档中的“Note about fast-forwards
”一节。)
- 改变分支
> git checkout feature-a
- 执行一次拉取操作
> git pull
- 在必要情况下,清理合并冲突
> git mergetool > git commit --all checkout feature-a
- 重新推送
> git push clone feature-a
如果我们调用的是无参数的
push
, 可能需要执行数次上述步骤,每个带冲突的分支都
要执行一次。
9️⃣ 命名分支
当我们在某个软件团队中进行共同协作时,对分支进行统一命名无疑是个明智的选择。
Git 允许开发者自由地命名本地分支。如果我们这样做了,就必须要在使用 fetch
、pull
或 push
时在参数中用冒号来指定它们。用冒号之前的单词指定源分支,而冒号之后的单词则用于指定目标后支。下面来看个具体例子。
> git pull clone feature-a:favorite-feature
在这里,pull
命令从 clone 版本库中导入了 feature-a 分支,并在本地将该分支命名成了
favorite-feature。
删除远程版本库中的分支是一种特殊情况。对于这种情况,我们只需在使用带冒号参数
的 push
命令时在冒号左侧留空,以表示将该分支设置成不指向任何地方即可。
如果你想删除远程版本库中的某个分支的话,可以执行以下步骤。但必须要注意,
这些步骤很有可能会造成某些提交丢失。
- 删除远程版本库中的分支(请留意冒号的用法)
> git push clone :feature-a
- 必须要的情况下,也要删除本地的相应分支
> git branch -d feature-a
🌾 总结
- 版本库URL: 即以URL 的格式指示远程版本库所在的位置,例如
ssh://stachi@server.de:git-book.git
。该 URL 支持以下协议:file 、ssh 、http 、https 、ftp 、ftps 、rsync 和git。 - 昵称:我们可以用
remote
命令为版本库定义一个昵称,这样一来,我们每次在需要访问该版本库是就不必指定那么长的URL 了。 - 获取:
fetch
命令可从远程版本的分支上获取提交。当然,它只能用于传送那些还不 在本地的提交。 - 无参数获取:该操作将检索远程版本库上所有分支上的提交。
- 不移动本地分支的获取:该操作只获取相关提交,并设置远程跟踪分支。
- 远程跟踪分支:我们可以指定远程版本库中分支的位置,例如 clone/featurea。然后 fetch 和 pull 命令就会去更新该远程跟踪分支。
- PULL拉取:
pull
命令是两种操作的组合。它首先执行的是获取操作。然后 再将本地分支的修改与其检索到的远程版本库中的修改合并起来。 - PUSH推送:
push
命令可将本地分支中的提交传送给远程版本库。 - 推送转换分支:分支指针原本是在远程版本库中设置的,它们可以被设置为本地版本库的状态。
- 只针对快进合并的推送:Git 会在有其他开发者在相同分支上执行推送的情况下拒绝 我们的推送,因为这时执行快进合并是不可能的。在这种情况下,相关修改必须要先一并在本地进行处理,例如先执行一次拉取操作。
- 无参数的推送:在这种情况下,就只有与远程版本库中有同名匹配的本地分支将会被传送。
《【Git教程】(七)变基与拣取 —— 变基操作的概念、适用场景及其实现方式,拣取操作的实现 ~》