(三)Git 学习之分支操作

一、分支简介

1.1 Git 分支初探

几乎所有的版本控制系统都会以某种形式支持分支。 使用分支意味着你可以把你的工作从开发主线上分离开来,以免影响开发主线。

假设你准备开发一个新功能,但是需要两周时间才能完成:

  1. 第一周你写了 50% 的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。
  2. 如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。

现在有了分支,就不用怕了~

你创建了一个属于你自己的分支,别人看不到,还可以继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。

每次提交,Git 都把它们串成一条时间线,这条时间线就是一个分支。当只有一条时间线时,这个分支就叫做主分支,即 master 分支。HEAD 严格来说不是指向提交,而是指向 master,master 才是指向提交的,所以,HEAD 指向的就是当前分支。

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

每次提交,master 分支都会向前移动一步,这样,随着你不断提交,master 分支的线也越来越长。

image-20230614214454335

当我们创建一个新的分支,例如 dev 时,Git 就会新建一个叫 dev 的指针,指向 master 相同的提交,再把 HEAD 指向 dev,就表示当前分支在 dev 上:

image-20230614214516941

Git 创建一个分支很快,因为除了增加一个 dev 指针,改改 HEAD 的指向外,工作区的文件都没有任何变化。

不过,从现在开始,对工作区的修改和提交就是针对 dev 分支了。比如新提交一次后,dev 指针往前移动一步,而 master 指针不变:

image-20230614214554968

假如我们在 dev 分支上的工作完成了,就可以把 dev 分支合并到 master 分支上了:

image-20230614214612380

所以 Git 合并分支也很快,就改改指针的指向,工作区内容也不变!合并完分支后,甚至可以删除 dev 分支,删除 dev 分支就是把 dev 指针给删掉,删掉后,我们就剩下了一条 master 分支了:

image-20230614214653758

1.2 深入 Git 处理分支方式

为了真正理解 Git 处理分支的方式,我们需要回顾一下 Git 是如何保存数据的。

我们知道,Git 保存的不是文件的变化或者差异,而是一系列不同时刻的快照,在进行提交操作时,Git 会保存一个提交对象。

知道了 Git 保存数据的方式后,我们可以很自然的想到:该提交对象会包含一个指向暂存区内容快照的指针。 但不仅仅是这样,该提交对象还包含了作者的姓名和邮箱、提交时输入的信息以及指向它的父对象的指针。

首次提交产生的提交对象没有父对象,普通提交操作产生的提交对象有一个父对象,而由多个分支合并(merge)产生的提交对象有多个父对象。

为了更加形象地说明,我们假设现在有一个工作目录,里面包含了三个将要被暂存和提交的文件。

我们通过 $ git add 命令将这三个文件添加到暂存区:暂存操作会为每一个文件计算 SHA-1 校验和,然后把当前版本的文件快照保存到 Git 仓库中 (Git 使用 blob 对象来保存它们,在 .git/object 目录下),最终将校验和加入到暂存区域等待提交。

当使用 $ git commit 命令进行提交时,Git 会先计算每一个子目录的校验和(本例中只有项目根目录),然后在 Git 仓库中将这些校验和保存为树对象。随后,Git 便会创建一个提交对象,它除了包含上面提到的那些信息外,还包含指向这个树对象的指针。如此一来,Git 就可以在需要的时候重现此次保存的快照了。

现在,Git 仓库中有五个对象:三个 blob 对象(保存着文件快照)、一个树对象(记录着目录结构和 blob 对象的索引),以及一个提交对象(包含着指向树对象的指针和所有提交信息),如下图所示:

image-20230614215656539

做些修改后再次提交,那么这次产生的提交对象会包含一个指向上次提交对象(父对象)的指针。如此操作两次,你将会得到三个提交对象:

image-20230614220039608

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

image-20230614220618016

二、分支操作

2.1 分支创建

Git 是怎么创建新分支的呢? 很简单,你只需要使用 $ git branch <branch> 命令,它会为你创建了一个可以移动的新的指针,比如,创建一个 testing 分支:

