git 分支管理

发布时间 2023-08-24 14:50:13作者: luckylan

1.获取远程仓库的指定分支

当从远程仓库拉取代码的时候一般都是使用git clone 命令来获取,这样我们是将项目整个都克隆到我们本地仓库,并且分支还是主分支maser。当然有时候我们只需拉取远程仓库指定的某一个分支,下面三种方法可以使用。

git clone 命令获取

使用git管理代码版本的时候,本地分支默认与远程同名分支建立追踪关系。文章开始也提到git clone 命令默认将整个远程版本库克隆到本地,但是git clone -b <分支名称>命令可以将指定的某一个远程分支拉取到我们本地,而且拉取的本地分支自动和远程同名分支建立追踪关系,它不会将新创建的HEAD指向克隆仓库中HEAD指向的maser主分支,而是指向我们刚拉取下来的分支;示例如下: 

git clone -b dev开发分支 https://github.com/lanyy/repo_test.git

分支拉取结束以后,执行git branch -a 命令,结果如下:

$ git branch -a
* dev开发分支
remotes/origin/HEAD -> origin/master
remotes/origin/dev开发分支
remotes/origin/master
remotes/origin/test测试分支

从执行结果可以看出,当前分支为“dev开发分支” ,剩余的四个都是远程分支;

注意:用git clone -b <分支名称> 拉取指定的某一个分支时,我们本地是没有克隆url对应的远程仓库代码,也就是说进入某个路径的文件夹后直接使用该命令去拉取指定分支,而不是先执行git clone 将远程代码库克隆到本地以后进入项目目录再执行该命令去拉取指定分支。否则,拉取下来的还是master主分支。

git fetch 命令获取

git fetch命令从远程仓库拉取指定某一个分支时,和上面的git clone -b命令时不一样。使用git fetch命令时,需要先将远程的仓库克隆到本地,然后在执行git fetch命令。该命令执行完以后还是处在master主分支的,如果进去目录没有发现想要拉取的仓库文件。此时并不是git fetch命令没有执行,而是我们还没有切换到我们想要拉取的分支上,需要我们执行git checkout命令切换到我们想要拉取的分支上。示例如下:

1.拉取整个远程代码库

git clone https://github.com/lanyy/repo_test.git

2.进入项目目录,也就是进入master主分支

cd repo_test/

3.执行git fetch命令,将远程仓库的所有分支拷贝到本地仓库

git fetch

4.执行git checkout <分支名称>命令,切换到我们想要拉取的指定某一个分支的本地分支

git checkout dev开发分支

这是就会看到本地仓库的dev开发分支和远程仓库的dev开发分支一样的内容, 默认情况下,git fetch下载的分支和远程的分支名相同。

5.执行git branch -a命令查看所有分支情况

$ git branch -a
* dev开发分支
master
remotes/origin/HEAD -> origin/master
remotes/origin/dev开发分支
remotes/origin/master
remotes/origin/test测试分支

可以看到本地有两个分支,目前所在的分支为dev开发分支,还有三个远程分支。到此通过git fetch命令来获取远程仓库某一个指定分支的步骤就执行完毕了。

git checkout -b 命令获取

第三种获取远程仓库某一个指定分支的方法和第二种方法有点类似,都是将远程仓库克隆到本地仓库,然后执行git checkout -b <本地分支名称> origin/<远程分支名称>,具体步骤如下:

1.进入某个文件夹执行克隆远程仓库的git

git clone https://github.com/lanyy/repo_test.git

此时远程仓库就克隆到了本地仓库。

2.进入项目目录,也就是进入master主分支

$ cd repo_test/

3.执行git branch -a查看所有分支名称,* 号表示当前分支

$ git branch -a
* master
remotes/origin/HEAD -> origin/master
remotes/origin/dev开发分支
remotes/origin/master
remotes/origin/test测试分支

4.执行git checkout -b <本地分支名称> origin/<远程分支名称>,拉取指定的某一个分支

