Git12-远程版本库

  • 克隆是版本库的副本,包含所有原始对象。因此,每个克隆都是独立、自治的版本库,与原始版本库是真正对称、地位相同的。
  • 克隆允许每个开发人员可以在本地独立地工作,不需要中心版本库,投票或者锁。归根结底,克隆使Git易于扩展,并允许地理上分离的很多贡献者一起协作。
  • 从本质上讲,在下述情况下,分离的版本库是相当有用的:
    • 开发人员独立自主工作。
    • 开发人员被广域网分离。在相同地区的一群开发人员可以共享一个本地版本库来积累局部变化。
    • 一个项目预计在不同的发展线上有显著差异。虽然前面几章展示的正常分支和合并机制可以处理任何数量的独立开发,但是产生的复杂性带来的麻烦可能会比它的价值更多、相反,独立的开发线可以使用单独的版本库,到适当的时候再进行合并。
  • 共享代码有两步
    • (1)克隆版本库
    • (2)对版本库之间进行关联,为数据交换建立路径。Git通过远程版本库为这些版本库建立连接。
  • 远程版本库(remote)是一个引用或句柄,通过文件系统或网络指向另一个版本库。可以在版本库中定义任意数量的远程版本库,从而创建共享版本库的阶梯网络。
  • 一旦远程版本库建立,Git就可以使用推模式或拉模式在版本库之间传输数据。例如,从原始版本库转移提交数据到克隆版本库,以保持克隆版本库处于同步状态。或者从克隆版本库传输数据到原始版本库。再或者设置两个版本库进行双向信息交换。
  • 远程追踪分支(remote-tracking branch):可以跟踪其他版本库中的数据。版本库中的每个远程追踪分支都作为远程版本库中特定分支的一个代理。
  • 本地追踪分支(local-tracking branch):可以集成本地修改与远程追踪分支对应的远程修改。
  • 发布版本库(publishing a repository):将你的版本库提供给他人。

1、版本库概念

1.1、裸版本库和开发版本库

  • Git版本库有两种:(bare)版本库和开发(非裸)(development,nonbare)版本库。
  • 开发版本库有工作目录,能用于常规的日常开发。它有当前分支的概念,并在工作目录中提供检出当前分支的副本。
  • 裸版本库没有工作目录,不能用于常规的的日常开发。没有检出分支的概念。裸版本库可以简单地看做.gir目录的内容。换句话说,不应该在裸版本库中进行提交操作
  • 裸版本库是作为协作开发的权威版本库。其他开发人员从裸版本库中克隆(clone)和抓取(fetch),并推送(push)更新。
  • 默认情况下,在开发版本库中可以使用引用日志(reflog)(对引用的修改记录),但在裸版本库中不行。
  • 裸版本库中不能创建远程追踪分支。如果你要创建一个版本库供开发人员推送修改,那么它应该是裸版本库。实际上,一个更通用的最佳实践是:发布的版本库应该是裸版本库。

示例1:

1、创建开发版本库和裸版本库

//初始化一个新的开发版本库
]# git init NONBARE_DIR_NAME
Initialized empty Git repository in /root/NONBARE_DIR_NAME/.git/

//初始化一个新的裸版本库
]# git init --bare NBARE_DIR_NAME
Initialized empty Git repository in /root/NBARE_DIR_NAME/

2、对比开发版本库和裸版本库的目录结构的差异

1.2、版本库克隆

  • git clone命令会创建一个新的Git版本库,基于文件系统或网络地址指定的原始版本库。Git不会复制原始版本库的所有信息,只跟原始版本库相关的信息会被忽略,如远程追踪分支。
//克隆版本库,得到一个开发版本库
git clone

//克隆版本库,得到一个裸版本库
git clone --bare
  • 执行git clone命令时:
    • 原始版本库中存储在refs/heads/下的本地开发分支,会成为新的克隆版本库中refs/remotes/下的远程追踪分支。
    • 原始版本库中refs/remotes/下的远程追踪分支不会被克隆。(克隆不需要知道有什么上游版本库被追踪。)
  • 原始版本库中的标签会被复制到克隆版本库中。但版本库的特定信息,如原始版本库中的钩子(hooks)、配置文件、引用日志(reflog)和储藏(stash)都不会被复制。
  • 默认情况下,每个新的克隆版本库都通过一个称为origin的远程版本库,建立一个链接指回它的父版本库。但是,原始版本库并不知道任何克隆版本库,也不维护指向克隆版本库的链接。这是一个单向关系。
    • origin这个名称并没有什么特别之处。如果你不想使用它,可以在克隆操作过程中通过--origin名称选项指定替代名。
  • Git使用默认的fetch refspec配置,从原始版本库中抓取哪些变更更新本地版本库。
    • 建立这个refspec预示你要通过在这种情况下,远程版本库的分支在克隆版本库中是可用的,只要在分支名前面加上origin/