$ git branch testing
  • 这会在当前所在的提交对象上创建一个指针

image-20230614220806726

那么,Git 又是怎么知道当前在哪一个分支上呢? 也很简单,它有一个名为 HEAD 的特殊指针。在 Git 中,HEAD 是一个指针,指向当前所在的本地分支(译注:将 HEAD 想象为当前分支的别名)。

在本例中,你仍然在 master 分支上,因为 git branch 命令仅仅创建一个新分支,并不会自动切换到新分支中去。

image-20230614220928682

你可以简单地使用 $ git log --decorate 命令查看各个分支当前所指的对象:

$ git log --decorate --oneline 
f30ab (HEAD -> master, testing) add feature #32 - ability to add new formats to the central interface
34ac2 Fixed bug #1328 - stack overflow under certain conditions
98ca9 The initial commit of my project
  • 正如你所见,当前 master 和 testing 分支均指向校验和以 f30ab 开头的提交对象

2.2 分支切换

要切换到一个已存在的分支,你需要使用 $ git checkout 命令,如我们可以使用如下指令切换到新创建的 testing 分支:

$ git checkout testing

这样 HEAD 就指向 testing 分支了:

image-20230614221433445

那么,这样的实现方式会给我们带来什么好处呢? 现在不妨再提交一次:

$ vim test.rb
$ git commit -a -m 'made a change'

image-20230616204542150

如图所示,你的 testing 分支向前移动了,但是 master 分支却没有,它仍然指向运行 git checkout 时所指的对象。 这就有意思了,现在我们切换回 master 分支看看:

$ git checkout master

image-20230616204833388

这条命令做了两件事:

  1. 一是使 HEAD 指回 master 分支;
  2. 二是将工作目录恢复成 master 分支所指向的快照内容

也就是说,你现在做修改的话,项目将始于一个较旧的版本。 本质上来讲,这就是忽略 testing 分支所做的修改,以便于向另一个方向进行开发。

分支切换会改变你工作目录中的文件:在切换分支时,一定要注意你工作目录里的文件会被改变。 如果是切换到一个较旧的分支,你的工作目录会恢复到该分支最后一次提交时的样子。 如果 Git 不能干净利落地完成这个任务,它将禁止切换分支。

我们不妨再稍微做些修改并提交:

$ vim test.rb
$ git commit -a -m 'made other changes'

现在,这个项目的提交历史已经产生了分叉,因为刚才你创建了一个新分支,并切换过去进行了一些工作,随后又切换回 master 分支进行了另外一些工作。 :

image-20230616205124088

上述两次改动针对的是不同的分支:你可以在不同分支间不断地来回切换和工作,并在时机成熟时将它们合并起来,而所有这些工作,你需要的命令只有 branch、checkout 和 commit。

你可以简单地使用 $ git log --oneline --decorate --graph --all 指令输出你的提交历史、各个分支的指向以及项目的分支分叉情况:

$ git log --oneline --decorate --graph --all
* c2b9e (HEAD, master) made other changes
| * 87ab2 (testing) made a change
|/
* f30ab add feature #32 - ability to add new formats to the
* 34ac2 fixed bug #1328 - stack overflow under certain conditions
* 98ca9 initial commit of my project

由于 Git 的分支实质上仅是包含所指对象校验和(长度为 40 的 SHA-1 值字符串)的文件,所以它的创建和销毁都异常高效。 创建一个新分支就相当于往一个文件中写入 41 个字节(40 个字符和 1 个换行符),如此的简单能不快吗?

通常我们想在创建一个新分支后立即切换过去,这可以用 $ git checkout -b <NewBranchName> 一条命令搞定。

2.3 分支合并

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

2.3.1 主线生成

首先让我们新建一个仓库,并在仓库中新建一个 main.c,作为当前的开发主线:

$ git init
Initialized empty Git repository in E:/gittest/.git/

$ git add main.c
$ git commit -m "[master] 生成主线"

编辑 main.c,添加如下内容:

#include <stdio.h>