$ git checkout -b dev开发分支 origin/dev开发分支

该命令的作用是:checkout远程仓库origin的分支“dev开发分支”,在本地起名为“dev开发分支”分支,并切换到本地的“dev开发分支”分支。

5.拉取该分支的最新代码

$git pull origin dev开发分支

至此,通过三种方法从远程仓库拉取指定某一分支就总结完了,如果有不同见解请指出。

2.Git 分支管理

几乎每一种版本控制系统都以某种形式支持分支,一个分支代表一条独立的开发线。

使用分支意味着你可以从开发主线上分离开来,然后在不影响主线的同时继续工作。

Git 分支实际上是指向更改快照的指针。

有人把 Git 的分支模型称为必杀技特性,而正是因为它,将 Git 从版本控制系统家族里区分出来。

创建分支命令:

git branch (branchname)

切换分支命令:

git checkout (branchname)

当你切换分支的时候,Git 会用该分支的最后提交的快照替换你的工作目录的内容, 所以多个分支不需要多个目录。

合并分支命令:

git merge 

你可以多次合并到统一分支, 也可以选择在合并之后直接删除被并入的分支。

创建master分支并提交

开始前我们先创建一个测试目录和master分支,并进行第一次版本提交:

$ mkdir gitdemo
$ cd gitdemo/
$ git init
Initialized empty Git repository...
$ touch README
$ git add README
$ git commit -m '第一次版本提交'
[master (root-commit) 3b58100] 第一次版本提交
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 README

列出分支

列出分支基本命令:

git branch

没有参数时,git branch 会列出你在本地的分支。

$ git branch
* master

此例的意思就是,我们有一个叫做 master 的分支,带*号的分支表示该分支是当前分支。当你执行 git init 的时候,默认情况下 Git 就会为你创建 master 分支。

Git 的分支,其实本质上仅仅是指向提交对象的可变指针。 Git 的默认分支名字是master 。 在多次提交操作之后,你其实已经有一个指向最后那个提交对象的分支master。 它会在每次的提交操作中自动向前移动。

Git 的 “master” 分支并不是一个特殊分支。 它就跟其它分支完全没有区别。 之所以几乎每一个仓库都有 master 分支,是因为git init命令默认创建它,并且大多数人都懒得去改动它。

创建新的分支

Git 创建新分支只是为你创建了一个可以移动的新的指针。 如果我们要手动创建一个新的分支,执行 git branch (branchname) 即可。
$ git branch testing
$ git branch
* master
  testing

现在我们可以看到,有了一个新分支 testing,并且testing指针指向当前的master所指向的分支

在 Git 中,通过一个HEAD指针来知道当前在哪一个分支上的,有一个名为HEAD的特殊指针,指向当前所在的本地分支。 因为git branch命令仅仅创建 一个新分支,并不会自动切换到新分支中去,所以git branch testing后,仍然在master分支上

 

当在第一次提交更新之后创建了新分支testing,如果我们并没有切换到testing分支,而是继续在master分支工作,并且如果master分支后来又有更新提交, 当我们能切换到了 testing 分支,Git 将还原工作目录到创建testing分支时候的样子。

2.继续对master分支进行修改

$ ls
README
$ echo 'runoob.com' > test.txt
$ git add .
$ git commit -m 'add test.txt'
[master 3e92c19] add test.txt
 1 file changed, 1 insertion(+)
 create mode 100644 test.txt
$ ls
README        test.txt

接下来我们将演示如何切换分支,我们用 git checkout (branch) 切换到我们要修改的分支。

分支切换

要切换到一个已存在的分支,你需要使用git checkout命令。 我们现在切换到新创建的testing分支去: 

$ git checkout testing

这样HEAD就指向testing分支了。

$ git checkout testing
Switched to branch 'testing'
$ ls
README

当我们切换到 testing 分支的时候,我们添加的新文件 test.txt 被移除了。切换回 master 分支的时候,它们又重新出现了。

$ git checkout master
Switched to branch 'master'
$ ls
README        test.txt