fetch=+refs/heads/*:refs/remotes/origin/*

1.3、远程版本库

  • 你目前在工作中使用的版本库称为本地(local)或当前(current)版本库,你交换文件用的版本库称为远程版本库(remote repository)。但是,后者可能有点用词不当,因为该版本库可能不一定是物理上远程的,或者甚至不一定在不同机器上,它可能只是存放在本地文件系统中的另一个版本库。
  • Git使用远程追踪分支来引用另一个版本库(即远程版本库)。远程版本库为版本库提供了更友好的名字,可以代替远程版本库实际的URL。一个远程版本库还形成了该版本库远程追踪分支名称的基本部分。
  • 使用git remote命令创建、删除、操作和查看远程版本库。你引入的所有远程版本库都记录在.git/config文件中,可以用git config来操作。
  • 与远程版本库有关的其他常见的Git命令有:
    • git fetch:从远程版本库抓取对象及其相关的元数据。
    • git pull:跟git fetch类似,但合并修改到相应的本地分支。
    • git push:转移对象及其相关的元数据到远程版本库。
    • git ls-remote:显示一个给定的远程版本库(在上游服务器上)的引用列表。这条命令间接地回答了问题:“有更新可用吗?”

1.4、追踪分支

  • 一旦克隆一个版本库,就可以与源版本库保持同步,即使已经进行了本地提交并创建了本地分支。
  • 为了不同功能的分支,建立了不同的命名空间。虽然仍然认为本地版本库中的任何分支是本地分支,但它们可以进一步分为不同的类别。
    • 远程追踪分支(remote-tracking branch)与远程版本库相关联,专门用来追踪远程版本库中每个分支的变化。
      • 远程追踪分支保留在refs/remotes/命名空间中
    • 本地追踪分支(local-trackingbranch)与远程追踪分支相配对。它是一种集成分支,用于收集本地开发和远程追踪分支中的变更。
    • 任何本地的非追踪分支通常称为特性(topic)或开发(development)分支。
      • 特性分支分支保留在refs/heads/命名空间中
    • 为了完成命名空间,远程分支(remote branch)是一个设在非本地的远程版本库的分支。很可能是远程跟踪分支的上游源。
  • 在克隆操作过程中,Git会创建一个远程跟踪分支,并作为每个特性分支在上游版本库的克隆。远程跟踪分支在专门用于远程克隆的本地版本库中引入一个新的、单独的命名空间。它们不是远程版本库中的分支。本地版本库使用远程跟踪分支来跟踪远程版本库中所做的更改。
  • 由于远程追踪分支都集中到自己的命名空间中,因此你在版本库中创建的分支(特性分支)和那些基于其他远程版本库的分支(远程追踪分支)是可以很容易区分的。
    • 在早期的Git中,独立的命名空间只是约定和最佳实践,旨在帮助防止发生意外的冲突。
    • 在新版本的Git中,独立的命名空间远远超过了约定:你如何使用分支来与上游版本库进行交互是一个不可分割的部分。
  • 在特性分支上可以执行的所有操作都可以在追踪分支上执行。然而,需要遵守一些限制和指导。
  • 因为远程追踪分支专门用于追踪另一个版本库中的变化,所以你应该把它们当作是只读的。不应合并或提交到一个远程追踪分支。这样做会导致你的远程追踪分支变得和远程版本库不同步。更糟糕的是,将来每个从远程版本库的更新都可能需要进行合并,这会使你的克隆越来越难以管理。

2、引用其他版本库

  • 为了协调你的版本库与其他版本库,可以定义一个远程版本库(remote),这里是指存在版本库配置文件中的一个实体名,它是由两个不同的部分组成的。
    • 第一部分以URL的形式指出其他版本库的名称。
    • 第二部分是refspec,指定一个引用(通常表示一个分支)如何从一个版本库的命名空间映射到其他版本库的命名空间。

2.1、引用远程版本库

  • Git支持多种形式的统一资源定位符(Uniform Resource Locator,URL),URL可以用来命名远程版本库。指定访问协议数据的位置或地址。
  • 从技术上讲,Git的URL形式既不是真正的URL,也不是统一资源标志符(Uniform Resource Identifier,URI),因为二者分别不完全符合RFC1738或RFC2396。因此,将这种通用的Git版本库地址称为Git URL。
  • .git/config文件中也使用Git URL

2.1.1、文件系统

  • 正如你所见,Git URL最简单的形式是:指代文件系统上的版本库。可以是本地文件系统,也可以是通过网络文件系统(NFS)挂载到本地的虚拟文件系统。
#本地文件系统
/path/to/repo.git

#网络文件系统
file:///path/to/repo.git
  • 虽然这两种格式基本上是相同的,但是两者之间有一个微妙而重要的区别。
    • 前者使用文件系统中的硬链接来直接共享当前版本库和远程版本库之间相同的对象.
    • 后者则复制对象,而不是直接共享它们。为了避免与共享版本库相关的问题,建议使用file://的形式。

2.1.2、Git原生协议

  • Git原生协议(Git native protocol)是Git内部用来传输数据的自定义协议。用于通过网络获取远程版本库。
#Git原生协议
git://example.com/path/to/repo.git
git://example.com/~user/path/to/repo.git
  • git-daemon用这些格式来发布匿名读取的版本库。可以使用这些URL形式来克隆和抓取。
  • 使用这些格式的客户端不用经过身份验证,不要求输入密码。因此,~user格式可以用来指代用户的主目录,仅有一个没扩展的裸~是没用的;没有经过身份验证的用户不可以使用主目录。此外,只有当服务器端用--user-path选项时~user格式才有效。
  • 对于经过身份验证的安全连接,Git原生协议可以通过Secure Shell(SSH)连接使用如下URL模板进行隧道封装
    • 第三种形式允许存在两个不同的用户名。第一个是验证会话的用户、第二个是访问主目录的用户。
ssh://[user@]example.com[:port]/path/to/repo.git
ssh://[user@]example.com/path/to/repo.git
ssh://[user@]example.com/~user2/path/to/repo.git
ssh://[user@]example.com/~/path/to/repo.git

2.1.3、类似scp语法的URL

  • Git还支持与scp语法类似的URL形式。这和SSH形式相同,但无法指定端口参数。
[user@]example.com:/path/to/repo.git
~[user@]example.com:~user/path/to/repo.git
[user@]example.com:path/to/repo.git

2.1.4、HTTP和HTTPS形式的URL

  • HTTP和HTTPS形式的URL,在1.6.6版本后,它们都发生了一些重要变化。
http://example.com/path/to/repo.git
https://example.com/path/to/repo.git
  • 在Git 1.6.6版本之前,无论是HTTP还是HTTPS协议,效率都不如Git原生协议。在版本1.6.6中,HTTP协议得到显著改善,与Git原生协议效率基本相同。Git文献把这个实现称为是“智能”的,与之前“愚蠢”的实现相对比。
  • 注意,大多数企业的防火墙允许HTTP的80端口和HTTPS的443端口保持打开状态,但默认的Git 9418端口通常是关闭的,可能需要经过开会决定才能打开。
  • HTTP和HTTPS形式的URL在流行的Git托管网站(如GitHub)上备受青睐。

2.1.5、rsync协议的URL

  • Rsyne协议可以按如下方式指定:
rsync://example.com/path/to/repo.git
  • 不鼓励使用Rsync,因为它不如其他选项。如果一定要用,那也应仅用于初始化克隆,在此刻远程版本库的引用应改为其他机制。随后的更新如果继续使用Rsyne协议,可能会导致丢失在本地创建的数据。

2.2、refspec

  • 引用(ref或reference)可以在版本库历史中指定一个特定的提交。
    • 通常引用是一个分支名
    • refspec把远程版本库中的分支名映射到本地版本库中的分支名。
  • 因为refspec必须同时指定本地版本库和远程版本库中的分支,所以完整的分支名在refspec中是很常见的,通常也是必需的。在refspec中,你通常会看到特性分支名的前缀refs/heads/,远程追踪分支名的前缀refs/remotes/。
  • refspec语法
    • 主要由源引用(source ref)、冒号(:)和目标引用(destination ref)组成。还可以在前面加上一个可选的加号(+),表示不会在传输过程中进行正常的快进安全检查。此外,星号(*)允许用通配符匹配分支名。
[+]source:destination
  • 在某些应用中,源引用是可选的;在另一些应用中,冒号和目标引用是可选的。
  • refspec在git fetch和git push中都使用。使用refspec的窍门是要了解它指定的数据流。refspec本身始终是“源:目标”,但源和目标依赖于正在执行的Git操作。此关系总结于表12-1中。

  • git fetch命令使用refspec,如
    • 将远程版本库的命名空间refs/heads/中的所有源分支(i)映射到本地版本库,使用由远程版本库名来构造名字(ii),并放在refs/remotes/remote命名空间中。
    • 因为有星号,所以refspee适用于远程版本库的refs/heads/*中的多个分支。就是这个规范导致远程版本库的特性分支作为远程追踪分支,被映射到你的版本库的命名空间并将它们分成基于远程版本库名的子名。
+refs/heads/*:refs/remotes/remote/*
  • 虽然不是强制性的,但惯例和最佳实践是将给定的远程版本库分支放在refs/remotes/remote/*下。
  • 使用git show-ref列出当前版本库中的引用。使用git ls-remote列出远程版本库的引用
  • git push命令通常要提供并发布在本地特性分支上的变更。在你上传变更后,为了让其他人在远程版本库中找到你的变更,你所做的更改必须出现在该版本库的特性分支中
  • git push命令使用refspec,如
    • 从本地版本库中,将源命名空间refs/heads/下发现的所有分支名,放在远程版本库的目标命名空间refs/heads/下的匹配分支中,使用相似的名字来命名。
    • 第一个refs/heads/指的是你的本地版本库(因为你执行了一次推送),而第二个指的是远程版本库。星号确保所有分支都复制。
+refs/heads/*:refs/heads/*
  • 如果命令中没有明确指定的远程版本库,默认使用origin。如果没有指定refspec,git push会将你的提交发送到远程版本库中你与上游版本库共有的所有分支。
    • 不在上游版本库中的任何本地分支都不会发送到上游;分支必须已经存在,并且名字匹配。
    • 因此,新分支必须显式地用分支名来推送。
//下面两条命令等价
git push origin branch
git push origin branch:refs/heads/branch
  • 在远程版本库的定义中,可以指定多个抓取refspec、多个推送refspec或者它们的组合。

3、使用远程版本库的示例

  • 本节示例中,将在一台物理机器上创建多个版本库。实际上,它们很可能会位于互联网中不同的主机上。因为同样的机制适用于在同一物理机上的多个版本库,也适用其他形式的远程URL规范。
  • Git的一个常用场景。创建一个所有开发人员都认可的权威版本库,虽然在技术上它和其他版本库没什么不同。换句话说,权威在于每个人都一致对待该版本库,而不在于某些技术或安全措施。
  • 都认可的权威副本通常放置在一个特殊的目录中,该目录称为仓库(depot)(当指仓库时避免使用“master”或“版本库”,因为这些惯用语在Git中有其他含义)。
  • 建立仓库通常有很好的理由。例如,你的组织可能想要对某些大型服务器的文件系统进行可靠且专业的备份。你想鼓励你的同事把一切都检入仓库中的主副本,以避免灾难性的损失。仓库将成为所有开发人员的remote origin

示例2-1:

1、创建一个新的开发版本库

//(1)添加用户配置
]# git config --global user.email "hengha@123.com"
]# git config --global user.name "heng ha"

//(2)初始化一个新的开发版本库
]# git init nonbare-example
Initialized empty Git repository in /root/nonbare-example/.git/

2、在开发版本库中创建一个提交

]# cd nonbare-example/

]# echo "Line 1 master" >> file1
]# git add file1
]# git commit -m "add 1 to file1"

3.1、创建权威版本库

  • 权威版本库可以放在文件系统上的任何地方。开发工作不应直接在权威版本库中进行。相反,个人的工作应在本地克隆中完成。
  • 实际上,权威版本库的上游版本库可能已经托管在一些服务器上,也许是GitHub、git.kernel.org或你的个人机器。
  • 通常,裸版本库名有个.git后缀。这不是必需的,但认为这是最佳实践。

示例2-2:

1、创建仓库

]# mkdir depot

2、创建一个权威版本库

  • git clone命令把~/nonbare-example中的Git远程版本库复制到当前工作目录~/depot下。
//(1)进入仓库中
]# cd depot/

//克隆版本库,获得一个裸版本库
]# git clone --bare ~/nonbare-example bare-example.git
Cloning into bare repository 'bare-example.git'...
done.

3、对比开发和裸(权威)版本库的目录结构的差异

  • 可以以看到
    • 原始开发版本库中所有项目文件都被检出到顶层目录,对象库和所有配置文件都位于.git子目录中。
    • 裸版本库没有工作目录,其文件布局更加清爽。
    • 可以把~/depot/bare-example.git这个裸版本库看成权威版本。
    • 因为在克隆操作过程中使用了--bare选项,所以Git没有引入一般默认的origin远程版本库。

4、对比开发和裸(权威)版本库的配置文件

3.2、创建远程追踪分支

  • 现在,有两个基本相同的版本库,唯一区别是开发版本库nonbare-example有工作目录,而克隆的裸版本库bare-example.git没有。因为开发版本库是使用git init创建的,因此它没有origin。事实上,它并没有配置任何远程版本库。

示例2-3:

  • 如果在开发版本库nonbare-example进行开发,然后将变更推送到仓库里的权威版本库bare-example.git中。此时,就需要将开发版本库转换成一个衍生的克隆版本库,即在remotes/origin/中添加远程追踪分支(克隆的开发版本库会自动创建一个origin远程追踪分支)。

1、在开发版本库中创建远程追踪分支

  • 将开发版本库nonbare-example转换成衍生的克隆版本库(即在remotes/origin/中添加远程追踪分支),将裸版本库bare-example.git作为远程版本库。
//(1)进入开发版本库
]# cd nonbare-example/

//(2)将开发版本库转换成衍生的克隆版本库,将裸版本库bare-example.git作为远程版本库
]# git remote add origin /root/depot/bare-example.git

2、对比将开发版本库转换成衍生的克隆版本库前后的配置文件

  • git remote会在配置中增加了一个新的[remote "origin"]段。origin可以是任何字符串,不过按照命名惯例,指向基础版本库的远程追踪分支名为origin
  • remote在当前版本库和远程版本库之间建立连接。在这种情况下,url值为/root/depot/bare-example.git,后缀.git不是必需的,即/root/depot/bare-example和/root/depot/bare-example.git都是有效的。
  • 现在,在这个版本库中,origin可以作为仓库中远程版本库的简写。请注意,按照分支名映射约定还增加了默认的抓取refspec。
  • 包含远程版本库引用(远程追踪分支)的版本库(引用者)和远程版本库(被引用者)之间的关系是不对称的。remote始终从引用者单向指向被引用者。被引用者不知道有其他版本库指向它。换句说:克隆版本库知道它的上游版本库在哪儿,但上游版本库不知道它的克隆版本库在哪儿

3、更新远程追踪分支信息

  • 在开发版本库中建立新的远程追踪分支,代表来自远程版本库的分支。首先,你能看到只有一个分支,即master分支。
//(1)查看所有分支
]# git branch -a
* master

//(2)更新远程追踪分支信息
]# git remote update
Fetching origin
From /root/depot/bare-example
 * [new branch]      master     -> origin/master

//(3)查看所有分支
]# git branch -a
* master
  remotes/origin/master
  • Git在版本库中引入了一个新的分支origin/master。这是一个origin远程版本库中的远程追踪分支。没有人在这个分支上进行开发。相反,它的目的是掌握和跟踪origin远程版本库的master分支中的提交。可以把它看成用来查看远程版本库中提交的本地版本库代理,最终可以用它将那些提交导入你的版本库。
  • 由git remote update命令产生的Updating origin并不意味着远程版本库更新了。相反,它意味着本地版本库中的origin已被基于远程版本库的信息更新了
  • 提示:
    • 普通的git remote update命令会导致在这个版本库中的每个remote都被更新,会从每个remote指定的版本库中检查并抓取新提交。也可以限制只从一个remote获取更新,如git remote update remote_name。
    • 在添加远程版本库时,使用-f选项将会立即对该远程版本库执行fetch,即git remote add -f origin repository。
  • 现在,你已经把你的版本库连接到仓库中的远程版本库了。

3.3、在开发版本库中进行开发

示例2-4:

  • 让我们在开发版本库中进行开发,并添加另一个文件file2。
//(1)进入开发版本库
]# cd nonbare-example/

//(2)创建一个提交 
]# echo "Line 2 master" >> file2
]# git add file2
]# git commit -m "add 2 to file2"

//(3)查看所有分支
]# git show-branch -a
* [master] add 2 to file2
 ! [origin/master] add 1 to file1
--
*  [master] add 2 to file2
*+ [origin/master] add 1 to file1
  • 这时,开发版本库比权威版本库多出了一个提交。更有趣的是开发版本库有两个分支,一个(master分支)上有新的提交,而另一个(origin/master)在追踪远程版本库。

3.4、推送变更

  • 你提交的任何变更都在本地版本库中,它尚未存在于远程版本库中。使用git push命令将提交从本地的master分支推送到origin远程版本库。

示例2-5:

1、推送变更

//(1)进入开发版本库
]# cd nonbare-example/

//(2)将提交从本地的master分支推送到origin远程版本库
]# git push origin master
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 2 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 270 bytes | 270.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To /root/depot/bare-example.git
   b27a388..9dc48d6  master -> master
  • 这些输出意味着Git已经提取了master分支的变更,并将它们捆绑在一起,发送到名为origin的远程版本库中。在这里Git还执行了一个步骤:取出那些相同的的变更,也将它们也添加到你的版本库的origin/master分支中。
  • 实际上,Git将你在本地master分支的变更发送到远程版本库,然后再把它们放回到origin/master的远程追踪分支
    • Git并不往返传输变更,毕竟,提交已经在你的版本库中了。Git能足够智能地对远程追踪分支进行简单地快进。

2、查看本地版本库的分支

  • 现在,两个本地分支master和origin/master,反映了你的版本库中相同的提交
]# git show-branch -a --more=10
* [master] add 2 to file2
 ! [origin/master] add 2 to file2
--
*+ [master] add 2 to file2
*+ [master^] add 1 to file1

3、查看远程版本库是否更新了

  • 你还可以探测远程版本库并验证是否也已更新。
//方法一:如果远程版本库在本地文件系统中
]# cd depot/bare-example.git/
]# git show-branch --more=10
[master] add 2 to file2
[master^] add 1 to file1

//方法二:如果远程版本库在不同的物理机器上时,可以用一个底层命令确定远程版本库的分支信息
]# cd nonbare-example/
]# git ls-remote origin
9dc48d63692e60b2dec2906c52912e1b39b4fc68	HEAD
9dc48d63692e60b2dec2906c52912e1b39b4fc68	refs/heads/master

3.5、添加新开发人员

  • 当建立了一个权威版本库后,为一个项目添加新开发人员是很容易的,只需要让他克隆权威版本库,然后开始工作就行了。

示例2-6:

  • 现在,一个开发人员Bob要加入到这个项目

1、克隆权威版本库

//克隆权威版本库,获得一个新的开发版本库bob
]# git clone /root/depot/bare-example.git bob
  • 因为Bob的版本库是从父版本库克隆来的,所以它有一个默认的远程版本库origin。Bob可以在他的远程版本库中找到更多关于origin的信息:
]# cd bob
]# git remote show origin
* remote origin
  Fetch URL: /root/depot/bare-example.git
  Push  URL: /root/depot/bare-example.git
  HEAD branch: master
  Remote branch:
    master tracked
  Local branch configured for 'git pull':
    master merges with remote master
  Local ref configured for 'git push':
    master pushes to master (fast-forwardable)
  • 在执行默认的克隆后,配置文件的完整内容展示了origin远程版本库的样子:
]# cat .git/config 
[core]
	repositoryformatversion = 0
	filemode = true
	bare = false
	logallrefupdates = true
[remote "origin"]
	url = /root/depot/bare-example.git
	fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
	remote = origin
	merge = refs/heads/master
  • 在他的版本库中除了有origin远程版本库之外,Bob还有一些分支。可以列出版本库中所有的分支:
    • master分支是Bob的主要开发分支,这是常见的本地特性分支,这也是一个与名为master的远程追踪分支相对应的本地追踪分支
    • origin/master分支是一个远程追踪分支,追踪origin版本库中master分支的提交。
    • origin/HEAD应用通过符号名指出哪个分支是远程版本库认为的活动分支。
    • master分支名前的星号表示那是他的版本库中当前检出的分支。
]# git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master

2、在开发版本库bob中创建一个提交

]# cd bob/
 
]# echo "Line 3 master" >> file3
]# git add file3
]# git commit -m "add 3 to file3"

3、推送变更

]# git push origin master
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 2 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 295 bytes | 295.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To /root/depot/bare-example.git
   9dc48d6..df04e41  master -> master

3.6、获取权威版本库的更新

3.6.1、git pull命令

  • 假设Bob去度假了,在临走之前将他的最新变更推送到了仓库中的权威版本库。我们要在他的更新的基础上进行开发。

示例2-7:

//(1)进入开发版本库nonbare-example
]# cd nonbare-example/

//(2)同步权威版本库
]# git pull origin master
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 274 bytes | 274.00 KiB/s, done.
From /root/depot/bare-example
 * branch            master     -> FETCH_HEAD
   dfd2695..d108dca  master     -> origin/master
Updating dfd2695..d108dca
Fast-forward
 file3 | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 file3
  • 完整的git pull命令允许指定版本库和多个refspec:git pull 远程版本库 refspecs。
    • 如果不在命令行上指定版本库,无论是通过Git URL还是间接通过远程版本库名,则使用默认的origin远程版本库。
    • 如果你没有在命令行上指定refspec,则使用远程版本库的抓取(fetch)refspec。
    • 如果指定版本库(直接或使用远程版本库),但没有指定refspec,Git会抓取远程版本库的HEAD引用。
  • git pull操作有两个步骤:先执行git fetch,然后执行git merge或git rebase。默认情况下,第二步骤是merge,因为这始终是大多数情况下期望的行为。
    • 因为拉取(pull)操作还进行merge或rebase步骤,所以git push和git pull不被视为是相对的。但可以将git push和git fetch认为是相对的。推送(push)和抓取(fetch)都负责在版本库之间传输数据,但方向相反。
    • 有时,你可能需要单独执行git  fetch和git merge操作。例如,你可能希望把更新抓取到你的版本库中,检查一下它们但不一定立即合并。在这种情况下,你可以简单地执行抓取操作,然后在远程追踪分支上执行其他操作,如git log、git diff或者甚至gitk之后,当你准备好了(如果有准备),你可以在方便的时候进行合并。

3.6.2、分开执行过抓取和合并操作

  • 即使你从来没有分开执行过抓取和合并操作,你也可能会进行复杂的操作,这些操作需要你知道每一步发生了什么。因此,让我们来看看每一步的细节。

1、抓取步骤

  • 在最开始的抓取步骤中,Git先定位远程版本库。如果在命令行中没有直接指定一个版本库的URL或远程版本库名,默认使用远程版本库名origin。该远程版本库的信息在配置文件中:
[remote "origin"]
	url = /root/depot/bare-example.git
	fetch = +refs/heads/*:refs/remotes/origin/*
  • Git现在知道使用URL /root/depot/bare-example.git作为源版本库。由于没在命令行中指定refspec,因此Git会使用remote条目中所有"fetch="的行,将抓取远程版本库中的每个refs/heads/*分支
  • 接下来,Git对源版本库进行协商,以确定哪些新提交是在远程版本库中而不是在你的版本库中的,根据所期望的,获取所有的refs/heads/*引用作为拉取符号引用给出的。
  • 提示:不必使用refs/heads/*的通配符形式来获取远程版本库的所有特性分支。如果你只想要特定的一两个分支,则可以明确地列出它们:
[remote "origin"]
	url = /root/depot/bare-example.git
	fetch = +refs/heads/master:refs/remotes/origin/master
	fetch = +refs/heads/stable:refs/remotes/origin/stable
  • 拉取操作输出中以"remote:"为前缀的部分反映了协商、压缩和传输协议,它可以让你知道新提交正传到你的版本库中。
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
  • Git把新提交放在你的版本库上一个合适的远程追踪分支中,然后告诉你它使用什么映射来确定新的提交所属:
From /root/depot/bare-example
 * branch            master     -> FETCH_HEAD
   dfd2695..d108dca  master     -> origin/master
  • 这几行表示Git查看了远程版本库/root/depot/bare-example,取得它的master分支,然后把它的内容取回到你的版本库,并把它们放在你的origin/master分支上。这个过程是分支追踪的核心

2、合并或变基步骤

  • 在拉取(pull)操作的第二步,Git会执行合并(merge)(默认),或变基(rebase)操作。
  • 在这个例子中,Git使用一种特殊类型的合并操作快进(fast-forward),合并远程追踪分支origin/master的内容到你的本地追踪分支master分支。
  • 但是Git是如何知道合并哪些特定的分支的呢?答案来自配置文件:
    • 当master分支是当前检出的分支时,使用origin作为fetch(或pull)操作过程中获取更新的默认远程版本库。此外,在git pull的merge步骤中,将远程版本库中的refs/heads/master分支合并到本地master分支。
//执行该命令git branch --set-upstream-to=origin/master master可以获得该配置
[branch "master"]
	remote = origin
	merge = refs/heads/master
  • 对于密切关注细节的读者来说,解释的第一部分是Git决定origin作为无参数的git pull命令使用的远程版本库的实际机制。
  • 配置文件中branch部分的merge字段的值(refs/heads/master)被视为refspec的远程部分,它必须与从git pull命令过程中取出的源引用相匹配。这有点令人费解,但可以将这看成是pull命令中从抓取步骤到合并步骤所传达的暗示。
  • 因为merge的配置值仅在执行git pull时适用,所以手动执行git merge时必须在命令行中指定合并的源分支。该分支可能是一个远程追踪分支,如:
//或者完全指定:refs/remotes/origin/master
git merge origin/master
  • 提示:当命令行上给出多个refspec并且它们都在remote条目上时,分支之间的合并行为有些轻微的语义差异,前者会导致章鱼合并,其中所有分支同时在n路操作中合并,而后者则没有。请仔细阅读git pull的使用说明页!
  • 如果你选择变基而不是合并,Git会将你的本地追踪特性分支上的变更向前移植到对应的远程追踪分支新抓取的HEAD。
  • git pull --rebase命令会引起Git只在这次pull过程中变基(而不是合并)你的本地追踪分支到远程追踪分支。要将变基设置为分支的正常操作,需要把branch.分支名.rebase变量配置为true:
[branch "master"]
	remote = origin
	merge = refs/heads/master
	rebase = true

3、应该合并还是变基

  • 所以,你在pull操作过程中应该合并还是变基呢?简短的回答是“做你想要做的”。那么,为什么你会选择其中一个而不是另一个呢?下面是一些需要考虑的问题。
  • 通过合并,每次拉取将有可能产生额外的合并提交来记录更新,同时存在于每一个分支的变更。从某种意义上说,它真实地反映了两条开发路径独立发展然后合并在一起。在合并期间必须解决冲突。每个分支上的每个提交序列都基于原来的提交。当推送到上游时,任何合并提交都将继续存在。有些人认为这些是多余的合并,并不愿意看到它们弄乱历史记录。另一些人认为,这些合并是开发历史记录更准确的写照,希望看到它们被保留。
  • 变基从根本上改变了一系列提交是在何时何地开发的概念,开发历史记录的某些方面会丢失。具体而言,你的开发最初基于的原始提交将更改为远程追踪分支新拉取的HEAD。这将使开发出现的比它实际晚一些(在提交序列方面)。如果这样对于你没问题,那么对于我也没问题。这只是跟合并的历史记录不一样,且更简单。当然,你在变基操作过程中仍然要去解决冲突。由于变基的变更仍然只在你的版本库中,还尚未公布,因此这次变基没有理由害怕“不改变历史记录”的禁咒。
  • 通过合并和变基,你应该认为新的最终内容与独立存在于任意开发分支的都不同。因此,它可能使某种形式的验证变成新的形式:也许被推送到上游版本库之前的编译和测试周期。
  • 我倾向于看到简单线性的历史记录。在我个人的开发过程中,我通常不会太在意把我的变更相对于从远程追踪分支抓取的同事的变更做轻微地重新排序,因此我喜欢使用变基选项。
  • 如果你真的想建立一个始终如一的方法,可以考虑根据需求设置选项branch.autosetupmerge或branch.autosetuprebase为true、false或者always。不仅仅是本地和远程分支之间,还有一些其他选项来处理纯粹的本地分支之间的行为。

1

#                                                                                                                         #
posted @ 2023-03-28 10:16  麦恒  阅读(77)  评论(0编辑  收藏  举报