int add(int a, int b)   // 加法运算
{
    return a + b;
}
int div(int a, int b)   // 除法运算
{
    return a / b;
}

int main()
{
    int a, b;
    scanf("%d%d", &a, &b);

    printf("a + b = %d\n", add(a, b));
    printf("a ÷ b = %d\n", div(a, b));

    return 0;
}

并将修改上库

$ git commit -am "[master] 新增加法和除法运算"

此时,Git 的分支情况如下图所示:

Git 内部原理.drawio

  • 其中,C 是 commit 的缩写,C1 表示第一次提交

2.3.2 需求开发创建 feature 分支

某一天,你接到了一个新的需求:新增减法、乘法和乘方的运算,那么你可以按照如下方式处理:

  1. 为这个需求任务新建一个分支(不妨取名为 feature),并在其中实现它
  2. 在测试通过之后,切换回主线分支,然后合并这个需求分支

首先,我们基于当前的主线分支新建一个 feature 分支,用于需求开发:

$ git checkout -b feature
Switched to a new branch 'feature'

使用 -b 参数的 git checkout 指令,可以让你在新建分支的同时切换过去,它是下面两条命令的简写:

$ git branch feature
$ git checkout feature

现在,Git 的分支情况如下图所示:

Git 内部原理.drawio

2.3.3 基于 feature 分支开发

你现在在 feature 分支上工作,并且做了一些提交:

$ vim main.c
$ git commit -am "[feature] 新增减法运算"

$ vim main.c
$ git commit -am "[feature] 新增乘法运算"

在此过程中,feature 分支在不断的向前推进:

Git 内部原理.drawio

2.3.4 紧急任务中断开发

正在此时,你突然接到一个电话说有个很严重的问题需要紧急修补(客户在运行 div 函数时,程序突然崩溃了)。有了 Git 的帮助,你不必把这个紧急问题和 feature 的修改混在一起,你也不需要花大力气来还原关于 feature 的修改,然后再添加关于这个紧急问题的修改。你所要做的仅仅是切换回 master 分支。

但是,在你这么做之前,要留意你的工作目录和暂存区里那些还没有被提交的修改, 它可能会和你即将检出的分支产生冲突从而阻止 Git 切换到该分支。 最好的方法是,在你切换分支之前,保持工作目录为一个干净的状态。有一些方法可以绕过这个问题,即暂存(stashing)和修补提交(commit amending)),当前,这不是本次的重点。

现在,我们假设你在 feature 分支做的的工作全部提交了,这时你可以切换回 master 分支了:

$ git checkout master
Switched to branch 'master'

这个时候,你的工作目录和你在开始 feature 之前是一模一样的,现在你可以专心修复紧急问题了。请牢记,当你切换分支的时候,Git 会重置你的工作目录,使其看起来像是回到了你在那个分支上最后一次提交的样子。

Git 会自动添加、删除、修改文件,以确保此时你的工作目录和这个分支最后一次提交时的样子一模一样。

接下来,你要修复这个紧急问题,我们来建立一个 bugfix 分支,在该分支上工作直到问题解决:

  1. 首先,你排查出是由于客户传入了 b == 0,导致在做除法运算时,程序崩溃

  2. 接着你在 div 函数中新增参数校验的逻辑:

    int div(int a, int b)   // 除法运算
    {
        if (b == 0) // [bugfix] 规避除数为 0 的情况
        {
            puts("b can't be set to zero.");
            return 0;
        }
        return a / b;
    }
    
  3. 修改完后,提交修改

    $ git commit -am "[bugfix] 规避除数为 0 的情况"
    

现在,你的 Git 仓库变成了这个样子:

Git 内部原理.drawio

经过测试,你发现修改有效,然后将 bugfix 分支的修改合并回你的 master 分支上。你可以使用 git merge 命令来达到上述目的:

$ git checkout master			# 检出到主线分支(master)
Switched to branch 'master'

$ git merge bugfix				# 将 bugfix 分支合并到当前分支(master)
Updating 9170ce0..9c1b97b
Fast-forward
 main.c | 5 +++++
 1 file changed, 5 insertions(+)