我们也可以使用 git checkout -b (branchname) 命令来创建新分支并立即切换到该分支下,从而在该分支中操作。

使用HEAD指针指向当前所在的分支,当我们切换到testing分支,并在testing分支上面做些修改提交后:

vim test.rb
$ git commit -a -m 'made a change'
HEAD 分支随着提交操作自动向前移动。
可以看到HEAD 分支随着提交操作自动向前移动。testing分支向前移动了,但是master分支却没有,它仍然指向运行git checkout时所指的对象。 这就有意思了,现在我们切换回masteer分支看看:
git checkout master
检出时 HEAD 随之移动。
检出时 HEAD 随之移动。git checkout命令做了两件事。 一是使 HEAD 指回master分支,二是将工作目录恢复成master分支所指向的快照内容。 也就是说,你现在做修改的话,项目将始于一个较旧的版本。 本质上来讲,这就是忽略testing分支所做的修改,以便于向另一个方向进行开发。
分支切换会改变你工作目录中的文件:
1.在切换分支时,一定要注意你工作目录里的文件会被改变。 如果是切换到一个较旧的分支,当前工作目录会恢复到切换到的分支最后一次提交时的样子。 如果 Git 不能干净利落地完成这个任务,它将禁止切换分支。
2.在切换分支时,要留意你的工作目录和暂存区里那些还没有被提交的修改,它可能会和你即将检出的分支产生冲突从而阻止 Git 切换到该分支。 最好的方法是,在你切换分支之前,保持好一个干净的状态。 有一些方法可以绕过这个问题(即,保存进度(stashing) 和 修补提交(commit amending)).
3.当切换分支的时候,Git 会重置你的工作目录,使其看起来像回到了检出分支上最后一次提交的样子。 Git 会自动添加、删除、修改文件以确保此时你的工作目录和检出分支最后一次提交时的样子一模一样。

分支的新建与合并

让我们来看一个简单的分支新建与分支合并的例子,实际工作中你可能会用到类似的工作流。 你将经历如下步骤:

  1. 开发某个项目。

  2. 为实现某个新的需求,创建一个分支。

  3. 在这个分支上开展工作。

正在此时,master有个很严重的问题需要紧急修补。 你将按照如下方式来处理:

  1. 切换到你的线上分支(production branch:master)。

  2. 为这个紧急任务新建一个分支,并在其中修复它。

  3. 在测试通过之后,切换回线上分支,然后合并这个修补分支,最后将改动推送到线上分支。

  4. 切换回你最初工作的分支上,继续工作。

新建分支

首先,我们假设正在进行项目上工作,并且已经有一些提交。

一个简单的提交历史。

现在,为了解决一个#53 问题,想要新建一个分支并同时切换到那个分支上,你可以运行一个带有 参数的 命令:-bgit checkout

$ git checkout -b iss53
Switched to a new branch "iss53"

它是下面两条命令的简写:

$ git branch iss53
$ git checkout iss53
创建一个新分支指针。
创建一个新分支指针

继续在 #53 问题上工作,并且做了一些提交。 在此过程中,iss53分支在不断的向前推进,因为你已经检出到该分支(也就是说,你的HEAD指针指向了iss53分支)

$ vim index.html
$ git commit -a -m 'added a new footer [issue 53]'
iss53 分支随着工作的进展向前推进。
 iss53 分支随着工作的进展向前推进

忽然有个紧急问题等待你来解决。 有了 Git ,所要做的仅仅是切换回master分支。

在切换回master分支之前,确保工作目录和暂存区里那些还没有被提交的修改,它可能会和你即将检出的分支产生冲突从而阻止 Git 切换到该分支。 最好的方法是,在你切换分支之前,保持好一个干净的状态。 有一些方法可以绕过这个问题(即,保存进度(stashing) 和 修补提交(commit amending))。 现在,我们假设你已经把你的修改全部提交了,这时你可以切换回master分支了:

$ git checkout master
Switched to branch 'master'

切换回master分支后,工作目录和在开始 #53 问题之前一模一样,现在可以专心修复紧急问题了。

需要注意的是:当你切换分支的时候,Git 会重置工作目录,使其看起来像回到了你在那个分支上最后一次提交的样子。 

接下来,你要修复这个紧急问题。 让我们建立一个针对该紧急问题的分支(hotfix branch),在该分支上工作直到问题解决:

$ git checkout -b hotfix
Switched to a new branch 'hotfix'
$ vim index.html
$ git commit -a -m 'fixed the broken email address'
[hotfix 1fb7853] fixed the broken email address
 1 file changed, 2 insertions(+)
基于 `master` 分支的紧急问题分支(hotfix branch)。
基于 master分支的紧急问题分支hotfix

你可以运行你的测试,确保你的修改是正确的,然后可以使用git merge将其合并回你的master分支来部署到线上。 

$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast-forward
 index.html | 2 ++
 1 file changed, 2 insertions(+)

在合并的时候,可以看到"快进(fast-forward)"这个词。 由于当前master分支所指向的提交是你当前提交(有关 hotfix 的提交)的直接上游,所以 Git 只是简单的将指针向前移动。 换句话说,当你试图合并两个分支时,如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候,只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧——这就叫做 “快进(fast-forward)”。

现在,最新的修改已经在master分支所指向的提交快照中,可以发布该修复了。

`master` 被快进到 `hotfix`。
master被快进到hotfix

关于这个紧急问题的解决方案发布之后,需要回到被打断之前时的工作中。 然而,应该先删除hotfix分支,因为master分支已经指向了同一个位置。 你可以使用带-d选项的git branch命令来删除分支:

$ git branch -d hotfix
Deleted branch hotfix (3a0874c).

现在你可以切换回你正在工作的分支继续你的工作,也就是针对 #53 问题的那个分支(iss53 分支)。

$ git checkout iss53
Switched to branch "iss53"
$ vim index.html
$ git commit -a -m 'finished the new footer [issue 53]'
[iss53 ad82d7a] finished the new footer [issue 53]
1 file changed, 1 insertion(+)
继续在 `iss53` 分支上的工作。
继续在iss53分支上的工作

你在hotfix分支上所做的工作并没有包含到iss53分支中。 如果你需要拉取hotfix所做的修改,你可以使用git merge命令将master分支合并入iss53分支,或者你也可以等到iss53分支完成其使命,再将其合并回master分支。

分支的合并

假设你已经修正了 #53 问题,并且打算将你的工作合并入master分支。 为此,你需要合并iss53分支到master分支,这和之前你合并hotfix分支所做的工作差不多。 你只需要检出到你想合并入的分支,然后运行git merge命令:

$ git checkout master
Switched to branch 'master'
$ git merge iss53
Merge made by the 'recursive' strategy.
index.html |    1 +
1 file changed, 1 insertion(+)

在这种情况下,你的开发历史从一个更早的地方开始分叉开来(diverged)。 因为,master分支所在提交并不是ISS53分支所在提交的直接祖先。 出现这种情况的时候,Git 会使用两个分支的末端所指的快照( C4和 C5)以及这两个分支的工作祖先(C2),做一个简单的三方合并。

一次典型合并中所用到的三个快照。
 一次典型合并中所用到的三个快照

和之前将分支指针向前推进所不同的是,Git 将此次三方合并的结果做了一个新的快照并且自动创建一个新的提交指向它。 这个被称作一次合并提交,它的特别之处在于他有不止一个父提交。

一个合并提交。
一个合并提交

需要指出的是,Git 会自行决定选取哪一个提交作为最优的共同祖先,并以此作为合并的基础.

既然你的修改已经合并进来了,你已经不再需要iss53分支了。 现在你可以在任务追踪系统中关闭此项任务,并删除这个分支。

$ git branch -d iss53

遇到冲突时的分支合并