在合并的时候,你应该注意到了 Fast-forward(快进)这个词。 由于你想要合并的分支 bugfix 所指向的提交 C5 是 master 所指提交 C2 的直接后继, 因此 Git 会直接将 master 指针向前移动。换句话说,当你试图合并两个分支时, 如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候, 只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧,这就叫做 Fast-forward。

合入后的分支情况如下所示:

Git 内部原理.drawio

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

2.3.5 问题解决重拾开发

当这个紧急问题的解决方案发布之后,你准备回到被打断之前时的工作中。然而,你应该先删除 bugfix 分支,因为你已经不再需要它了(master 分支已经指向了同一个位置)。 你可以使用 $ git branch -d 命令来删除 bugfix 分支:

$ git branch -d bugfix
Deleted branch bugfix (was 9c1b97b).

现在你可以切换回你正在工作的分支继续你的工作:

$ git checkout feature
Switched to branch 'feature'

$ vim main.c
$ git commit -am "[feature] 新增乘方运算"

Git 内部原理.drawio

2.3.6 开发完成合入主线

假设你已经完成了开发工作,并且打算将你的工作合入 master 分支。为此,你需要合并 feature 分支到 master 分支,这和之前你合并 bugfix 分支所做的工作差不多。 你只需要检出到你想合并入的分支,然后运行 git merge 命令:

$ git checkout master
Switched to branch 'master'

$ git merge feature
Auto-merging main.c
Merge made by the 'ort' strategy.
 main.c | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

这和你之前合并 bugfix 分支的时候看起来有一点不一样。 在这种情况下,你的开发历史从一个更早的地方(C2)开始分叉开来(diverged)。因为,master 分支所在提交并不是 feature 分支所在提交的直接祖先,Git 不得不做一些额外的工作。

出现这种情况的时候,Git 会使用两个分支的末端所指的快照(C5 和 C6)以及这两个分支的公共祖先(C2),做一个简单的三方合并:

Git 内部原理.drawio

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

Git 内部原理.drawio

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

$ git branch -d feature
Deleted branch feature (was 8d47290).

2.4 分支管理

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

$ 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

这里显示了 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 选项强制删除它。

上面描述的选项 --merged 和 --no-merged 会在没有给定提交或分支名作为参数时, 分别列出已合并或未合并到「当前分支」的分支列表。但你也可以提供一个附加的参数来查看其它分支的合并状态而不必检出它们。例如,尚未合并到 master 分支的分支都有哪些?

$ git branch --no-merged master
topicA
featureB

三、远程分支

3.1 概念介绍

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

远程跟踪分支是远程分支状态的引用,它们是你无法移动的本地引用。一旦你进行了网络通信, Git 就会为你移动它们以精确反映远程仓库的状态。请将它们看做书签, 这样可以提醒你该分支在远程仓库中的位置就是你最后一次连接到它们的位置。

它们以 / 的形式命名。

3.2 克隆远程分支

假设你的网络里有一个在 git@gitee.com:melephant/remote-branch-test.git(以下简写为 git@gitee.com) 的 Git 服务器:

Git 远程分支.drawio

  • 该远程分支共有三次提交

如果你从这里克隆,$ git clone 命令会为你自动将其命名为 origin,拉取它的所有数据, 创建一个指向它的 master 分支的指针,并且在本地将其命名为 origin/master。Git 也会给你一个与 origin 的 master 分支在指向同一个地方的本地 master 分支,这样你就有工作的基础,如下图所示为克隆之后的服务器与本地仓库:

Git 远程分支.drawio

Notes:

  1. 远程仓库名字 origin 与分支名字 master 一样,在 Git 中并没有任何特别的含义。
  2. master 是当你运行 git init 时默认的起始分支名字,而 origin 则是当你运行 git clone 时默认的远程仓库名字。
  3. 如果你运行 $ git clone -o booyah,那么你默认的远程分支名字将会是 booyah/master。

3.3 同步远程仓库的修改