有时候合并操作不会如此顺利。 如果你在两个不同的分支中,对同一个文件的同一个部分进行了不同的修改,Git 就没法干净的合并它们。 如果你对 #53 问题的修改和有关hotfix的修改都涉及到同一个文件的同一处,在合并它们的时候就会产生合并冲突:

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

此时 Git 做了合并,但是没有自动地创建一个新的合并提交。 Git 会暂停下来,等待你去解决合并产生的冲突。 你可以在合并冲突后的任意时刻使用git status命令来查看那些因包含合并冲突而处于未合并(unmerged)状态的文件:

$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")

Unmerged paths:
  (use "git add <file>..." to mark resolution)

    both modified:      index.html

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

任何因包含合并冲突而有待解决的文件,都会以未合并状态标识出来。 Git 会在有冲突的文件中加入标准的冲突解决标记,这样你可以打开这些包含冲突的文件然后手动解决冲突。 出现冲突的文件会包含一些特殊区段,看起来像下面这个样子:

<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
 please contact us at support@github.com
</div>
>>>>>>> iss53:index.html

这表示HEAD所指示的版本(也就是你的master 分支所在的位置,因为你在运行 merge 命令的时候已经检出到了这个分支)在这个区段的上半部分(======= 的上半部分),而iss53分支所指示的版本在=======的下半部分。 为了解决冲突,你必须选择使用由==============分割的两部分中的一个,或者你也可以自行合并这些内容。 例如,你可以通过把这段内容换成下面的样子来解决冲突:

<div id="footer">
please contact us at email.support@github.com
</div>

上述的冲突解决方案仅保留了其中一个分支的修改,并且<<<<<<< , =======, 和>>>>>>>这些行被完全删除了。 在你解决了所有文件里的冲突之后,对每个文件使用git add命令来将其标记为冲突已解决。 一旦暂存这些原本有冲突的文件,Git 就会将它们标记为冲突已解决。

等你退出合并工具之后,Git 会询问刚才的合并是否成功。 如果你回答是,Git 会暂存那些文件以表明冲突已解决: 你可以再次运行git status来确认所有的合并冲突都已被解决:

$ git status
On branch master
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:

    modified:   index.html

如果你对结果感到满意,并且确定之前有冲突的的文件都已经暂存了,这时你可以输入git commit来完成合并提交。 默认情况下提交信息看起来像下面这个样子:

Merge branch 'iss53'

Conflicts:
    index.html
#
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
#    .git/MERGE_HEAD
# and try again.


# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# All conflicts fixed but you are still merging.
#
# Changes to be committed:
#    modified:   index.html
#

如果你觉得上述的信息不够充分,不能完全体现分支合并的过程,你可以修改上述信息,添加一些细节给未来检视这个合并的读者一些帮助,告诉他们你是如何解决合并冲突的,以及理由是什么。

分支管理

现在已经创建、合并、删除了一些分支,让我们看看一些常用的分支管理工具。

git branch 命令不只是可以创建与删除分支。 如果不加任何参数运行它,会得到当前所有分支的一个列表:

$ git branch
  iss53
* master
  testing

注意master分支前的*字符:它代表现在检出的那一个分支(也就是说,当前HEAD指针所指向的分支)。 这意味着如果在这时候提交,master分支将会随着新的工作向前移动。 如果需要查看每一个分支的最后一次提交,可以运行git branch -v命令:

$ git branch -v
  iss53   93b412c fix javascript issue
* master  7a98805 Merge branch 'iss53'
  testing 782fd34 add scott to the author list in the readmes

--merged 与 --no-merged这两个有用的选项可以过滤这个列表中已经合并或尚未合并到当前分支的分支。 如果要查看哪些分支已经合并到当前分支,可以运行git branch --merged :

$ git branch --merged
  iss53
* master

因为之前已经合并了iss53分支,所以现在看到它在列表中。 在这个列表中分支名字前没有*号的分支通常可以使用git branch -d删除掉; 你已经将它们的工作整合到了另一个分支,所以并不会失去任何东西。