如果你在本地的 master 分支做了一些工作,在同一段时间内有其他人推送提交到 git@gitee.com,并且更新了它的 master 分支,这就是说你们的提交历史已走向不同的方向。

但即便这样,只要你不与 origin 服务器连接,你的 origin/master 指针就不会移动:

Git 远程分支.drawio

如果你想要同步远程仓库的数据,那么你可以运行 $ git fetch origin 命令:

  1. 这个命令会查找 origin 是哪一个服务器(在本例中是 git@gitee.com);
  2. 并从中抓取本地没有的数据,更新本地数据库;
  3. 并移动 origin/master 指针到更新之后的位置。

如下图所示,使用 git fetch 更新你的远程跟踪分支:

Git 远程分支.drawio

为了演示有多个远程仓库与远程分支的情况,我们假定你有另一个内部 Git 服务器,仅服务于你的某个开发团队(这个服务器位于 git@gitee.com:melephant/remote-branch-test-tmp.git)。

你可以通过运行 $ git remote add <name> <url> 命令添加一个新的远程仓库引用到当前的项目中:

Git 远程分支.drawio

  • 添加另一个远程仓库,并将这个远程仓库命名为 tmp,将其作为完整 URL(git@gitee...test-tmp.git) 的缩写

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

Git 远程分支.drawio

  • 远程跟踪分支 tmp/master

3.4 推送本地修改至远程分支

当你想要公开你的修改时,需要将其推送到有写入权限的远程仓库上。本地的分支并不会自动与远程仓库同步,你必须显式地推送想要分享的分支,这样,你就可以把不愿意分享的内容放到私人分支上,而将需要和别人协作的内容推送到公开分支。

我们以如下图所示的状态为例演示推送流程:

image-20230628202525293

3.4.1 git push origin master

  1. 通过 $ git fetch origin 同步远程仓库的修改
  2. 通过 $ git merge origin/master 将远程仓库中有而本地仓库中没有的 C6 和 C7 合并到本地 master
  3. 通过 $ git push origin master 将本地 master 分支推送至远程 master 分支

Git 远程分支.drawio

执行完 git push 后,远程仓库就变成了这样:

Git 远程分支.drawio

其实 $ git push origin master 的完整写法为 $ git push origin master:master:表示推送本地的 master 分支(冒号前)来更新远程仓库上的 master 分支(冒号后)。

3.4.2 git push origin master:newbranch

如果你并不想让远程仓库上的分支叫做 master,可以运行 $ git push origin master:newbranch 来将本地的 master 分支推送到远程仓库的 newbranch 分支,如果远程仓库没有,则会创建,如下图所示:

Git 远程分支.drawio

当其他协作者从服务器上抓取数据时,他们会在本地生成一个远程分支 origin/newbranch,指向服务器的 newbranch分支的引用:

$ git fetch origin
remote: Enumerating objects: 8, done.
remote: Counting objects: 100% (8/8), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 6 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (6/6), 475 bytes | 19.00 KiB/s, done.
From gitee.com:melephant/remote-branch-test
 * [new branch]      newbranch  -> origin/newbranch

要特别注意的一点是,当抓取到新的远程跟踪分支时,本地不会自动生成一份可编辑的副本(拷贝)。 换一句话说,这种情况下,不会有一个新的 newbranch 分支,只有一个不可以修改的 origin/newbranch 指针:

$ git log --oneline --decorate --graph --all
* 57e12a4 (HEAD -> master, origin/master) seventh commit
* c36e153 sixth commit
| * 2ce322b (origin/newbranch) fifthly commit
| * a690b40 fourth commit
|/
* 435cce1 third commit
* f4307b3 second commit
* c08d3c1 first commit

我们可以通过运行 $ git merge origin/newbranch 将这些 newbranch 分支上的工作合并到当前所在的分支,或者将其建立在远程跟踪分支之上:

$ git checkout -b newbranch origin/newbranch
Switched to a new branch 'newbranch'
branch 'newbranch' set up to track 'origin/newbranch'.