查看所有包含未合并工作的分支,可以运行 :git branch --no-merged

$ git branch --no-merged
  testing

这里显示了其他分支。 因为它包含了还未合并的工作,尝试使用git branch -d命令删除它时会失败:

$ git branch -d testing
error: The branch 'testing' is not fully merged.
If you are sure you want to delete it, run 'git branch -D testing'.

如果真的想要删除分支并丢掉那些工作,如同帮助信息里所指出的,可以使用-D选项强制删除它。

分支开发工作流

长期分支

因为 Git 使用简单的三方合并,所以就算在一段较长的时间内,反复把一个分支合并入另一个分支,也不是什么难事。 也就是说,在整个项目开发周期的不同阶段,你可以同时拥有多个开放的分支; 你可以定期地把某些特性分支合并入其他分支中。

许多使用 Git 的开发者都喜欢使用这种方式来工作,比如只在master分支上保留完全稳定的代码——有可能仅仅是已经发布或即将发布的代码。 他们还有一些名为develop或者next的平行分支,被用来做后续开发或者测试稳定性——这些分支不必保持绝对稳定,但是一旦达到稳定状态,它们就可以被合并入master分支了。 这样,在确保这些已完成的特性分支(短期分支,比如之前的iss53分支)能够通过所有测试,并且不会引入更多 bug 之后,就可以合并入主干分支中,等待下一次的发布。

事实上我们刚才讨论的,是随着你的提交而不断右移的指针。 稳定分支的指针总是在提交历史中落后一大截,而前沿分支的指针往往比较靠前。

渐进稳定分支的线性图。
渐进稳定分支的线性图

通常把他们想象成流水线(work silos)可能更好理解一点,那些经过测试考验的提交会被遴选到更加稳定的流水线上去。

渐进稳定分支的工作流(“silo”)视图。
渐进稳定分支的流水线(“silo”)视图

你可以用这种方法维护不同层次的稳定性。 一些大型项目还有一个 proposedpu(建议) 或proposed updates(建议更新)分支,它可能因包含一些不成熟的内容而不能进入 next或者master分支。 这么做的目的是使你的分支具有不同级别的稳定性; 当它们具有一定程度的稳定性后,再把它们合并入具有更高级别稳定性的分支中。 再次强调一下,使用多个长期分支的方法并非必要,但是这么做通常很有帮助,尤其是当你在一个非常庞大或者复杂的项目中工作时.

特性分支

特性分支对任何规模的项目都适用。 特性分支是一种短期分支,它被用来实现单一特性或其相关工作。 也许你从来没有在其他的版本控制系统()上这么做过,因为在那些版本控制系统中创建和合并分支通常很费劲。 然而,在 Git 中一天之内多次创建、使用、合并、删除分支都很常见。VCS

你已经在上一节中你创建的 和 特性分支中看到过这种用法。 你在上一节用到的特性分支( 和 分支)中提交了一些更新,并且在它们合并入主干分支之后,你又删除了它们。 这项技术能使你快速并且完整地进行上下文切换(context-switch)——因为你的工作被分散到不同的流水线中,在不同的流水线中每个分支都仅与其目标特性相关,因此,在做代码审查之类的工作的时候就能更加容易地看出你做了哪些改动。 你可以把做出的改动在特性分支中保留几分钟、几天甚至几个月,等它们成熟之后再合并,而不用在乎它们建立的顺序或工作进度。iss53hotfixiss53hotfix

考虑这样一个例子,你在 分支上工作到 ,这时为了解决一个问题而新建 分支,在 分支上工作到 ,然而对于那个问题你又有了新的想法,于是你再新建一个 分支试图用另一种方法解决那个问题,接着你回到 分支工作了一会儿,你又冒出了一个不太确定的想法,你便在 的时候新建一个 分支,并在上面做些实验。 你的提交历史看起来像下面这个样子:masterC1iss91iss91C4iss91v2masterC10dumbidea

拥有多个特性分支的提交历史。
拥有多个特性分支的提交历史

现在,我们假设两件事情:你决定使用第二个方案来解决那个问题,即使用在 分支中方案; 另外,你将 分支拿给你的同事看过之后,结果发现这是个惊人之举。 这时你可以抛弃 分支(即丢弃 和 提交),然后把另外两个分支合并入主干分支。 最终你的提交历史看起来像下面这个样子:iss91v2dumbideaiss91C5C6

合并了 `dumbidea` 和 `iss91v2` 分支之后的提交历史。
Figure 29. 合并了 和 分支之后的提交历史dumbideaiss91v2

我们将会在 分布式 Git 中向你揭示更多有关分支工作流的细节,因此,请确保你阅读完那个章节之后,再来决定你的下个项目要使用什么样的分支策略(branching scheme)。

请牢记,当你做这么多操作的时候,这些分支全部都存于本地。 当你新建和合并分支的时候,所有这一切都只发生在你本地的 Git 版本库中 —— 没有与服务器发生交互。

远程分支

远程引用是对远程仓库的引用(指针),包括分支、标签等等。 你可以通过 来显式地获得远程引用的完整列表,或者通过 获得远程分支的更多信息。 然而,一个更常见的做法是利用远程跟踪分支。git ls-remote (remote)git remote show (remote)

远程跟踪分支是远程分支状态的引用。 它们是你不能移动的本地引用,当你做任何网络通信操作时,它们会自动移动。 远程跟踪分支像是你上次连接到远程仓库时,那些分支所处状态的书签。

它们以 形式命名。 例如,如果你想要看你最后一次与远程仓库 通信时 分支的状态,你可以查看 分支。 你与同事合作解决一个问题并且他们推送了一个 分支,你可能有自己的本地 分支; 但是在服务器上的分支会指向 的提交。(remote)/(branch)originmasterorigin/masteriss53iss53origin/iss53

这可能有一点儿难以理解,让我们来看一个例子。 假设你的网络里有一个在 的 Git 服务器。 如果你从这里克隆,Git 的 命令会为你自动将其命名为 ,拉取它的所有数据,创建一个指向它的 分支的指针,并且在本地将其命名为 。 Git 也会给你一个与 origin 的 分支在指向同一个地方的本地 分支,这样你就有工作的基础。git.ourcompany.comcloneoriginmasterorigin/mastermastermaster

注意
“origin” 并无特殊含义

远程仓库名字 “origin” 与分支名字 “master” 一样,在 Git 中并没有任何特别的含义一样。 同时 “master” 是当你运行 时默认的起始分支名字,原因仅仅是它的广泛使用,“origin” 是当你运行 时默认的远程仓库名字。 如果你运行 ,那么你默认的远程分支名字将会是 。git initgit clonegit clone -o booyahbooyah/master

克隆之后的服务器与本地仓库。
Figure 30. 克隆之后的服务器与本地仓库

如果你在本地的 分支做了一些工作,然而在同一时间,其他人推送提交到 并更新了它的 分支,那么你的提交历史将向不同的方向前进。 也许,只要你不与 origin 服务器连接,你的 指针就不会移动。mastergit.ourcompany.commasterorigin/master

本地与远程的工作可以分叉。
Figure 31. 本地与远程的工作可以分叉

如果要同步你的工作,运行 命令。 这个命令查找 “origin” 是哪一个服务器(在本例中,它是 ),从中抓取本地没有的数据,并且更新本地数据库,移动 指针指向新的、更新后的位置。git fetch origingit.ourcompany.comorigin/master

`git fetch` 更新你的远程仓库引用。
Figure 32. 更新你的远程仓库引用git fetch

为了演示有多个远程仓库与远程分支的情况,我们假定你有另一个内部 Git 服务器,仅用于你的 sprint 小组的开发工作。 这个服务器位于 。 你可以运行 命令添加一个新的远程仓库引用到当前的项目,这个命令我们会在 Git 基础 中详细说明。 将这个远程仓库命名为 ,将其作为整个 URL 的缩写。git.team1.ourcompany.comgit remote addteamone