$ git log --oneline --decorate --graph --all
* 57e12a4 (origin/master, master) seventh commit
* c36e153 sixth commit
| * 2ce322b (HEAD -> newbranch, origin/newbranch) fifthly commit
| * a690b40 fourth commit
|/
* 435cce1 third commit
* f4307b3 second commit
* c08d3c1 first commit
  • 这会给你一个用于工作的本地分支,并且起点位于 origin/newbranch。

3.5 跟踪分支

从一个远程跟踪分支检出一个本地分支会自动创建所谓的「跟踪分支」,它跟踪的分支叫做「上游分支」。跟踪分支是与远程分支有着直接关系的本地分支。

如果在一个跟踪分支上输入 git pull,Git 能自动地识别去哪个服务器上抓取、合并到哪个分支。

当克隆一个仓库时,它通常会自动地创建一个跟踪 origin/master 的 master 分支。 然而,如果你愿意的话,可以设置其他的跟踪分支,或是一个在其他远程仓库上的跟踪分支,又或者不跟踪 master 分支。

最简单的方法就是像之前看到的那样,运行 $ git checkout -b <branch> <remote>/<branch>,这是一个十分常用
的操作,所以 Git 提供了 --track 快捷方式:

$ git checkout --track origin/newbranch
Switched to a new branch 'newbranch'
branch 'newbranch' set up to track 'origin/newbranch'.

由于这个操作太常用了,该捷径本身还有一个捷径。如果你尝试检出的分支 newbranch 不存在且 origin 刚好只有一个名字
与之匹配的远程分支 origin/newbranch,那么 Git 就会为你创建一个跟踪分支:

$ git checkout newbranch
Switched to a new branch 'newbranch'
branch 'newbranch' set up to track 'origin/newbranch'.

如果想要将本地分支与远程分支设置为不同的名字,你可以轻松地使用上一个命令增加一个不同名字的本地分支:

$ git checkout -b nb origin/newbranch
Switched to a new branch 'nb'
branch 'nb' set up to track 'origin/newbranch'

上游快捷方式:当设置好跟踪分支后,可以通过简写 @{upstream}@{u} 来引用它的上游分支。 所以,如果当前在 master 分支、并且它正在跟踪 origin/master 时,可以使用 $ git merge @{u} 来代替 $ git merge origin/master

3.6 查看所有跟踪分支

如果想要查看设置的所有跟踪分支,可以使用 $ git branch -vv,这会将所有的本地分支列出来并且包
含更多的信息:

  1. 如每一个分支正在跟踪哪个远程分支
  2. 远程分支与本地分支是否是领先、落后或是都有
$ 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

3.7 拉取

当使用 git fetch 命令从服务器上抓取本地没有的数据时,它并不会修改工作目录中的内容,它只会获取数据然后让你自己合并。

然而,有一个叫做 $ git pull 的命令,在大多数情况下它的含义是一个 git fetch 紧接着一个 git merge 命令。

如之前章节中演示的设置好的跟踪分支,不管它是显式地设置还是通过 git clone 或 git checkout 命令为你创建的,git pull 都会查找当前分支所跟踪的服务器与分支, 从服务器上抓取数据然后尝试合并入那个远程分支。

由于 git pull 的用法经常令人困惑,所以通常单独显式地使用 fetch 与 merge 命令会更好一些。

3.8 删除远程分支

假设你已经通过远程分支做完所有的工作了,也就是说你和你的协作者已经完成了一个特性,并且将其合并到了远程仓库的 master 分支上(或任何其他稳定代码分支),你可以运行带有 --delete 选项的 git push 命令来删除一个远程分支。

如果想要删除服务器上的 serverfix 分支,运行下面的命令:

$ git push origin --delete serverfix
To https://github.com/schacon/simplegit
- [deleted] serverfix

基本上这个命令做的只是从服务器上移除这个指针。Git 服务器通常会保留数据一段时间直到垃圾回收运行,所以如果不小心删除掉了,通常是很容易恢复的。

posted @ 2023-07-12 20:33  MElephant  阅读(222)  评论(0编辑  收藏  举报