添加另一个远程仓库。
Figure 33. 添加另一个远程仓库

现在,可以运行git fetch teamone来抓取远程仓库 有而本地没有的数据。 因为那台服务器上现有的数据是 服务器上的一个子集,所以 Git 并不会抓取数据而是会设置远程跟踪分支 指向 的 分支。 teamoneoriginteamone/masterteamonemaster

远程跟踪分支 `teamone/master`。
Figure 34. 远程跟踪分支 teamone/master

删除分支

删除分支命令:

git branch -d (branchname)

例如我们要删除 testing 分支:

$ git branch
* master
  testing
$ git branch -d testing
Deleted branch testing (was 85fc7e7).
$ git branch
* master

分支合并

一旦某分支有了独立内容,你终究会希望将它合并回到你的主分支。 你可以使用以下命令将任何分支合并到当前分支中去:

git merge
$ git branch
* master
  newtest
$ ls
README        test.txt
$ git merge newtest
Updating 3e92c19..c1501a2
Fast-forward
 runoob.php | 0
 test.txt   | 1 -
 2 files changed, 1 deletion(-)
 create mode 100644 runoob.php
 delete mode 100644 test.txt
$ ls
README        runoob.php

以上实例中我们将 newtest 分支合并到主分支去,test.txt 文件被删除。

合并完后就可以删除分支:

$ git branch -d newtest
Deleted branch newtest (was c1501a2).

删除后, 就只剩下 master 分支了:

$ git branch
* master

合并冲突

合并并不仅仅是简单的文件添加、移除的操作,Git 也会合并修改。

$ git branch
* master
$ cat runoob.php

首先,我们创建一个叫做 change_site 的分支,切换过去,我们将 runoob.php 内容改为:

<?php
echo 'runoob';
?>

创建 change_site 分支:

$ git checkout -b change_site
Switched to a new branch 'change_site'
$ vim runoob.php
$ head -3 runoob.php
<?php
echo 'runoob';
?>
$ git commit -am 'changed the runoob.php'
[change_site 7774248] changed the runoob.php
 1 file changed, 3 insertions(+)

将修改的内容提交到 change_site 分支中。 现在,假如切换回 master 分支我们可以看内容恢复到我们修改前的(空文件,没有代码),我们再次修改 runoob.php 文件。

$ git checkout master
Switched to branch 'master'
$ cat runoob.php
$ vim runoob.php    # 修改内容如下
$ cat runoob.php
<?php
echo 1;
?>
$ git diff
diff --git a/runoob.php b/runoob.php
index e69de29..ac60739 100644
--- a/runoob.php
+++ b/runoob.php
@@ -0,0 +1,3 @@
+<?php
+echo 1;
+?>
$ git commit -am '修改代码'
[master c68142b] 修改代码
 1 file changed, 3 insertions(+)

现在这些改变已经记录到我的 "master" 分支了。接下来我们将 "change_site" 分支合并过来。

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

$ cat runoob.php     # 打开文件,看到冲突内容
<?php
<<<<<<< HEAD
echo 1;
=======
echo 'runoob';
>>>>>>> change_site
?>

我们将前一个分支合并到 master 分支,一个合并冲突就出现了,接下来我们需要手动去修改它。

$ vim runoob.php 
$ cat runoob.php
<?php
echo 1;
echo 'runoob';
?>
$ git diff
diff --cc runoob.php
index ac60739,b63d7d7..0000000
--- a/runoob.php
+++ b/runoob.php
@@@ -1,3 -1,3 +1,4 @@@
  <?php
 +echo 1;
+ echo 'runoob';
  ?>

在 Git 中,我们可以用 git add 要告诉 Git 文件冲突已经解决

$ git status -s
UU runoob.php
$ git add runoob.php
$ git status -s
M  runoob.php
$ git commit
[master 88afe0e] Merge branch 'change_site'

现在我们成功解决了合并中的冲突,并提交了结果。