每个人的-Git-版本控制初学者指南-全-

每个人的 Git 版本控制初学者指南(全)

原文:annas-archive.org/md5/9ee3849c3fb0f513d0eb9a1e9be81e8d

译者:飞龙

协议:CC BY-NC-SA 4.0

序言

本书是一本非程序员指南,旨在帮助读者在日常工作中快速上手使用 Git 版本控制系统,以提高他们在处理各类电子数据时的效率和生产力。

通过逐步的示例和插图截图,你将被引导完成安装、配置,并掌握使用 Git 这一顶级工具对数据进行版本控制所需的概念。

每一章的概念都通过简单的日常例子和有趣的类比进行讲解,让学习本身成为一种值得珍惜的体验。

本书特别面向使用不同操作系统(如 Microsoft Windows、Linux 和 Mac OS)的多元化背景读者,所有插图都通过图形用户界面(GUI)和命令行界面(CLI)两种模式进行解释。

最后一章专为那些想要理解 Git 功能背后原理的读者准备,讲解他们在前几章中所使用的 Git 操作,通俗易懂。对于那些在阅读本书之前就已经在使用 Git 的人也会很有兴趣。

到书的最后,你不仅会获得理论知识,还会对如何处理数字文件的方式有一个实际的理解和经验,这些概念能让你在处理文件时做出不同的选择。

本书也可以作为参考资料,或者用来重新学习每一章中讨论的概念。书中会根据需要提供示例,确保内容易于理解。

本书的内容

第一章,放松呼吸 – 版本控制系统简介,介绍了版本控制系统的概念、必要性及其发展演变,更重要的是,为什么 Git 被认为是最优秀的版本控制工具。

第二章,欢迎上船 – 安装 Git,指导你根据操作系统的不同进行 Git 的安装。

第三章,打磨你的日常 – 如何提高日常生产力,教授你五个基本而重要的概念(初始化你的仓库,将文件添加到仓库,开始通过提交进行版本控制,使用 checkout 回退,以及在需要时进行重置),这些都是你开始使用 Git 对文件进行版本控制所需的全部内容。

第四章,分担负载 – 使用 Git 进行分布式协作工作,教你通过互联网和内网等多种媒介与他人共享内容,学习协作开发的基本知识,并探索在不同位置与不同人共同继续工作的方法。

第五章,成为木偶大师 – 学习控制 Git 功能的高级技巧,教你一些可以在各种情况下实施的小技巧,这些技巧能够改变 Git 在前面章节中所涉及的功能的常规行为。

第六章,释放猛兽 – Git 在文本文件中的应用,向你展示了一个名为“合并”的新功能,它被认为是 Git 的标志性特性之一。你将学会如何合并内容并解决由此产生的冲突。

第七章,平行维度 – 使用 Git 进行分支,介绍了 Git 最受赞誉的功能之一——分支的概念、它的重要性,以及如何实施分支来改变你的工作方式。

第八章,幕后 – Git 基础的基础,深入讲解了 Git 的内部结构,并用简单的术语进行阐述。你将了解到当你执行前面章节中的各种 Git 命令时,Git 所执行的底层操作。

本书所需的工具

学习本书中的概念的基本要求是具有管理权限(或至少安装权限)访问运行 Windows、Linux 或 Mac 操作系统的计算机,偶尔还需要该计算机的互联网连接。

除此之外,最好有你最喜欢的文本编辑器和一个压缩工具(你的计算机默认应该会有一个),还需要像 MS Office、OpenOffice 或 LibreOffice 等办公软件包来创建文档。

本书适合人群

本书适合任何具有计算机基础知识并希望以高效的方式维护多个文件版本的人,帮助他们在不必处理存储在不同位置的各种文件和混乱的名称的情况下,随时回到过去查看不同版本。

本书也适合那些有 Git 或其他版本控制系统使用经验的人,因为他们会从最后一章中收获一些有趣的内容,该章专注于用简单的术语讲解 Git 的内部结构。

约定

本书中,你将看到几个标题经常出现。

为了提供清晰的操作步骤,我们使用:

行动时间 – 标题

  1. 行动 1

  2. 行动 2

  3. 行动 3

指令常常需要额外的解释才能让它们更有意义,因此接下来会有:

刚刚发生了什么?

该标题解释了你刚刚完成的任务或指令的工作原理。

你还会在书中找到其他一些学习辅助工具,包括:

英雄尝试 – 标题

这些设置实际的挑战,并给你提供一些实验的思路,帮助你巩固所学内容。

您还会看到几种不同风格的文本,以区分不同类型的信息。以下是这些样式的一些示例及其含义说明。

文本中的代码词汇通常如下所示:“在桌面上创建一个名为BCT的目录。”

一段代码通常如下所示:

[remote "capsource"]
url = https://github.com/cappuccino/cappuccino
fetch = +refs/heads/*:refs/remotes/capsource/*

任何命令行输入或输出通常如下所示:

git add .
git commit –m 'Unfinished list of marketing team'
git checkout master 

新术语重要词汇以粗体显示。您在屏幕上看到的,诸如菜单或对话框中的词汇,通常以这种方式显示:例如,“点击添加按钮”。

注意

警告或重要提示通常会以框的形式展示,如下所示。

提示

提示和技巧以这种方式展示。

读者反馈

我们非常欢迎读者的反馈。请告诉我们您对本书的看法——您喜欢或不喜欢的部分。读者反馈对我们来说非常重要,帮助我们开发出真正能让您获益的书籍。

要向我们发送一般反馈,您只需发送电子邮件至<feedback@packtpub.com>,并在邮件的主题中注明书名。

如果您在某个主题上有专长,并且有兴趣写作或为书籍做贡献,请参考我们在www.packtpub.com/authors上的作者指南。

客户支持

现在,您已经成为一本 Packt 图书的骄傲拥有者,我们有许多资源帮助您最大限度地利用您的购买。

下载示例代码

您可以从您的账户中下载所有已购买的 Packt 图书的示例代码文件,网址为www.packtpub.com。如果您是在其他地方购买的本书,您可以访问www.packtpub.com/support,并注册以便将文件直接发送到您的电子邮箱。

勘误表

尽管我们已经尽力确保内容的准确性,但错误仍然可能发生。如果您发现我们书中的错误——可能是文本或代码中的错误——我们非常感激您向我们报告。通过这样做,您可以帮助其他读者避免困惑,并帮助我们改进后续版本的内容。如果您发现勘误,请访问www.packtpub.com/support,选择您的书籍,点击勘误提交表单链接,填写勘误详情。经确认后,您的勘误提交将被接受,并且勘误将被上传至我们的网站,或添加到该书的勘误列表中。

盗版

互联网版权材料的盗版问题在所有媒体中持续存在。在 Packt,我们非常重视版权和许可的保护。如果您在互联网上发现任何我们作品的非法副本,无论其形式如何,请立即提供该副本的地址或网站名称,以便我们采取相应的措施。

如需举报疑似盗版材料,请通过 <copyright@packtpub.com> 与我们联系,并提供相关链接。

感谢您帮助保护我们的作者,以及支持我们为您提供有价值的内容。

问题

如果您在书籍的任何方面遇到问题,可以通过 <questions@packtpub.com> 与我们联系,我们会尽力解决。

第一章:轻松呼吸——版本控制系统简介

我们或许曾无数次地想,是否有办法回到过去改变曾经发生过的事情。虽然听起来像是虚构的,但你将会学到一种方法,当涉及到数字化文件时,可以实现这种“穿越”!没错,你没有看错;本章将向你介绍一个让这一切成为可能的系统。我们将从为你提供关于 Git 版本控制的坚实概念理解开始。

本章将回答以下问题:

  • 什么是版本控制系统?

  • 你在哪里需要它?

  • 它们是如何发展的?

  • 为什么 Git 是你的最佳选择?

到本章结束时,你将能够清楚地了解如何更好地处理在数字化文件的不同部分频繁变动的情况。那我们就马上开始吧!

你需要帮助吗?

我在学会打开或关闭计算机之前就学会了玩电脑游戏,那时我需要寻求成年人的帮助。早期的电脑游戏即使在当时也让我们惊叹,但也有一些让人沮丧的时刻,它们不允许我们保存进度。即使它们有保存选项,也只是一次只能保存一个档位,这意味着你只能用新的进度覆盖掉旧的进度。这样很遗憾,因为你之前的存档可能是在游戏中特别有趣的部分,你现在希望保存并在以后某个时候重新访问,或者更糟糕的是,你当前的存档可能是在一个无法通关的情境中,你希望撤销这一点。

计算机游戏从这一状态进化而来,而我们处理数字化文件的方式却依然保持不变。像 撤销重做 这样的选项在文件仍然打开时能短暂地帮助我们,但无法超越这个限制。你不能仅仅打开一个文件,然后开始撤销在上次保存之前所做的更改,以回到较早的状态。

还有一些情况,我们希望维护同一个文件的多个版本。即使是最常用的通过按顺序命名新文件的方式来维护多个版本,比如 Inventory_product_2011-09-21.docSystem_requirement_specification_v6.xls 等等,随着版本数量的增加,这种方式也会变得令人头疼,因为必须维护的文件数量巨大。

如果你曾经历过或思考过这些情况,并想知道是否有更好的方法来处理,你将在本章结束时感到欣喜。这就是 版本控制系统VCS)发挥作用的地方。

什么是版本控制系统

一个能够记录文件或一组文件在一段时间内所做更改的系统,以便它允许我们从未来回到过去,回溯某个特定版本的文件,这种系统被称为版本控制系统。

为了给您更正式的解释,版本控制系统是一个软件包,当启动时,它会监控您的文件变化,并允许您在不同级别上标记这些变化,以便您在需要时可以随时回访这些标记的阶段。

安装并启动后,版本控制系统会在您的文件所在位置创建一个本地目录,用于管理您文件所有更改的历史记录。

为什么您需要一个版本控制系统

请尝试回答以下与您当前系统设置相关的问题:

  • 您是否可以在同一个文件名下保持多个版本,从而避免因文件名中提及版本而导致文件冗余?

  • 您是否有任何方法在更改文件/文件内容之前,标记出未来可能需要的特定部分?

  • 您是否满意现有的情况,即唯一的备份方案是将文件或一组文件复制并粘贴到一个包含“备份”字样的单独文件夹中,并定期更新它?

如果您对这些问题的回答是个大,那么这正是您可能需要版本控制系统和本书的原因。

如果您对这些问题的回答是,这意味着您可能已经找到了变通的方法来解决这些问题。简单的措施包括在最新版本的 Windows 中创建还原点,该还原点会在当时存储您所有文档的版本,如您的 Word、Excel 或 PowerPoint 文件。

解决方案可能会有很多种,但请允许我告诉您,版本控制系统将以其强大、简单和易用的特点让您感到惊讶。它们将使您在比现在的解决方案所需时间和精力少一半的情况下,获得更好的结果。

通过使用版本控制系统,您可以掌控文档变更的流程。每当您需要对现有内容做出大量修改时,您可以将这些更改标记为一个阶段(带有标签),以便稍后回访;这就像一个安全机制,以防事情没有按计划进行,您希望将文档内容恢复到某个较早的状态。

下图展示了有无版本控制系统的内容创作流程:

为什么您需要版本控制系统

上一图展示了在不同时间点跨会话传播的内容创作矩阵的流程。如你所见,在常规的建设性环境中,流程是从左到右的,这意味着在不同时间段的内容创作过程中,你是单向前进的。在这个流程中,你不能回到一个早期阶段,从那里你可以选择一个完全不同的方向,而不是你已经采取的方向。

通过我们流程图的说明,你无法从最终阶段回到任何中间阶段去写一个完全不同的第三段内容以服务于新的目的,而不会丢失任何数据。(你不能使用撤销功能,因为内容是在不同的时间段中构建的,而且一旦你保存并关闭文件后,不能撤销任何操作。)

目前,我们通过使用“另存为”选项,给文件起一个不同的名字,删除第三段,并开始写新的一段来实现这一点。

相比之下,当你使用版本控制系统时,它是一个多方向自由流动的环境。你标记下每一个你认为重要的更改作为一个新的阶段,并继续进行内容创作。这使你可以在没有任何数据丢失的情况下,回到你创建的任何早期阶段。

你为什么需要版本控制系统

最棒的部分是,你不受以下限制:

  • 跳跃的次数

  • 跳跃之间的阶段数

  • 跳跃的方向

这意味着我们可以毫无顾虑地在任何方向上自由地在不同阶段之间跳来跳去而不会丢失任何数据。现在听起来是不是正是这个时代所需要的?

版本控制系统的类型

有三种类型的版本控制系统可用。这些是根据它们的操作模式进行分类的:

  • 本地版本控制系统

  • 集中式版本控制系统

  • 分布式版本控制系统

让我们简要回顾一下历史。

本地版本控制系统

在认识到仅通过遵循文件命名规则来维护多个版本的文件是高度容易出错后,本地版本控制系统是第一个成功解决此问题的尝试。

修订控制系统RCS)曾是这一领域最流行的版本控制系统之一。

这个工具基本上通过在版本跟踪器中使用一种特殊格式来保持补丁集(即文件在不同阶段内容之间的差异),并将其存储在本地硬盘上。

它可以通过按顺序添加所有相关补丁并“签出”(将内容复制到用户的工作区),精确地在任何给定的时间点重建文件的内容。

本地版本控制系统

小贴士

版本跟踪器实际上是一个拥有自己文件格式的文件,通过该格式可以存储结构化内容,并执行其功能。

当文件被放入 RCS 时,它会创建一个版本跟踪条目,其中包含该文件特定的 RCS 配置,版本号、日期、时间、作者、状态、分支以及指向下一个阶段的链接,接着是文件内容,格式化方式特别。完成此过程后,你的文件会被删除!

如前所述,文件的检索是通过修补程序的重建来完成的。

集中式版本控制系统

与其他任何软件包或概念一样,随着需求不断发展,用户觉得本地版本控制系统限制了他们的活动。

人们无法在同一个项目上进行协作工作,因为带有版本的文件存储在某个人的本地计算机上,其他在同一文件上工作的人无法访问这些文件。

那么你该如何解决这个问题呢?

通过将文件保存在一个大家都能从本地计算机(客户端)访问的公共位置(服务器)来解决问题。因此,集中式版本控制系统应运而生。

集中式版本控制系统

每当人们想编辑单个或多个文件时,只会检索文件的最后一个版本。

这种设置不仅为需要文件的人提供访问权限,还提供了对其他人工作进展的可见性。

由于文件存储在一个统一位置,所有人都需要共享这些文件,因此对文件所做的任何更改会自动与其他人共享。

分布式版本控制系统

每当你将大量资源押注于一个单一单位时,失败的概率也会很高。是的,使用集中式版本控制系统确实存在很高的风险,因为用户只能在系统中获得文件的最后一个版本用于工作;如果服务器发生故障,而且没有实施故障保护程序,你最终可能会丢失文件的全部历史记录。

现在人们感到困惑。当你使用集中式版本控制概念将整个历史存储在一个地方时,你将面临巨大的风险;相反,当你使用本地版本控制时,你失去了协作工作的能力。

那么你该怎么做呢?

没错!你将两者的优点结合起来,构建一个混合系统。这也是分布式版本控制系统进入视野的关键原因之一。

分布式版本控制系统

分布式版本控制系统具备本地版本控制系统的优点,例如以下几点:

  • 在不担心与服务器持续连接的情况下进行本地更改

  • 不依赖存储在服务器中的单一文件副本

这些与集中式版本控制系统的优点结合起来,例如以下几点:

  • 工作的可重用性

  • 协作工作,不依赖存储在个人机器上的历史记录

分布式版本控制系统设计成双向工作。它在每台机器上本地存储文件/文件的整个历史记录,并且在需要时将用户所做的本地更改同步回服务器,以便将更改与其他人共享,提供一个协作的工作环境。

在性能、易用性和管理方面,还有其他一些优势。一般来说有一句话说:“你列举任何一个集中式版本控制系统能做的事情;分布式版本控制系统都能做,并且做得更好。”

信赖 Git

在前一节中我们接触到了不同类型的版本控制系统,从中我们清楚地了解到,分布式版本控制系统能让我们的工作变得更加轻松、安全和可靠。

现在,市场上有很多分布式系统,那么该选择哪个呢?

Git 是一个相对较新的软件包(2005 年 4 月 7 日发布第一个原型),它从零开始设计,避免了许多其他版本控制系统存在的缺陷。

Linus Torvalds,Linux 内核的创造者,也是这个项目的骄傲发起人。Git 的架构本身就为更好的速度、性能、灵活性和可用性量身定制。当我第一次听到这句话时,我脑海中和你现在的想法是一样的:“它说得很好;它真的能做到吗?”

事实上,有多个实际案例研究;我在看到 Git 如何优雅地处理复杂的 Linux 内核源代码时,深受震撼。

对于那些不了解 Linux 内核或为什么它被称为复杂的人,可以想象大约 900 万行内容分布在 25000 个文件中,内容经常被不同的开发人员操作,每天多次往返。即使如此,Git 的操作响应时间仍然仅为几秒钟。

为什么他们信任 Git 来完成如此具有挑战性的任务,以及 Git 如何满足他们的期望,可以通过以下几点来解释:

  • 原子性

  • 性能

  • 安全性

原子性

原子性只不过是一个操作的特性,它在调用和响应之间看似在一个瞬间发生。

以银行系统为例。当你从你的账户向另一个账户转账时,操作要么完全成功,要么被拒绝,这意味着要么钱从你的账户中扣除并存入收款人的账户,要么整个操作被取消,钱根本不会从你的账户中扣除。

这些系统避免了部分完成的情况,比如钱已经从你的账户扣除,但没有存入收款人的账户。

另一个例子是座位预定系统,其中可能有以下几种状态:

  • 同时付款并预定座位

  • 既不付款也不预定座位

Git 的创建者理解我们数据的价值,并在处理 Git 内容时实现了这一点。它确保了由于部分操作导致的数据丢失或版本不匹配的情况不会发生,从而提高了可靠性。

性能

无论一辆车的内部如何豪华,如果它不够快,就无法与时间赛跑。Git 被证明比它的竞争者快得多。

即使处理数百万个文件,使用 Git 执行的操作也只需几秒钟即可完成。这其中的一个主要原因在于 Git 处理文件的方式。大多数其他系统(如 CVS、Subversion、Perforce、Bazaar 等)概念上将数据视为一组文件,并把每个文件的变更看作版本更新的一部分。

以下是其他系统如何处理文件及其版本的图示:

性能

相比之下,Git 将文件之间的关系看作是其操作的核心。它对整个文件集进行 快照,而不是存储每个文件版本之间的差异;这有助于 Git 在某些操作(如将文件内容恢复到先前版本)中的快速性(稍后的章节我们会讨论到)。每次创建版本时,Git 都会进行快照。这并不意味着 Git 存储多个文件副本;如果 Git 发现某个文件内容没有变化,它只会存储指向先前快照的引用,而不是存储文件本身,如下图所示:

性能

最好的部分是,Git 尽可能占用最少的空间(与其他版本控制系统相比,空间占用少了几倍)来维护文件的版本历史。在凯斯·P.(keithp.com/blogs/Repository_Formats_Matter/)发布的关于 Mozilla Firefox 源代码管理的案例研究中,展示了版本控制系统在维护文件历史记录时是如何高效利用空间的。

Mozilla 的 CVS 仓库大小为 2.7 GB;导入到 Subversion 后大小增至 8.2 GB,而在 Git 中,大小压缩到了 450 MB。对于一个 350 MB 的源代码项目,能够拥有自 1998 年以来的全部项目历史,只需要额外 100 MB 的空间,真是相当不错。

安全性

当你使用 Git 时,你可以放心,没有人会篡改你的文件内容。所有提交到 Git 的内容都会先通过 SHA-1 哈希进行校验,确保内容的完整性,之后使用该校验和来引用文件。

这意味着,在没有 Git 知道的情况下,任何文件或目录的内容都无法更改。这里使用的 SHA-1 哈希是由 40 个十六进制字符(a-f 和 0-9)组成,基于文件或目录结构的内容生成。以下是一个哈希示例:

9e79e3e9dd9672b37ac9412e9a926714306551fe

对于那些希望进一步了解的人,你可以听听版本控制系统的创始人——Linus Torvalds 在谷歌技术讲座上的演讲。

总结

本章讨论了我们日常生活中遇到的与数字化文件相关的问题,接着针对这些问题提供了具体的解决方案,并通过版本控制系统确保能够解决这些具有挑战性的问题。

我们还简要回顾了版本控制系统的发展历程,并深入理解了分布式版本控制系统如何使我们的生活更加便捷。

接着我们介绍了行业领先的分布式版本控制系统 Git,并通过一些有趣的统计数据和案例研究讨论了这一声明背后的几个理由。随后,我们还探讨了它的一些内部机制,如原子性、性能和安全性。

在完成了足够的准备工作之后,我们已经准备好安装 Git 并在系统中运行它,这也是下章的主题。

第二章:欢迎加入 – 安装 Git

在上一章中,我们了解了版本控制系统如何改变我们面对数字化文件时的日常处理方式,并探讨了版本控制系统的演变。

我们还了解了为什么 Git 被认为是同类中最好的工具,以及它如何满足我们的需求。

在本章中,我们将了解如何安装和配置 Git。我们将涵盖以下内容:

  • 选择适合你环境的 Git 类型

  • 安装 Git

选择你的类型 – 下载适合你操作系统的安装包

像许多其他工具一样,Git 可以从互联网下载,最棒的是,它是免费的,这要感谢开源社区。Git 可以安装在各种操作系统上,如 Mac OS X、Windows、Linux 和 Solaris。在讨论我们的概念时,我们将考虑用户群体最多的前三大操作系统:

  • Windows

  • Linux

  • Mac OS X

首先,你需要下载适合你操作系统的 Git 安装包;我们可以从 git-scm.com/downloads 获取下载包列表。继续下载与你的操作系统相关的最新稳定版本,可以在网站上找到该版本,如下图所示(在编写本章时,版本 1.8.0.2 是最新的稳定版本):

选择你的类型 – 下载适合你操作系统的安装包

注意

对于那些喜欢使用命令行界面CLI)模式的 Linux 和 Mac 用户来说,完全不需要通过图形界面(GUI)下载和安装。你可以使用操作系统特定的安装程序来完成安装。例如,如果你使用的是基于 Debian 的 Linux 操作系统,apt-get install git-core 就足够在系统上安装 Git,而如果你使用的是 Mac,可以使用 Apple 的 Xcode IDE,这是一种 Apple 批准的方法来安装工具,或者使用 Macports 或 Fink 来安装 Git。

Windows

如果你从 Windows 机器下载,网站会自动识别并提供为 Windows 下载按钮。点击该按钮后,你将被提示保存安装文件。选择你希望保存文件的位置,之后你就可以开始安装了。

Linux

在 Unix 操作系统中,总是有多种方式来完成任务。我们将选择最简单的方式,确保每个人都可以跟上。所以,除非你是那种喜欢通过编译安装包的人,否则无需从该网站下载,你可以直接跳到安装部分。

注意

对于想要通过获取源代码并编译安装 Git 的用户,执行以下步骤:

  • 点击Git 源代码库链接,你将进入一个列出源代码包内容的页面。点击名为Zip的按钮,系统会提示你下载文件。保存文件到你选择的位置。

  • 然后,你可以按照常规的unzip, configuremakemake install命令进行操作,我们在此不作详细讨论。

Mac

如果你是从 Mac 设备下载,网站会自动检测并提供Mac 版下载按钮。点击该按钮后,会提示你保存安装文件。选择你希望保存文件的位置,然后就可以继续了。

下载文件通常遵循类似于 Git-latest.stable_release_version_here-min-required-os-info.dmg 的命名规范,例如,git-1.8.0.2-3-intel-universal-snow-leopard.dmg

你可以使用相同的 1.8.0.2 安装程序安装在 Snow Leopard 及以上版本的 Mac OS 上。对于 Leopard 用户,Git 提供了较低版本,你可以从 code.google.com/p/git-osx-installer/downloads/list 下载。

安装

现在你已经拥有了自己的 Git 副本,接下来让我们进入安装阶段。我们将逐一讲解不同操作系统的安装过程。

注意

和其他任何软件安装一样,安装该软件需要管理员权限。

操作时间 – 在 Windows 上安装 Git

执行以下步骤:

  1. 双击下载的安装文件即可开始安装。

  2. 安装过程的第一步和第二步是显而易见的。它首先通过欢迎信息问候你,并告知你一些安装前的“安全遵循”程序,即在继续之前关闭所有其他打开的应用程序(以避免任何共享的 dll/exe 被覆盖,或者系统内存不足等问题,安装过程中会消耗大量内存)。然后,它会展示关于 GNU 公共许可证第 2 版的信息,Git 就是依照此许可证来管理的。

    注意

    若要了解更多关于软件包可以和不能做的事情,继续阅读相关内容。

  3. 点击屏幕上的下一步按钮,将进入下一个屏幕,选择你希望安装的路径:操作时间 – 在 Windows 上安装 Git

    默认安装位置是 Windows 安装中的Program Files目录。如果你将所有自定义软件安装在单独的分区中,以防操作系统崩溃时保护数据,可以点击浏览...按钮选择你希望的路径。

  4. 点击下一步后,你将看到一个组件配置屏幕,在其中你需要在上下文菜单项部分选择Git Bash HereGit GUI Here选项,如下图所示:操作时间 – 在 Windows 上安装 Git

    这些选项将为我们提供快速访问 Git 命令界面的方式。我们将在接下来的章节中详细介绍。

  5. 接下来,我们需要选择一个组名,在此组名下将快捷方式放置在开始菜单中,方便快捷地访问。我们保持默认值Git并点击下一步

  6. 这将带我们进入下一个屏幕,在该屏幕中我们选择第二个选项,即从 Windows 命令提示符运行 Git行动时刻 – 在 Windows 上安装 Git

    这个设置主要是为那些将使用命令行控制 Git 及其活动的人准备的。此选项将允许我们通过 Windows 原生命令行接口 DOS 进一步控制 Git。

    选择完成后,点击下一步按钮。

  7. 剩下的两个主要步骤是我们在远程工作和/或跨操作系统协作时需要的配置。

    只有在你的注册表中有任何 SSH 会话时,安装文件才会检测到并提示你以下屏幕:

    行动时刻 – 在 Windows 上安装 Git

    如果你是一个对 SSH 这个术语感到陌生的用户,但通过其他方式在系统中拥有 SSH 会话,或者是一个想要切换到 OpenSSH 的有经验的用户,可以选择使用 OpenSSH选项。

    如果你习惯使用其他 SSH 应用程序连接到远程系统,选择使用(Tortoise)Plink选项。在选择 SSH 可执行文件屏幕上做出选择并点击下一步

    注意

    OpenSSH 密钥通过 ssh-keygen 创建,并使用 ssh-agent 进行缓存。而使用 putty 套件时,密钥通过图形程序 puttygen 创建,并通过 pageant 加载/缓存,SSH 则通过 putty 完成。

  8. 任何在不同操作系统上处理文件的人都一定知道不同风格的行尾符号所带来的问题。

  9. 现在你需要告诉 Git 如何处理这些行尾符号。在接下来的屏幕中,选择结帐 Windows 风格,提交 Unix 风格行尾符号,以确保在跨平台工作时回车换行符CRLF)和换行符LF)之间不会发生冲突:行动时刻 – 在 Windows 上安装 Git

就是这样。你的安装程序现在应该完成安装了。

刚刚发生了什么?

恭喜!你的 Windows 机器现在已经准备好使用 Git 控制任何内容的版本。

行动时刻 – 在 Mac 上安装 Git

执行以下步骤:

  1. 双击.dmg文件以开始安装。接下来的屏幕应该会出现:行动时刻 – 在 Mac 上安装 Git

  2. 双击.pkg文件以启动安装过程。出现在你面前的窗口欢迎你并提供有关接下来将要发生的情况的信息。

  3. 点击继续按钮后,您将获得关于软件将占用多少磁盘空间的信息,并显示此软件在您计算机上其他用户的访问权限,如下图所示:操作时间 – 在 Mac 上安装 Git

  4. 您可以根据需要自定义此设置,但目前我们继续使用默认设置,并通过点击安装按钮为所有用户进行安装。

  5. 安装过程中,系统会要求您提供管理员密码以继续安装。操作时间 – 在 Mac 上安装 Git

  6. 如果身份验证成功,您的安装将完成,系统会显示一个成功消息,如下图所示:操作时间 – 在 Mac 上安装 Git

刚刚发生了什么?

恭喜!您的 Mac 机器现在已经准备好通过 Git 来控制任何内容的版本。

操作时间 – 在 Linux 上安装 Git

正如我们之前讨论的,我们将通过使用您的发行版内建的图形软件管理系统在 Linux 操作系统上进行 Git 安装。在本教程中,我使用的是名为 Ubuntu 的发行版,它是基于(下游)著名的 Debian 操作系统。

在这里,软件管理系统称为 synaptic。请执行以下步骤:

  1. 通过按下Alt + F2 打开运行实用工具提示框,并输入synaptic操作时间 – 在 Linux 上安装 Git

  2. 匹配的工具会自动显示在下方;让我们点击第一个,它显示为synaptic-pkexec。这时会弹出一个身份验证对话框,因为安装需要提升权限,正如之前所说的那样。所以请提供您的管理员密码并点击认证操作时间 – 在 Linux 上安装 Git

  3. 这将打开Synaptic 包管理器窗口,您将在中间窗格中看到可用的软件包,并在左侧看到可用的仓库源。现在我们在快速筛选文本框中输入要安装的软件包名称git。它会自动在下方的内容窗格中填充匹配的软件包。操作时间 – 在 Linux 上安装 Git

  4. 现在我们可以看到一个名为git的软件包,其默认版本是1.7.5.4(不用担心版本不匹配;我们接下来要学习的概念对于所有版本都是一样的),其描述为快速、可扩展的分布式版本控制系统。这是我们要安装的软件包,所以让我们右击它并选择标记为安装操作时间 – 在 Linux 上安装 Git

  5. 现在,包名称旁边的复选框将显示一个勾选选项,表示你已选择该包进行安装。为了更方便操作,我们需要再安装两个与 Git 相关的包,分别是Git GUIGitk。所以我们再次右击同一个包,选择Mark Suggested for Installation选项,并从中选择git-gui时间行动 - 在 Linux 上安装 Git

  6. 包管理器会提示你有关所需的包更改,也就是将gitk添加到可安装列表中;所以点击Mark。现在你已经标记了将要安装的包,我们继续吧!等等,我们并没有选择我们说需要的 gitk 包!没错,我们没有选择。但它会自动安装,因为它是一个依赖包。你将在接下来的步骤中看到这一点。

  7. 现在继续,点击菜单栏下方快捷栏上的绿色Apply按钮。系统会弹出如下确认对话框:时间行动 - 在 Linux 上安装 Git

  8. 点击Mark后,系统会为你提供即将安装的包的摘要,并要求确认。时间行动 - 在 Linux 上安装 Git

  9. 在确认后,安装将开始,完成后你将看到如下的成功页面:时间行动 - 在 Linux 上安装 Git

刚才发生了什么?

恭喜!你的 Linux 机器现在已经可以借助 Git 控制任何内容的版本。

总结

在前一章节中了解了 Git 的强大功能后,本章节我们成功地获取了 Git 并将其变成了你自己的工具。

然后,我们成功地在你的操作系统上安装了 Git,使其可以使用。

现在我们已经安装了 Git,接下来的步骤是个性化设置它,之后我们将看到一些神奇的命令,这些命令会帮助我们提高生产力。这是下一章节的主题。

第三章:打磨你的工作流程 – 如何提高每日生产力

我们只触及了版本控制系统概念的表面,以理解 Git 如何让我们在工作中更加高效。毫不拖延地,我们也迅速获取了它,并在我们的系统中成功运行起来。

我听见你激动地喊道:“让我们深入了解吧!”

好的,船长,出发了。在这一章中,你将学习五个重要概念,这些概念通常是你在工作中所需的全部:

  • 启动过程

  • 将你的文件添加到仓库(目录)中

  • 提交已添加的文件

  • 检出

  • 重置

没错,仅仅五个概念就足以让你产生变化。当然,如果你在操作过程中迷失了方向,我们还将学习如何借助 Git 内建的帮助功能重新回到正轨。

装备好 - 为你的 Git 做好准备

假设你有一根魔杖,它将完美地执行你给它的命令!没错,就是这样,你现在拥有了 Git。你需要命令 Git 执行它必须为你完成的任务。

听起来很有趣,对吧?

我们已经了解到,要保持多个文件的版本,它们必须保存在一个目录(文件夹)中,因此我们将在桌面上创建一个名为Workbench的目录,来学习本章实践中解释的概念。

说到操作计算机,有些人希望通过以下任何一种方式完成任务:

  • GUI 模式(图形用户界面)

  • CLI 模式(命令行接口)

也可以同时使用两者的组合。为了满足多样化的受众,我们将尝试涵盖这两种实现模式。

初始化

初始化其实就是指向那个目录,让 Git 知道从那时起它需要监控该目录的内容,以便捕捉变化。

如前所述,我们将涵盖执行这些操作的两种方式(GUI 和 CLI)。

操作时间 - 图形用户界面模式中的初始化

要创建/初始化一个仓库,请执行以下步骤:

  1. 从桌面或应用程序菜单中打开Git Gui,并选择创建新仓库选项,如下图所示:操作时间 - 图形用户界面模式中的初始化

  2. Git 会向你展示一个新屏幕,要求你提供你想要创建仓库的目录位置。所以点击浏览按钮,从桌面上选择我们的工作台目录,然后点击创建按钮。操作时间 - 图形用户界面模式中的初始化

  3. 你应该看到如下的屏幕:操作时间 - 图形用户界面模式中的初始化

    现在别关闭这个窗口;我们将继续从这个屏幕开始,继续讲解接下来的概念。

刚才发生了什么?

你已经成功指示 Git 监控你的Workbench目录及其内容。

上一张图片展示了主页面,我们将经常与之交互。它由四个窗格组成,我们可以将它们称为:

  • 未暂存更改窗格(左上角)

  • 暂存更改窗格(左下角)

  • 差异内容窗格(右上角)

  • 操作窗格(右下角)

在我们的例子中,我们创建了一个名为Workbench的新目录并将其初始化为一个仓库。您也可以按照相同的步骤将一个已有的包含文件的目录转换为仓库,以便 Git 进行监控。当您这么做时,仓库内的文件将初步出现在未暂存更改窗格中。

行动时间 – 在 CLI 模式下初始化

对于那些喜欢听按键声音而不是点击鼠标的人,总有命令行界面CLI)模式可用。

使用键盘按键的速度越来越快的人比例不断增加,在可能的情况下,更多地优先使用按键而非鼠标点击操作。这也是Gmail为几乎所有功能引入快捷键的主要原因之一。

若要通过命令行界面模式创建或初始化仓库,您需要执行以下操作:

  1. 打开您的shell(Windows 中的命令提示符或 Mac/Linux 中的终端/控制台)。

  2. 使用cd(更改目录)命令前往桌面上的Workbench目录。

  3. 一旦进入Workbench目录,输入git init并按Enter键完成初始化过程。

  4. 您应该会收到 Git 的状态信息,显示已初始化空的 Git 仓库,在您的/路径/到/Workbench/目录/这里行动时间 – 在 CLI 模式下初始化

啊!按键的声音,真好听。

刚刚发生了什么?

您已成功命令 Git 监视我们的Workbench目录及其内容。Init是初始化仓库的操作关键字。

屏幕背后

这个初始化过程将在我们的Workbench目录中创建一个名为.git的目录。Git 通常会将这个目录设置为只读隐藏,以防止用户意外删除或篡改它。它是 Git 存储有关您文件及其更改历史的地方。

所以要小心那个目录;删除它将抹去该目录下所有文件的历史记录。

配置 Git

为了使您的 Git 安装准备好使用,必须正确配置它。配置 Git 之前有几个原因,但现在讨论所有这些理由还为时过早,我们将在实际需要时了解它们。对于现在,为了最基本的配置并开始使用,我们将告诉 Git 我们的名字和电子邮件地址,以便它能够以我们的身份记录更改。

行动时间 – 在 GUI 模式下配置 Git

要通过 GUI 模式向 Git 传达我们的名字和电子邮件地址,请按照以下步骤操作:

  1. 从初始化过程后留下的屏幕的编辑菜单中选择选项行动时间 – 在 GUI 模式下配置 Git

    配置屏幕被分为两部分。

    • 本地配置(左侧 – 特别是我们的 Workbench 仓库)

    • 全局配置(右侧 – 适用于使用此安装创建的所有仓库)

  2. 不要让大屏幕上众多的选项让你感到不知所措。现在让我们先专注于屏幕的上半部分,按照之前的图示,输入我们的姓名和电子邮件地址在本地和全局配置中,然后点击保存按钮。

刚刚发生了什么?

通过提供我们的用户名和电子邮件地址,无论是本地还是全局,我们已经为 Git 提供了识别和分组更改文件的方法,这些文件存在于任何仓库中。

脱离流程

万一你在初始化过程后关闭了屏幕,想知道如何重新进入相同的屏幕,不用担心。有两种方法可以回到之前的界面。

  1. 打开Git Gui,你会看到一个新添加的选项叫做Open Recent Repository,在这里你可以找到我们的Workbench仓库。脱离流程

  2. 在桌面上找到Workbench目录,并右键点击该文件夹。在菜单中选择Git GUI here。需要从命令行模式切换到图形界面模式的人也可以使用此选项。

行动时间 – 在 CLI 模式下配置 Git

要使用 CLI 配置 Git,你可以使用以下命令:

git config --global user.name "your full name"
git config --local user.name "your full name"
git config --global user.email "your email id"
git config --local user.email "your email id"
git config -l

行动时间 – 在 CLI 模式下配置 Git

刚刚发生了什么?

通过提供我们的用户名和电子邮件地址,无论是本地还是全局,我们已经为 Git 提供了识别和分组更改文件的方法,这些文件存在于任何仓库中。

config是需要与git命令一起使用的操作关键字,用于设置 Git 的配置。要设置全局值,我们在命令中添加--global参数,而要设置本地值,则添加--local参数。

如其名称所示,全局配置仅仅是为系统中由该用户创建的所有仓库提供的全局值,而本地配置则正好相反。正如你现在所猜到的,user.nameuser.email这两个参数分别用来记录用户的姓名和电子邮件地址。

要获取迄今为止设置的配置列表,你可以使用上一个命令,其中包含–l参数。它会列出所有配置变量。

将你的文件添加到你的目录

现在你已经为操作打下了完美的基础,让我们通过将文件添加到你创建的仓库中迈出一步。

等等!那个我们之前常常遇到的术语是什么?仓库

接下来,我们将介绍一个目录/文件夹,这个目录已经被 Git 指示作为仓库来监控。

是的,宝贝,学习 Git 术语,给你的约会留下深刻印象!添加文件的过程就像复制粘贴,或者在我们的仓库中创建你的文件,并要求 Git 监控它们。

行动时间 – 将文件添加到你的目录(GUI 和 CLI 模式)

让我们创建一个名为 content.docx 的 Word 文档,文档中包含文本 "I love working with Git. It's a simple, fast, and superb version control system",以便学习和实践我们在本章开头提到的功能(无法创建 .docx 文件的人可以使用任何其他文档格式,如 .odt.txt 等)。

开始操作 – 将文件添加到你的目录(GUI 和 CLI 模式)

Git 会向你报告已添加到我们仓库中的文件,并等待你的指示继续操作。现在,我们可以继续并告诉 Git 监视这些文件的变化,按照接下来我们将讨论的步骤进行操作。

如果你使用的是 GUI 模式,请执行以下步骤:

  1. 点击 重新扫描 按钮(或按键盘上的 F5 键)来刷新 操作 面板中的内容。开始操作 – 将文件添加到你的目录(GUI 和 CLI 模式)

  2. 点击文件名旁边像页面一样的图标,将文件推送到 已暂存更改 面板。

如果你使用的是 CLI 模式,请使用以下命令:

git status
git add content.docx

刚刚发生了什么?

我们已经成功地将文件添加到仓库中了。

通过点击 重新扫描 按钮或输入 git status 命令,我们命令 Git 列出自上次状态以来对仓库所做的更改。这些更改称为未暂存的更改,意味着自上次确认仓库状态以来发生的更改。

用户必须通过将它们移至已暂存更改状态来确认这些更改,可以通过点击文件名旁边的文件图标或使用 git add 命令来完成此操作。

忽略它们

我们刚刚看到了一些将文件放入 Git 监视范围的方法,但也有许多情况下,人们可能希望避免将某些文件添加到工作仓库中。作为实际案例,在向 content.docx 文件中添加一些内容,并尝试像前一步骤中一样将文件添加到仓库时,一些人可能遇到 Git 报告(当然,刷新 Git GUI 或在 CLI 中使用 git status 命令之后)显示两个文件 content.docx~$ontent.docx 的更改,如下图所示:

忽略它们

只有在打开的 content.docx Word 文档在刷新或执行 git status 命令之前没有关闭,才会发生这种情况。

忽略它们

这是因为 Microsoft 的 Word 应用程序有定期保存当前工作区(可以配置的)到临时文件中的习惯,用于灾难恢复。

正是因为这个机制,当你没有在文档突然关闭之前妥善保存时,Word 会提示你一个文件恢复对话框,从中你可以恢复最近的更改。

忽略它们

不仅是 Microsoft Word,所有智能应用程序和编辑器都会遵循这样的程序,以便让最终用户感到舒适。这些文件会在相应的源文件保存并正确关闭后自动删除。控制这些临时文件的版本没有任何意义。

因此,在将文件添加到仓库时,重要的是在进行提交操作之前排除这些临时文件,否则撤销这个操作将是一个痛苦的过程。

这种将文件添加到 Git 的方式适用于少量文件,但当涉及到处理存储库中的多个文件时,每次点击文件旁边的图标或对每个文件执行 git add 将是一个耗时且繁琐的操作。

批量操作

当你想将多个文件从 未暂存的更改 区域移动到 暂存的更改 区域时,可以使用以下方法:

  • GUI:按 Ctrl + I,如果有提示要求添加未知文件,选择 ,而不是点击每个文件旁边的图标。

  • CLI:命令 git add . 相当于在使用 GUI 模式时按 Ctrl + I。它会一次性将所有更改添加到暂存区。通配符字符(如 *.docx)的使用也是允许的。

    git add .
    git add *.docx
    
    

使用这些选项,我们可以消除每次添加单个文件的繁琐过程,但这违背了排除临时文件不被添加到仓库的目标。那么,如何将批量操作的强大功能与排除某些文件或文件类型的控制结合起来呢?

.gitignore 来拯救我们

为了智能地处理这个问题,Git 提供了一个解决方案。通过在仓库中创建一个名为 .gitignore 的文件,并输入文件名或文件名模式,我们可以让 Git 忽略它们。

行动时刻 - 使用 .gitignore

  1. 打开文本编辑器并输入以下内容:

    ~*.*
    
  2. 将文件保存为 .gitignore,并将其放在我们的 Workbench 仓库中,如下图所示:行动时刻 - 使用 .gitignore

    保存文件时,确保从 保存类型 下拉框中选择 所有文件 选项。

刚才发生了什么?

我们已经成功地指示 Git 忽略 Word 应用程序创建的临时文件。现在,可以刷新你的 GUI 或在 CLI 中获取状态。你在 未暂存的更改 区域中唯一看到的添加将是 .gitignore 文件,而不是临时文件。

每当 Git 想要检查仓库中是否有新的文件(未跟踪的更改)时,它会检查 .gitignore 文件以排除文件。通过观察临时文件的名称(~$ontent.docx),我们可以猜测,Word 创建的任何临时文件都会以特殊字符 ~ 开头,因此我们在 .gitignore 文件中加入一条规则,匹配所有以该字符开头的文件。.gitignore 文件中的条目 ~*.* 表示排除任何以字符 ~ 开头的文件,且无论其扩展名是什么。

注意

尽管.gitignore文件的添加本身是一次性操作,但该文件中的排除规则必须根据仓库中添加的文件的性质和内容类型进行更新。

撤销添加

在提交之前的任何时刻,如果你想将文件从暂存区更改移动到未暂存区更改,你可以执行以下操作:

  • 图形用户界面(GUI):点击位于暂存区更改面板中相应文件名旁边的勾选图标

  • 命令行界面(CLI):使用以下命令:

    git reset filename.extension
    
    

提交已添加的文件

直到现在,我们已经初始化了仓库,将文件添加到仓库,并通过暂存它们来确认这些更改(将它们推送到暂存区更改阶段),但在提交之前,这些文件还不算是处于版本控制之下。(这是因为只有在你提交时,Git 才会记录文件的内容,并将其保存为该文件的一个新阶段,这样下次它就能通过比较现有版本与最后保存的版本来识别文件是否有任何内容更改)。

这是 Git 术语中的一个新概念:这个过程叫做提交

所以让我们先进行一次文件的初始提交。当你第一次将文件添加到仓库并进行提交时,Git 会注册这个新文件。之后对这些文件进行的任何提交,都将基于仓库中相同文件的前一个版本进行更改提交。

尽管 Git 执行你的命令,它有一个健康的习惯,就是在每次提交时都加上评论,这样它就能根据各种文件类型的行为和情绪建立一个基于观察模式的人工智能系统,从而自动化你的日常操作。

基本上,你在每次提交时提供的评论仅仅是为了帮助你自己或其他阅读你仓库历史的人理解文件的目的和/或变更。

最好写一个评论,内容可以是任何有用的信息。在了解了背后的理论后,我们来看一下实际操作。

行动时间 – 在 GUI 模式下提交文件

  1. 让我们在操作面板下的初始提交信息标签提供的空白处输入本次提交的理由。 行动时间 – 在 GUI 模式下提交文件

  2. 点击提交按钮。提交完成后,Git 会在底部显示状态消息,格式为状态提交 ID: 你的提交评论行动时间 – 在 GUI 模式下提交文件

    提交 ID 只是一个唯一标识符,Git 用它来在未来回忆起你的提交。我们将在后续功能中看到评论和 Git 提交 ID 的其他用途。

行动时间 – 在 CLI 模式下提交文件

假设你已经按照初始化过程中的步骤打开了命令提示符,给 Git 输入以下命令:

git commit –m "your comments for the commit"

行动时间——在 CLI 模式下提交文件

如果你看到一个类似之前提到的状态消息,那是确认的标志。

刚刚发生了什么?

你已经成功将文件提交到仓库。以后对这些文件的任何更改都会是相对的。

让我们看看当你更改仓库中文件的内容时会发生什么。

我突然觉得我需要传达 Git 如何影响我的工作,而不仅仅是在我们的content.docx文件中说“它是一个简单、快速且出色的版本控制系统”。所以我将其替换为文本“在处理频繁更改的文件时,Git 大大提高了我的工作效率。”

Git 会跟踪变化,并在我们询问状态更新时向我们展示。

行动时间——在 GUI 模式下重新扫描

如果你已经打开了Git Gui,那么只需点击重新扫描按钮,就能从 Git 获取最新的状态更新。如果你还没有打开该工具,我想你知道如何打开它。

如你所见,它显示了在未暂存的变更区域中,从早期状态变更的文件。

行动时间——在 GUI 模式下重新扫描

你刚刚学会了如何暂存文件的更改并提交它,所以剩下的交给你了。只是让你知道,我这次提交的消息是“添加了更多解释为什么使用 Git 的文本”。

注意

内容面板显示了你在文件中所做的更改。与文件的先前版本相比,绿色文本表示新增,红色文本表示删除。我们将在后续章节中更详细地探讨这一点。

对于命令行爱好者,我们从添加文件开始就一直在使用status命令来检查仓库的状态,这里也不例外。使用git status命令来了解仓库中的变更。

检出

目前为止,我们一直在通过向 Git 发出命令,使用我们学到的概念来前进版本管理。不管你到目前为止学到的是什么,这只是一个单向的过程!

为了更清楚——如果你不知道如何使用 Word 应用程序中的撤销和重做功能,你会有什么感受?

所以,让我们学习如何使用 Git 根据内容回到过去。

检出是一个过程,它帮助你在你做出的任何单个文件或仓库中一组文件的更改之间来回切换。

你可以回到之前做过的提交,查看单个文件或一组文件的内容,并在瞬间返回到该文件的最新版本及其最新变更。

这有多好呢?

除了查看早期提交中的文件内容外,还有许多其他操作,我们将在后续章节中讨论,主题是分支。

学习了背后的理论之后,让我们付诸实践。

行动时间——在 GUI 模式下进行检出

  1. 选择Repository菜单,然后在打开的Git Gui屏幕中选择Visualize All Branch History选项以打开gitk;你将看到如下屏幕:该是行动的时候了——使用 GUI 模式进行检出

    Gitk 是一个强大的图形化仓库浏览器,允许我们执行各种操作,如可视化仓库、标签、重置等。

    再次提醒,不要担心屏幕上大量的信息;我们将一步步地进行。

    现在让我们聚焦在左上角的窗格,这里显示了路径,其中彩色圆圈代表你所做的提交;圆圈旁边是你的注释。

    紧接其下的是一个名为SHA1 ID的字段,它显示了你所选择的提交的提交 ID。如我们之前所讨论的,我们将使用此提交 ID 来标识某个特定的提交,回到过去。

  2. 选择我们的第一个提交,内容为Initial commit to showcase the commit functionality of Git,即可在SHA1 ID字段中显示其提交 ID,并复制该 ID(通过双击选中整行内容并按Ctrl + C复制它)。

  3. 切换到Git Gui并选择Branch | Checkout打开检出操作窗口(或者你可以按Ctrl + O)。将你复制的 SHA1 ID 粘贴到Revision Expression文本框中,并点击Checkout按钮,如下截图所示:该是行动的时候了——使用 GUI 模式进行检出

  4. 点击弹出窗口中的OK按钮(我们将在后面的章节中讨论“分离检出”一词,属于分支相关内容)。该是行动的时候了——使用 GUI 模式进行检出

刚刚发生了什么?

你已经成功回到了过去。如果现在打开我们的文档,你可以看到我们最初创建的内容。

在任何时候,你都可以通过选择Branch | Checkout | Localbranch来恢复到最新的更改;确保选择了master,然后点击Checkout按钮。

刚刚发生了什么?

如你所见,你已经跳回到了带有最新更改的内容。

是的,厉害吧,怎么样?

该是行动的时候了——使用 CLI 模式进行检出

  1. 让我们再学习两个 Git 命令,丰富一下你的 Git 术语。

    Git log
    Git checkout ___commit_id___
    
    

    Git log 用于显示仓库的历史记录;它为我们提供如提交 ID、作者、日期以及我们给出的提交注释等信息。

    我们需要这个提交 ID 以便稍后使用。

    该是行动的时候了——使用 CLI 模式进行检出

    不用担心记住 40 个字符的序列。我们的魔法棒 Git 会为你完成剩下的工作,只要你提供前五个字符,它就能帮你识别某个提交。

  2. 让我们看看实际操作。该是行动的时候了——使用 CLI 模式进行检出

    现在你已经回到了先前的提交,文件将包含该提交的内容。你现在可以查看文件的内容。

    注意

    当你回到先前的提交时,你就处于悬空状态;现在对文件所做的任何更改,一旦回到主分支将会丢失。稍后我们将通过一个叫做分支的概念来处理这种情况。

  3. 要返回到最新的更改,运行git checkout master;这将带你回到最新的更改。该行动了——使用 CLI 模式查看

    如果你看到与前图类似的消息,那么你已经回到了最新的更改。你可以再次查看文件内容。

重置

与我们之前学习的 checkout 功能不同,重置是针对内容的永久回溯。重置有三种类型。

  • 软重置

  • 硬重置

  • 混合重置

我们忽略所有在特定提交后所做的更改的目标只能通过执行硬重置来实现,因此我们将在本章中仅学习硬重置类型。

该行动了——使用 GUI 模式重置

  1. 选择仓库菜单,然后在打开的Git Gui界面中选择可视化所有分支历史选项以打开 Gitk。

  2. 在左上方的面板中,你可以看到你的仓库历史是如何演变的。目前它是线性的,包含两个提交。现在右键点击第一个提交,该提交信息为最初提交以展示 Git 的提交功能,然后选择重置主分支至此选项,如下图所示:该行动了——使用 GUI 模式重置

  3. 你将看到一个确认对话框,其中有三种重置选项,如前所述;选择硬重置并点击确定按钮,如下图所示:该行动了——使用 GUI 模式重置

  4. Gitk 应会自动重新加载,以显示我们仓库的更改历史。如果它没有自动重新加载,我们可以手动执行,通过选择文件|重新加载选项或按Ctrl + F5

该行动了——使用 CLI 模式重置

重置可以通过在 CLI 模式下使用以下命令来完成:

git log
git reset --hard 8b4fe

该行动了——使用 CLI 模式重置

Git log用于查看特定提交的提交 ID,命令git reset --hard your_commitid则告诉 Git 你希望重置自该提交 ID 之后发生的所有更改。

刚刚发生了什么?

恭喜!我们已经成功地将仓库永久重置到早期状态。你可以通过检查文件内容和仓库日志来验证这一点。

Git 帮助

Git 是一个持续学习的平台。无论你已经多么熟练,每次使用时,你都有可能学到新东西,因为总有多种方式可以完成同一件事。你需要用 Git CLI 执行基本操作的任何命令总是遵循以下模式:git operation_keyword 参数和/或值

当我们说几乎所有的操作在 Git 中都是本地/离线时,我们是认真的!

Git 内置了帮助模块,可以帮助你在不确定如何使用某个命令或命令本身时查阅相关内容。你可以使用以下命令立即参考内置文档:

  • git help 获取命令行参数列表以及最常用的操作关键字和描述

  • git help operation_keyword 获取该特定操作关键字的完整参考表,并在你的默认浏览器中打开

尝试一下英雄 – 使用帮助模块

尝试列出常用的 Git 命令,挑选一个命令,尝试打开它的帮助页面

总结

我们已经学会了如何在 GUI 和 CLI 模式下做以下操作:

  • 初始化仓库

  • 配置 Git

  • 将文件添加到我们的仓库中

  • 忽略添加到我们仓库中的不需要的文件

  • 提交新文件/修改现有文件

  • 检查之前的提交,以防我们需要引用旧数据

  • 重置仓库以永久返回到早期记录的状态

  • 使用内置帮助模块

很快你将学会如何做以下事情,以及更多内容:

  • 维护多个环境,并在它们之间切换,就像它们是多个用户账户一样

  • 从先前的提交继续修改,从而保持多个路线(在技术上称为分支)来源一致

第四章:分担负载——使用 Git 进行分布式工作

到目前为止,我们一直在使用单个本地仓库。现在是时候更进一步,探索 Git 在前几章中提到的最重要功能之一:使用 Git 进行分布式工作。

本章你将学习协作开发的基本要点:

  • 如何通过互联网和内联网共享你的文件/项目

  • 各种概念包括:

    • Git clone

    • Git fetch

    • Git merge

    • Git pull

    • Git push

    • Git remote

这些概念涉及在互联网和内联网中逐步且持续地共享你的文件。

为什么要共享你的文件

让我们继续使用之前帮助我们理解 Git 基础的计算机游戏类比。

场景 1:单人游戏

想象一下你最喜欢的游戏,它允许你在任何时候将游戏状态保存到你的系统中,并在之后继续游戏。现在假设你身处远程地点,能够访问一台电脑,但无法继续游戏,因为保存的游戏文件无法从那台电脑访问。

将这种情况应用到你的数据文件上。平均来说,我们大部分时间都待在两个到三个不同的地方;想一想,如果能够在不同系统之间继续工作,而不必在每次切换到新系统时重新开始工作,会有多么高效。

场景 2:多人游戏——一次一个

想象一下你最喜欢的冒险游戏,它有多个关卡。假设你在某个关卡卡住了,不知道该如何前进。经过无数次徒劳的尝试后,你突然意识到你的朋友在那个关卡是专家,你希望借助朋友的帮助。于是,你快速将游戏的最后保存状态文件分享给他,让他帮你完成那个关卡,保存进度,并将文件推送回给你,这样你就可以继续游戏了。

当你与数据文件一起工作时,尤其是在团队合作的情况下,不同的人负责完成较大任务的不同部分,以最终产生单一结果时,同样的情况也可能适用。另一种可能性是,你希望领域专家处理工作的特定部分,等等。

当涉及通过网络共享文件时,只有两种模式可供选择。

  • 互联网

  • 内联网

基于接近程度选择合适的方法。

孩子的游戏——为远程源进行推送和拉取

在深入探讨分布式文件系统(如场景 1)或协作开发(如场景 2)之前,是时候将五个新的术语加入到我们的 Git 词汇中,分别是:

  • Git clone

  • Git fetch

  • Git merge

  • Git push

  • Git remote

让我们快速理解这些术语的含义,并看看它们可以在哪里应用。

在这里,克隆并不是被禁止的

是的,我们现在谈论的是 Git 的clone功能。Git clone 用于当我们需要现有仓库的精确副本或复制品时,并且需要包括其历史记录。

因此,可能会产生一个问题:所有克隆的仓库是如何保持同步的。

好吧,答案就在剩下的四个 Git 命令中,它们在git clone之后列出,即git fetchgit mergegit pushgit remote

  • Git fetch:此命令用于从源获取更改到目标。

  • Git merge:合并用于将两个工作区(技术上称为分支)合并为一个。它通常用于将当前用户的工作区与远程用户的工作区合并,在从远程源获取更改之后。

    注意

    Git pull:执行 git pull 将内部执行 git fetch,然后是 git merge。因此,它作为 fetchmerge 的替代方案使用。

  • Git push:此命令用于将我们的内容从源推送到目标。

  • Git remote:此命令用于管理源和目标。它指定了你可以如何与他人共享你的工作,反之亦然。

任何使数据共享成为可能的操作,都需要通过 git remote 建立远程连接。在这里,git fetchgit pushgit pull 都利用了 git remote 建立的远程连接。

现在我们已经了解了一些概念,让我们看看如何将它们付诸实践。

场景 1:解决方案

我们将学习如何在前面提到的场景 1中利用 Git 为你提供服务。

公有化——通过互联网共享

有几个在线 Git 托管服务提供商可供使用,且定价模式各不相同。大致来说,有些提供有限功能的免费服务,并要求你为额外使用支付费用;另一些提供完整功能的免费使用期限,之后要求你选择支付计划继续使用;还有一些则结合了上述两种模式。

从现在起,我将选择 Bitbucket,一个可靠的第三类服务提供商,带你了解与互联网共享相关的概念。

Bitbucket 是 Atlassian 的一款产品,目前提供免费的无限公共和私有仓库,唯一的限制是你与哪些人共享私有仓库的数量。这意味着我们可以与五个具有读写权限的人免费共享我们的私有仓库。

注意

还有一些其他竞争产品,如 GitHub、Codaset 等。我们选择 Bitbucket 是因为它提供免费的私有仓库。

一点关于 Bitbucket

让我们快速注册他们的服务;打开浏览器,访问 bitbucket.org,点击定价与注册按钮,然后点击免费配额下的第一个注册按钮。接下来,你会进入注册页面,选择一个个人账户类型(也可以让整个团队使用一个账户),然后分别在用户名密码字段中输入你的用户名和密码,再在电子邮件地址字段中输入你的有效邮箱地址,而名字姓氏是可选字段,如下图所示:

Bitbucket 的简要介绍

完成相关程序后,你可以期待收到来自 Bitbucket 的确认邮件,以验证你的电子邮件地址。

注意

作为完成整个注册流程的替代方法,如果你有 OpenID 账户,可以使用它进行登录。

Bitbucket 的一个亮点是它几乎对所有操作都提供了键盘快捷键,像 Gmail 一样。类似 Gmail,你可以按 Shift + ? 查看可用的快捷键列表。以下是一些常用快捷键的汇总,供你参考:

键盘快捷键组合 操作
? 显示键盘快捷键帮助。
c + r 创建仓库。
i + r 导入仓库。
g + d 进入仪表盘。
g + a 进入账户设置。
g + i 进入收件箱。
/ 聚焦到站点搜索框。将光标定位到搜索框中。
Esc 关闭帮助对话框或移除表单字段的焦点。
u 返回到你刚刚通过快捷键浏览过的页面,就像浏览器的返回按钮一样,它将带你回到你在 Bitbucket 中浏览过的页面。

让我们通过在你的账户中创建一个新的仓库来开始我们的旅程。你可以按 c + r,或者点击顶部菜单中的创建仓库选项,或者直接点击右侧仓库块中的创建仓库链接,这将带你到一个页面,指导你创建新的仓库(repo 是仓库的常用简写)。

字段名称 原因
名称 online_workbench 我们将把桌面上的Workbench仓库导入到这个在线平台。
描述 一个在线 Git 仓库,用于展示 Git 的协作功能 这是你仓库的简短描述。你可以在这里写下一个描述,最好能够表达仓库的用途。
访问级别 已勾选 私有仓库仅对你和你授权的人可见(稍后会详细讲解)。如果此框未勾选,所有人都可以看到你的仓库。
仓库类型 Git Bitbucket 支持 Git 和 Mercurial 版本控制系统。由于我们要导入一个 Git 仓库,所以选择 Git。

输入字段中的值,如下图所示:

Bitbucket 介绍

点击创建仓库按钮以完成仓库创建过程。现在你已经拥有一个空仓库,Bitbucket 会提示你立即采取行动,如下图所示:

Bitbucket 介绍

这里有两种不同的启动选项。

  • 在我们的机器上创建一个新目录,将其初始化为仓库,并将其链接到我们刚刚创建的远程 Bitbucket 仓库,通过我从头开始链接来表示。

  • 跳到后面的部分,即将我们现有的仓库链接到远程 Bitbucket 仓库并推送内容,这通过我有代码要导入链接来表示。

由于我们已经创建了仓库,让我们选择第二个选项,它将引导我们到如下屏幕:

Bitbucket 介绍

我们现在进入了核心部分。屏幕上显示了 CLI 用户链接Workbench仓库从桌面到 Bitbucket 中的online_workbench仓库的说明。

操作时间 – 使用 CLI 模式添加远程源

将远程源链接或添加到你的仓库(又一个 Git 术语)是一个简单的过程。启动命令行界面并输入以下命令:

cd /path/to/your/Workbench/repo
git remote add origin https://your_bitbucket_repo_identity_here/online_workbench.git
git push -u origin master

执行git push命令后,系统会提示你输入 Bitbucket 账户密码以完成该过程,如下所示:

操作时间 – 使用 CLI 模式添加远程源

如果你在窗口中看到类似的消息,则表示链接和传输成功。

刚才发生了什么?

我们刚刚为Workbench仓库创建了一个远程链接,并将我们的文件推送到online_workbench仓库,使它们在线可用,从而开启了分布式文件系统的大门,使用 CLI 模式。

git remote add是用来将通过路径识别的 Git 仓库添加到当前仓库配置文件中的命令,以便在一个仓库中的更改可以在另一个仓库中被追踪。可以说,origin不过是远程仓库路径的别名。

参数–u origin master,与git pull一起使用,用于将仓库的推送和拉取操作默认指向指定的远程分支。

提示

如果没有使用–u,那么每次执行拉取或推送请求时,我们都需要在请求中指定origin master。现在,我们只需要输入git push进行推送,输入git pull进行拉取。

这意味着只要你可以访问安装了 Git 和应用程序软件的计算机,你就可以随时随地继续工作(在这种情况下我们处理的是 Word 文档,应用软件是 Microsoft Word)。

操作时间 – 使用 CLI 模式从任何地方恢复工作

现在,让我们进入第二阶段,我们想要从远程机器恢复工作。

这里只涉及三个阶段。

  1. 从服务器克隆仓库。

    git clone https://raviepic3@bitbucket.org/raviepic3/online_workbench.git /path/where/you/would/like/the/clone_to_be
    
    
  2. 对需要修改的文件进行更改。

  3. 添加/暂存文件中的修改,提交并推送。

    git add *
    git commit –m 'Your commit message'
    git pull
    git push
    
    

    提示

    作为git pull的替代方法,我们也可以使用git fetch,然后再执行git merge @{u}

刚刚发生了什么?

我们刚刚实践了通过有效处理如场景 1中所述情况来最大化生产力的解决方案。

Git add *会暂存/添加你所有的更改,这些更改通过git commit命令被确认并记录。git pull用来检查服务器上是否有未同步的更新;如果有,系统会将它们同步,然后再执行git push,该命令将你在本地仓库中所做并提交的更改推送到服务器,更新服务器上的文件。

注意

你可能会想,为什么在我们唯一的目的是将更新的文件推送到服务器时,要先执行git pull再执行git push。这个问题非常好——请保持这个思考,等到我们讨论分支概念时,你会更清楚地理解。

行动时间——使用 GUI 模式添加远程源

通过 Git GUI 将远程源链接或添加到我们桌面上的Workbench仓库并同步内容,步骤如下:

  1. 打开你桌面上的 Git GUI 窗口,查看我们的Workbench仓库。

  2. 在你的 GUI 窗口中,点击远程菜单中的添加选项。行动时间——使用 GUI 模式添加远程源

  3. 这会打开添加新远程窗口,在其中输入以下信息:

    字段名称
    名称 origin
    位置 your_bitbucket_repo_identity_here/online_workbench.git
    进一步操作 现在不做其他操作
  4. 如下图所示,点击添加按钮:行动时间——使用 GUI 模式添加远程源

  5. 我们现在已经成功地将远程源添加到Workbench仓库。

  6. 要将我们的代码推送到online_workbench仓库,打开相同的远程菜单并选择推送选项,这会将你引导到推送分支窗口,如下图所示:行动时间——使用 GUI 模式添加远程源

  7. 默认情况下,master会被选中为源分支,而在目标仓库下的远程选项列表框中,origin会被选中。保持默认设置,点击推送按钮,等待片刻,系统会提示你输入 Bitbucket 账户密码,如下图所示:行动时间——使用 GUI 模式添加远程源

  8. 通过身份验证成功后,你的内容将与 online_workbench 仓库同步,以下截图可以证明这一点:行动时间 – 使用 GUI 模式添加远程 origin

    这表示你的本地 Workbench 仓库的主分支与 online_workbench 仓库的主分支已同步(更多关于分支的内容将在后续章节讲解)。

发生了什么?

我们刚刚为我们的 Workbench 仓库创建了一个与 online_workbench 仓库的远程连接,并将我们的文件推送到它,使其在线可用,从而为分布式文件系统打开了大门,使用的是 GUI 模式。

现在,如果你在浏览器中打开你的 Bitbucket 账户,你会看到如以下截图所示,你的历史记录已经更新:

发生了什么?

这意味着,如果你能访问安装了 Git 和应用软件的计算机,你可以从任何地方继续工作(在这个例子中是 Microsoft Word,因为我们处理的是 Word 文档)。

继续浏览可用的不同标签页,使自己熟悉它。一旦你完成了这部分,我们就可以进入下一部分,看看如何从分布式位置恢复我们的工作。

行动时间 – 使用 GUI 模式从任何地方恢复你的工作

在这里,我们通过创建在线仓库、远程连接并同步本地文件到在线仓库,获得了之前所做的工作的好处。从任何你能接触到的机器上恢复工作是一个简单的三阶段过程。

  1. 从服务器克隆仓库。

    1. 打开 Git GUI 并选择克隆现有仓库选项,如下图所示:行动时间 – 使用 GUI 模式从任何地方恢复你的工作

    2. 这将引导你到相应的窗口,在那里你将被提示输入源位置目标目录,并按以下方式输入相应的值:

      字段名称
      源位置 your_name@bitbucket.org/username/online_workbench.git
      目标目录 /Path/where/you/want/to/have/the_cloned_repository_for_ease_of_work

      如下图所示;点击克隆按钮:

      行动时间 – 使用 GUI 模式从任何地方恢复你的工作

    3. 一旦克隆过程开始,你将被提示输入你的 Bitbucket 账户密码。通过身份验证后,你将获得一个克隆的仓库,里面包含你可以继续工作的文件。

  2. 根据需要对文件进行修改。

  3. 添加/暂存文件中所做的修改,提交、获取、合并并推送。

    1. 我们已经知道如何将修改添加/暂存到文件并提交到仓库。那么,现在让我们从 fetch 开始。要执行 fetch 操作,请进入远程 | | Origin菜单选项。这将弹出远程 fetch 窗口,提示你输入 Bitbucket 账户密码,如下截图所示:随时随地使用 GUI 模式恢复工作

      输入正确的密码并成功认证后,如果服务器中有任何文件的更新尚未同步到本地仓库,这些更改将会被同步。

      随时随地使用 GUI 模式恢复工作

      上一张截图展示了同步过程和同步状态。同步成功后,我们可以关闭窗口并继续合并这两个工作空间。

    2. 合并两个工作空间,即本地主分支(你的本地工作空间,已用来做更改)和远程主分支(服务器上的工作空间),可以通过选择合并 | 本地合并菜单选项来执行。这将打开一个本地合并窗口,如下截图所示:随时随地使用 GUI 模式恢复工作

      默认选中的选项是origin/master;保持默认,点击合并按钮。

    3. 如果在合并时没有冲突,你应该会看到如下截图中显示的成功消息:随时随地使用 GUI 模式恢复工作

      这标志着你已经成功将服务器中的内容与本地内容同步。现在,让我们使用git push功能,将你的内容上传到服务器中,这个功能可以通过远程 | 推送菜单选项访问。

刚才发生了什么?

我们刚刚实践了一个有效的解决方案,通过使用 Git GUI 来有效处理场景 1中描述的情况,从而最大化生产力。

场景 2:解决方案

处理场景 2现在变得非常简单,因为我们已经知道如何处理场景 1。与前者相比,场景 2的唯一新增内容是多个用户参与同一个仓库。

邀请用户访问你的 Bitbucket 仓库

邀请你的朋友访问你的游戏文件,让他帮你完成那个关卡,这是一个非常简单的两步操作,具体如下:

  1. 从你的仓库主页,点击分享图标或邀请按钮,如下截图所示:邀请用户访问你的 Bitbucket 仓库

  2. 这时你需要输入你想要邀请或共享你的仓库的用户的详细信息。如果是现有用户,你可以输入他/她的用户名;如果是新用户,你可以输入他/她的电子邮件地址,并点击如以下截图所示的添加按钮:邀请用户到你的 Bitbucket 仓库

  3. 现在,用户名/电子邮件地址已添加到列表中,你将被提示指定你所添加用户的访问权限。点击写入按钮,然后点击共享按钮,如以下截图所示:邀请用户到你的 Bitbucket 仓库

  4. 就是这样!你将在页面顶部看到一个成功消息,如以下截图所示,以确认共享:邀请用户到你的 Bitbucket 仓库

    而你添加的用户将收到一封电子邮件,告知他/她你希望与他/她共享你的仓库,如以下截图所示:

    邀请用户到你的 Bitbucket 仓库

  5. 点击链接后,你将有两个选项。

    • 注册:如果你的朋友是 Bitbucket 的新用户,他/她需要按照本章前面讨论的注册流程进行注册。注册后,你将被引导到仪表盘。

    • 使用现有用户名登录:如果你的朋友已经有 Bitbucket 账户,登录凭据后,他/她将被提示接受共享仓库,如以下截图所示:

    邀请用户到你的 Bitbucket 仓库

    当点击接受邀请按钮时,用户将被带到他/她的仪表盘。

    仪表盘将包含一条屏幕通知的确认信息,如以下截图所示,同时,你注册的电子邮件地址也会收到一封电子邮件,邮件中包含你被授予访问权限的仓库的详细信息:

    邀请用户到你的 Bitbucket 仓库

    同时,这个用户会收到一封电子邮件,邮件中包含他/她可以访问的仓库的详细信息。

刚才发生了什么?

完美!我们已经成功地实践了一个有效的解决方案来处理情景 2的案例。

这意味着你可以将一个较大的任务拆分成更小的任务,并与其他人共享这些任务和相关文件,以便他们能够填补各自的部分,最终产生一个共同的结果。

保持本地 – 通过内联网共享

有些情况下,你可能在局域网内工作,比如在同一栋楼的不同楼层,而因为各种原因(例如成本、上传下载带宽消耗、安全性等),你不想将文件上传到网上。

在这种情况下,有几种方法可以处理这种情况,最常用的几种方法是:

  • Gitolite 服务器

  • 公共共享目录与裸仓库

我们将查看在共享目录中创建裸仓库的过程,这样它就可以在你的网络内共享。

裸仓库的概念

一旦说到你需要一个裸仓库来与他人共享文件时,你可能会心中有一些基本问题:

  • 什么是裸仓库?

  • 为什么我们需要这样一个东西来与他人共享我们的仓库文件?

让我们逐一查看它们。

  • 裸仓库:裸仓库是没有工作目录的仓库。

  • 工作目录:这只是一个包含源文件的目录,例如,content.docx 位于 Workbench 目录中。

    .git 目录的内容将是整个目录的内容,如果它是一个裸仓库的话。

  • 为什么是裸仓库:想象一下一个场景,多个用户同时在同一个文件上工作。那么,当你在从仓库中更改某些内容时,另一个正在处理同一文件的人做了他自己的更改并推送到你的仓库时会发生什么?

    你的文件内容可能会被更改,或者文件本身可能会根据另一端执行的操作而不存在,而你则会打开该文件进行操作。

    处理这种情况时会引起很多混乱,因此创建 Git 的人采取了聪明的做法,通过实现裸仓库概念来避免这种情况。这个裸仓库充当所有这些克隆和包含工作目录的源仓库之间的中介。所以你不能仅仅从一个克隆推送到克隆的源,如果源包含工作目录的话。

    裸仓库的概念

让我们创建一个裸仓库,并快速查看它以便更好地理解。

行动时间 – 在 CLI 模式下创建裸仓库

创建裸仓库的命令与克隆仓库时使用的命令相同,唯一的区别是 --bare 参数,它是关键。

git clone --bare C:\Users\raviepic3\Desktop\Workbench C:\generic_share\Bare_Workbench

在控制台中执行上述代码应该会在你的公共共享文件夹 generic_share 中创建一个 Workbench 仓库的裸克隆。

行动时间 – 在 GUI 模式下创建裸仓库

使用 GUI 从已经存在的仓库创建裸克隆是一个简单的过程。你需要做的只是:

  1. 复制现有仓库中的 .git 目录,并将其粘贴为 different_name.git(你想给新裸仓库取的任何名称)到仓库外部。

    在我们的例子中,我们有一个非裸仓库,名为 Workbench,路径是 C:\Users\raviepic3\Desktop\,其中包含 content.docx。现在,我想使用 GUI 从这个仓库创建一个新的裸仓库。我将复制 C:\Users\raviepic3\Desktop\Workbench\.git 并将其粘贴为 C:\generic_share\Bare_Workbench.git

  2. 使用文本编辑器打开 Bare_Workbench.git 中的 config 文件,找到 bare = false 这一行,并将 false 字符串替换为 true

  3. 保存并退出。

发生了什么?

通过 CLI 或 GUI 执行之前的操作后,我们已经从 Workbench 仓库创建了一个裸仓库,并将其存放在名为 generic_share 的目录下,仓库名称为 Bare_Workbench,其内容如下图所示:

发生了什么?

为了更好理解,下面的图示展示了两个仓库之间的内容对比:

发生了什么?

如果你处在本地网络中,可以通过控制共享文件夹 generic_share 的可见性来控制谁能访问仓库,方法和你控制网络中其他共享文件夹的可见性一样。

总结

我们已经学习了什么是以及如何:

  • 克隆一个仓库

    • 区分裸仓库和非裸仓库,了解它们的使用和实现方式
  • 将远程仓库添加到本地仓库

  • 获取、合并并推送内容到添加的远程仓库或克隆的仓库

    • 拉取操作及其替代方法

此外,你还学会了如何:

  • 通过以下方式在互联网和内联网中共享你的仓库:

    • Git 命令行界面(CLI)

    • Git 图形用户界面(GUI)

  • 你也已经准备好开始使用从第一天开始所学的概念, 因为你已经拥有一个 Bitbucket 账户, 通过这个账户你可以创建和管理无限量的公共和私有仓库,并与最多五个用户共享这些仓库,完全不需要花费一分钱。

第五章。成为木偶大师 – 学习 Git 功能控制的高级特性

在通过学习、练习和验证目前所学概念打下坚实的基础后,我们已准备好学习一些高级特性,这些特性将基于我们的基础知识,扩展功能使用。

在本章中,你将学习一些概念,帮助你完成以下任务,涉及到你仓库中的内容:

  • Shortlog

  • 日志搜索

  • 清理

  • 标签

为什么要学习这些高级特性

为什么你在Contra中需要 "S" 这样的能力,或者在Counter-Strike中需要 M4 步枪,明明你已经有了默认武器?

虽然你可以仅凭给定的基本工具达成最终目标——击败对手,但使用那些专业工具可以更轻松地实现这一目标,这就是原因所在。

同样,这些我们即将学习的功能将为我们提供简单的控制 Git 的方法,根据不同的情况提供相应的数据,你可以根据自己的工作角色决定在何时使用这些方法。而且,随身携带一些现成的技巧,也能让你在需要时轻松应对,使你成为这个工作的合适人选。

前提条件

为了更好地学习这些概念,我们需要一个包含多个提交和多人参与开发的仓库。因此,我们将从著名的 Git 托管网站(如 GitHub 或 Bitbucket)下载一个仓库。

在这里,我下载了一个名为 cappuccino 的项目,并对仓库进行了简化,以适应我们的目的。

提示

下载示例代码

你可以从你在 www.PacktPub.com 的账户中下载所有购买的 Packt 书籍的示例代码文件。如果你是在其他地方购买本书,可以访问 www.PacktPub.com/support,并注册以便直接将文件通过电子邮件发送给你。

你也可以从之前指定的 Git 托管网站下载你自己的项目文件来进行学习。

Shortlog

尽管信息就是财富,但有时过多的信息会失去它的实际用途。想想电子表格应用中与筛选选项相关的价值。Shortlog 就是 Git 中的一个功能,可以限制查看日志时显示的信息量。它按字母顺序排列所有参与构建仓库数据的用户,并显示他们提交的次数以及每次提交的描述。

行动时间 – 熟悉 shortlog

现在,让我们快速打开你从 cappuccino 仓库中的 CLI 窗口,试试以下命令:

git shortlog

这应该会给出如下截图中的输出:

行动时间 – 熟悉 shortlog

刚才发生了什么?

我们刚刚列出了由不同作者负责的 24 次提交的评论;这些作者按字母顺序排列,一次性显示。听起来简洁又易于展示,对吧?

这还没结束!短日志有一些已定义的参数,可以用来重新排序或缩小你的搜索范围,从日志中提取特定的信息。

行动时间 - 参数化短日志

只需在之前的命令中添加参数–n

git shortlog –n

你应该会看到如下所示的输出:

行动时间 - 参数化短日志

发生了什么?

通过添加–n(编号)参数,得到的输出将根据提交次数进行加权,而不是按字母顺序排序。

现在我们有了基本概念,让我们快速过一遍剩下的可以使用的参数。要获取附加到现有输出中的作者电子邮件等元数据,我们将使用–e参数。

git shortlog -e

你可以期待如下所示的输出:

发生了什么?

想知道如何快速获取仓库从不同用户处经历的阶段/提交数吗?让我来介绍一下-s参数,它可以为我们提供每个用户的提交计数历史。

git shortlog –s

发生了什么?

总结一下这些参数及其功能,请参考以下表格:

参数 操作描述
简写形式 完整形式
--- ---
-n --numbered
-s --summary
-e --email
-h --help

日志搜索 - git log

继续短日志的内容,给你的工具箱增加一些能根据需要提取数据的利器。你可能知道从前面的章节中了解到 Git log 命令仅用于查看提交 ID 和相关元数据。但你将在这里学到的是日志命令本身的灵活性,以及它所包含的功能。

行动时间 - 跳过提交日志

让我们快速在之前从cappuccino仓库打开的 CLI 窗口中尝试以下操作:

git log --skip=2

这应该会生成如下所示的输出:

行动时间 - 跳过提交日志

发生了什么?

尽管输出一开始可能看起来与通常的git log输出类似,但当你进行对比时会发现,你已经跳过了列出的最后两次提交。

发生了什么?

--skip=number使这一切成为可能。number参数可以接受任何整数值,用来跳过相应数量的提交。

有很多情况,你可能希望基于日期过滤内容。这可以通过git log中的since/afteruntil/before操作符实现。

行动时间 – 按日期范围过滤日志

执行git log后,选择两个日期,我们将使用这两个日期来过滤接下来的命令:

git log --since=2008-09-08 --until=2008-09-09

这应该会产生如下输出:

行动时间 – 按日期范围过滤日志

刚才发生了什么?

使用--since=date操作符可以过滤从指定日期开始的日志,并通过–until参数限制在指定日期之前的日志。

注意

请注意,你还可以指定相对日期,比如–since=2.days--since=3.months来过滤输出。

如果你在想是否有一种方法可以根据关键词在提交中进行搜索——是的,可以,使用git log–grep参数。

行动时间 – 搜索单词/字符匹配

在 CLI 窗口中输入以下命令:

git log --grep="Merge"

这应该会产生如下输出:

行动时间 – 搜索单词/字符匹配

刚才发生了什么?

我们使用与git log关联的grep工具,搜索给定的关键词,在不同提交的提交信息中查找"Merge"。

注意

要执行不区分大小写的搜索,可以在之前的命令后添加-i选项。

为了总结各参数及其功能,请参考以下表格:

参数 操作描述
--skip=number 跳过指定数量的提交,在显示日志输出时
--since,after=<date> 显示自给定日期以来的提交
--until,before=<date> 显示直到给定日期的提交
--grep=<pattern> 限制日志输出,匹配提交信息中给定的模式

清理

在处理文件时,我们可能需要Ctrl + Z撤销一些错误。有一个经典的例子是在工作过程中,你在一个已经包含文件的目录中解压 ZIP 包,结果发现 ZIP 包直接将文件放入该目录,而没有为解压的文件创建一个单独的目录。我看到你点头了,我知道你在微笑。

好吧,像这样的情况如果你解压的 ZIP 包所在的目录被 Git 监视(即是一个 Git 仓库),就可以轻松处理了。让我们再现这个场景,看看如何在几秒钟内处理它。

行动时间 – 模拟混乱

执行以下步骤:

  1. www.packtpub.com/support下载名为readme_package.zip的 ZIP 包,并将其放置在我们一直在使用的cappuccino仓库中,以便学习这些命令。

  2. 将 ZIP 内容解压到 cappuccino 目录内,确保看到如下截图中所示的七个 README 文件:行动时间 – 模拟杂乱

  3. 现在打开你的 CLI 窗口,输入以下命令:

    git status
    
    

    这将显示如下的当前状态:

    行动时间 – 模拟杂乱

刚才发生了什么?

我们已经成功模拟了之前讨论的意外解压的场景。

但在 Git 中,你解压的文件会被列为未跟踪的文件。这意味着,无论解压的文件和原始文件混合的数量如何,Git 都能轻松识别它们并提醒你。

我们可以通过指定模式来删除所有或部分文件,当匹配该模式时,clean 命令将跳过这些文件的删除。我们将在接下来的 行动时间 中了解更多关于这一点的信息。

行动时间 – 使用模式匹配清理你的杂乱

让我们跳过那些 README 文件的删除,首先删除我们复制并粘贴到仓库中的 ZIP 包。在终端中输入以下命令:

git clean –f –e*.txt

这将给你如下所示的输出:

行动时间 – 使用模式匹配清理你的杂乱

刚才发生了什么?

Git clean 需要指定一个 force–f)操作符,才能删除那些 Git 没有监控的文件,而 –e 操作符则跟随一个模式,并排除匹配该模式的文件。

在我们的例子中,*.txt 是匹配模式,它匹配了由于解压而在仓库中创建的所有 .txt 文件,因此唯一未被删除的文件是 readme_package.zip,它已被删除。

注意

如果我们将 clean.requireForce 配置变量设置为 false,则每次运行 git clean 命令时,可以避免指定 –f 参数。

行动时间 – 完全清除你的杂乱,无一例外

要删除这些不小心添加到仓库中的文件,你只需要对它们发起一场“火力打击”!没错,从之前的命令中,只需去掉排除部分。

git clean –f

这将给你如下所示的输出:

行动时间 – 完全清除你的杂乱,无一例外

刚才发生了什么?

clean 命令会删除当前仓库中所有未跟踪的文件。–f 参数强制 git clean 删除那些未跟踪的文件。

以下是你可以在 git clean 中使用的参数列表:

参数 操作说明
简写形式 全写形式
--- ---
-f --force
-d
-n --dry-run
-q --quiet
-e<pattern> --exclude=<pattern>

标签

标签在你想要在历史中标记一个特定点并附加一些元数据时非常有用,此后你可以使用相同的标签进行引用。在 Git 中有两种类型的标签。

  • 轻量级标签:这种标签方法仅跟踪标签名,不关心标签是由谁或何时创建的。当你只有一个人在仓库中工作,或者你创建的标签仅用于简单参考项目文件经历的不同阶段时,这种方法可能会很有用。

  • 注释标签:这种标签方法会记录作者的姓名、标签创建时间和标签名(如果提供的话,还包括描述)。当你想要回溯已达成的里程碑,或者当多个开发者共同参与同一个仓库的工作时,这种方法可能会很有用。

注意

当你使用注释标签时,项目拥有者可以在标签过程中保持授权访问权限;在高级设置中,甚至可以控制谁进行标签操作,何时操作,并标记他们的授权身份以供日后参考。

说了这么多,让我们通过实际操作更好地理解它。

行动时间 – 轻量级/无注释标签

  1. 首先,让我们通过在 CLI 窗口中执行 git tag 命令来列出 cappuccino 仓库中现有的标签。这应该会产生如下输出:行动时间 – 轻量级/无注释标签

    提供的 git tag 命令已检索了仓库中所有可用的标签,并按字母顺序列出它们。

  2. 现在,让我们通过执行以下操作在 cappuccino 仓库中创建一个轻量级标签:

    git tag edge_v1.1
    
    

    如果没有返回错误,说明你的标签应该已经创建。你可以通过进行标签列出操作来验证,正如我们在前一步中学习的那样。

    行动时间 – 轻量级/无注释标签

  3. 或者你可以通过打开 gitk 来查看它,这样你会看到如下效果:

    注意

    你可以通过 Git Gui 的仓库菜单或直接在 CLI 窗口中输入 gitk 来打开 gitk。

    行动时间 – 轻量级/无注释标签

  4. 如果你想浏览某个已打标签提交的文件更改,可以查看 gitk 窗口左下角,或者使用 git show <tagname>,如以下命令所示:

    git show edge_v1.1
    
    

    这将显示如下截图所示的输出:

    行动时间 – 轻量级/无注释标签

刚刚发生了什么?

我们已经成功地创建并附加了一个轻量/未注解标签到一个特定的提交。我们还学习了列出所有在仓库中可用的标签,如果需要,可以查看与任何给定标签相关的详细变化。

我们学习了如何通过标签名来引用提交,而不是使用提交的 SHA1 ID。让我们从实际操作中理解这意味着什么。

执行动作 – 引用标签

我们已经学习了 git checkout 作为返回历史的一个功能。正如你所知道的,这个过程需要你想要访问的提交的 SHA1 ID。现在让我们看看在处理标签时,如何做到这一点。在你的命令行窗口输入以下命令:

git checkout 2e361b44
git checkout edge_v1.1

这应该会给你如下图所示的输出:

执行动作 – 引用标签

刚刚发生了什么?

我们使用通常的 git checkout SHA1 ID 方法检出了一个较早日期的提交,但通过使用与提交关联的标签名称(edge_v1.1)回到了最新的提交。

执行动作 – 注解标签

在学习了如何创建未注解/轻量标签后,创建注解标签的过程和它一样简单,只需要在命令中添加 –a 参数。让我们在命令行窗口输入以下命令:

git tag –a ann_v1.1 –m 'Annotated tag v1.1'

刚刚发生了什么?

如果命令执行后没有返回任何错误,这意味着你的注解标签已经成功创建。我们创建了一个名为 Ann_v1.1 的注解标签。在这里,–a 表示注解标签名称,–m 用于设置创建标签时的描述字符串。

注意

请注意,我们为同一个提交创建了注解标签和未注解标签,以便后续进行比较。

简单练习

  1. 为了验证创建过程,进行标签列表操作,验证你创建的标签是否存在。

  2. 检查与创建的注解标签关联的提交内容变化。

为了理解轻量标签和注解标签之间的区别,它们的输出将并排显示。

简单练习

尽管创建过程略有不同,但删除注解标签和未注解标签的过程是相同的。你只需将 –d 参数与标签名一起使用,如下所示:

git tag –d edge_v1.1
git tag –d Ann_v1.1

这应该会产生一个确认信息,如下图所示:

简单练习

刚刚发生了什么?

我们刚刚删除了 edge_v1.1Ann_v1.1 标签,这两个标签是在学习标签过程中的 cappuccino 仓库中创建的。

–d 参数将删除传递给它的标签名,无论它是注解标签还是未注解标签。你可以执行标签列表命令,验证删除后的状态,如前图所示。

总结

我们已经了解了什么是以及如何:

  • 使用短日志:

    • 按作者对提交进行排序和量化

    • 总结提交日志

    • 获取每个提交者的元数据,例如电子邮件地址

  • 使用不同的git log参数,这使我们能够:

    • 在显示日志条目时跳过指定数量的提交

    • 从提交日志中提取日期范围内的数据

    • 在提交消息中搜索字符串值以识别提交

此外,我们还学习了如何清理我们的仓库,以防我们或其他人通过将不属于该仓库的文件注入其中而弄乱它。

不仅如此,我们还实际操作了如何使用注释标签和轻量标签标记仓库内容的里程碑。

第六章:释放野兽 - Git 与基于文本的文件

我们在之前的章节中看到了两种不同的与 Git 管理内容的工作模式,用游戏类比分别称为单人模式和多人模式。

请系好安全带。本章将解答你长久以来的疑问:多人模式是如何工作的,几个人可以并行玩耍?换句话说,就是让多人同时在同一内容上工作。

本章将学习的概念如下:

  • 合并与

  • 解决冲突,按你或团队的方式处理内容

Git 用于基于文本的文件 - 介绍

Git 在处理文本文件时配备了多种功能。从更高层次理解,我们可以通过以下栈来了解不同的文件类型及其含义:

Git 用于基于文本的文件 - 介绍

从上到下,它是一种分层的方法,从用户看到文件的方式,到计算机如何看到文件,直到文件在最底层的存储。

注意

二进制数据:任何只能通过特定程序(如 Microsoft Word 用于文档、图片查看器用于图片)读取内容的文件,称为二进制数据/值。

文本数据:任何文件,不论其扩展名或性质如何,只要内容是纯文本,可以用普通文本编辑器如记事本或 WordPad 打开的文件,都被认为是包含文本数据的文件。

为了更清楚地说明问题,打开项目中的.git目录,你将看到如下面截图所示的文件结构:

Git 用于基于文本的文件 - 介绍

这里名为index的文件被认为包含二进制数据,而COMMIT_EDITMSGconfigdescriptionHEAD这些文件被认为包含文本数据。用文本编辑器打开它们,你就会明白为什么。

注意

确保你不要更改这些文件的任何内容,否则你可能会弄乱仓库的版本,修复这些问题可能需要一些操作,但目前我们不感兴趣。

另一个例子是,你可以尝试用文本编辑器打开一个图像文件,以查看它的原始内容。

这就是我们之前提到的基本区别。在我们理解了分支合并的概念后,接下来我们可以进一步讨论这个问题。

多人模式 – 多个玩家同时在线

让我们继续延续之前章节中的游戏类比,以便更好地理解目前所学到的多人模式概念。

多个玩家 – 一次一个

想象一下你最喜欢的冒险游戏,它有多个关卡。设想一个情景,你在某一关卡卡住了,不知道如何继续。经过一番徒劳的尝试后,你突然意识到你的朋友是这个关卡的专家,你想寻求朋友的帮助。于是你迅速将最后保存的游戏文件状态分享给他,让他帮你完成这一关,保存状态后再把文件推送回给你,这样你就能继续进行游戏。

同样的情况也适用于你处理数据文件时,特别是在团队协作中,大家分工不同,共同完成更大任务以得出最终结果。另一种可能性是你希望领域专家处理特定的工作部分,等等。

这也意味着,当多人在同一文件上处理同一话题时,文件按照顺序传递给其他人逐一完成工作,可能会顺利进行,但当多个不同的人在同一个文件上处理同一话题时,尤其是对于包含二进制数据的文件,可能会变得混乱。

多个玩家 – 众志成城(同时多人)

我是第一人称射击FPS)游戏的忠实粉丝。即便今天,反恐精英仍然排在我的游戏榜单的顶端。我们在这里以反恐精英或其他任何团队游戏为例。每个队员不仅精通一种武器,还会精通两三种武器,以适应不同的情况。当需要时,他们会协同作战,共同击败对手并完成目标。

同样,当你处理文件中的文本数据时,多个不同的人可以协作编辑同一个文件、同一个话题、同一行内容,并利用 Git 管理最终产出。让我们学习如何更好地利用这一特性。

分享你的仓库

有两种常用的方式可以与他人分享你的仓库。

  • 内联网

  • 互联网

已经习惯通过 Bitbucket 在互联网上进行分享的你,这次让我们模仿使用我们在第四章中学到的裸仓库概念,通过内联网进行分享,分担负载 – 使用 Git 进行分布式协作

注意

如果你无法回忆起相关内容,我建议你阅读第四章中的保持本地 – 通过内联网分享部分,以了解什么是裸仓库,我们为什么需要它,以及它是如何操作的。

行动时间 – 准备分享

为了保持清晰和简洁,我们将从一个最小数据的新实例开始,这样变化会更加显著。

  1. 让我们创建一个新的目录并命名为collab_source

  2. 在目录中创建一个新的文本文件,命名为mycontent.txt

  3. 打开你刚创建的文本文件,并输入以下内容:

    Unchanged first line from source
    Second line
    Third line
    
    
  4. 保存并关闭文件。

  5. 现在,将 collab_source 目录变成 Git 仓库;然后添加文件 mycontent.txt 并提交,提交信息为 "从源仓库的基础提交"。

  6. 现在,这个副本将存放在您的机器上,以便您进行操作。让我们从您的副本中创建一个裸仓库,并将其放置在一个公共位置,您的团队成员可以从这个位置克隆,以获取他们自己的文件副本。

  7. 若要创建现有仓库的裸克隆,请使用以下命令:

    git clone --bare /your/path/to/collab_source /your/path/to/bare_collab
    
    

注意

在这个例子中,为了传达概念,我已经将 bare_collab 仓库克隆到我本地系统,而不是公共网络共享目录。但操作流程是一样的。

本主题的主要目的是传达多用户概念,因此我只选择了执行模式(CLI)。通常情况下,您应该已经知道这些命令的图形界面(GUI)对应操作。如果有例外情况,我会提供一个简要说明。

刚刚发生了什么?

我们创建了一个源仓库并添加了内容,然后从源仓库克隆了一个裸仓库,这样就为我们的团队成员提供了克隆选项。如果您按照前面的步骤操作,您应该会看到如下图所示的结构:

刚刚发生了什么?

行动时间 - 分布式工作团队

现在,Bob 和 Lisa(我们的虚拟团队成员)可以执行常规的 git clone 命令,其中源是 bare_collab 仓库,目标是他们选择的本地位置,以获取文件的副本。

git clone /path/to/repository/bare_collab /path/of/local/copy/Bob_collab
git clone /path/to/repository/bare_collab /path/of/local/copy/Lisa_collab

刚刚发生了什么?

除非 Git 报告了错误,否则 Bob 和 Lisa 已经从我们名为 bare_collab 的裸仓库克隆了文件。现在,这些仓库的演变结构如下图所示:

刚刚发生了什么?

如下图所示,提交树的结构是这样的:

刚刚发生了什么?

行动时间 - Bob 的更改

  1. 现在,Bob 觉得需要修改文件内容。因此,他打开文件并将第一行的文本更改为 "源文件的第一行 - Bob 更改",这样文件内容如下所示:

    First line from source - Changed by Bob
    Second line
    Third line
    
    
  2. 然后他添加了更改并提交,如下图所示:行动时间 - Bob 的更改

  3. 为了与团队成员共享更改,他希望将自己的更改推送到公共的裸仓库,但根据经验法则,在与多人合作时,使用 Git 时,推送前先拉取,以便将他人的更改合并进来,防止别人先于你推送更改。Bob 先执行了 git pull,然后再执行了 git push,如下图所示:行动时间 - Bob 的更改

刚刚发生了什么?

由于这次推送操作,裸仓库已更新,并包含了 Bob 的更改,而我们机器上的仓库(collab_source)以及 Lisa 的仓库仍然落后。现在,提交树的结构如下:

刚刚发生了什么?

行动时刻 – 莉莎的更改

  1. 在鲍勃进行所有这些操作时,莉莎也做了自己的更改。恰巧她修改了文件的第一行,并在其后追加了一行,这使得文件内容变成了如下所示:

    Unchanged first line from source = Not any more ;)  - Lisa
    Second line
    Third line
    Fourth line by Lisa
    
    
  2. 然后她添加更改并提交,正如以下截图所示:行动时刻 – 莉莎的更改

  3. 为了与团队成员共享更改,莉莎想将她的更改推送到公共裸仓库,但根据经验法则,在多人合作时,推送之前需要先拉取,以便在别人已经推送之前先整合对方的更改。她首先执行了git pull,然后收到了以下错误信息:行动时刻 – 莉莎的更改

刚刚发生了什么?

莉莎进行了更改、添加并提交,当她尝试从中央bare_collab仓库拉取时,遇到了合并冲突。

如果你专注于最后三行,就能清楚地看出为什么拉取操作被阻止。Git 自动尝试将莉莎所做的更改与鲍勃已经推送的更改合并,因为两人都修改了第一行,Git 巧妙地停止了合并,并要求我们解决冲突。

行动时刻 – 莉莎检查合并冲突

莉莎打开文件查看导致她无法继续的冲突,并发现了如下的模式:

<<<<<<< HEAD
Unchanged first line from source = Not any more ;)  - Lisa
=======
First line from source - Changed by Bob
>>>>>>> 9bab0336e6c9ab984b538f1f7724bf8a9703f55e
Second line
Third line
Fourth line by Lisa

刚刚发生了什么?

第一行上显示的连续左箭头和HEAD字样只是莉莎当前在仓库中的位置。下一行显示了她对文件所做的更改。

之后是连续的=符号,标志着莉莎内容的结束和鲍勃内容的开始。它是一个分隔符,紧接着下一行显示的是鲍勃的内容,然后是鲍勃进行该更改时生成的提交 ID。

行动时刻 – 莉莎解决合并冲突

执行以下步骤:

  1. 解决冲突是一个非常简单的过程。你有四个选择。

    • 指定顺序并合并两者的更改(在我们这个例子中是莉莎和鲍勃的更改)

    • 删除现有更改并应用你的更改

    • 删除你的更改并应用拉取的更改

    • 删除两者

    然而,第四个选项不太可能发生。

    注意

    要执行这些操作,可以使用常见的文本编辑器或交互式合并工具,它会为你提供三种视图(本地、基础和远程),你需要利用这些视图来解决提交的冲突。

    本地视图 是当前修改版本,基础 是我们在修改之前的早期版本,由 Git 自动决定,并且远程 是修改的远程版本,我们正在尝试获取和合并。您需要使用可用的箭头和指示器移动和排序您的更改以及远程版本。互动合并工具的屏幕截图(我展示了基于 Python 的meld实用工具)如下所示:

    行动时间 - Lisa 解决合并冲突

    注意

    现在,我们将使用普通文本编辑器来解决这种情况,以便理解其中的概念。

    Lisa 将选择第一种选项来排序和整合这两种更改;让我们看看她是如何做到的。

  2. Lisa 觉得将 Bob 的更改放在前面更好。在决定顺序之后,她使用普通文本编辑器打开文件,并按以下方式更改内容:

    First line from source - Changed by Bob
    Unchanged first line from source = Not any more ;)  - Lisa
    Second line
    Third line
    Fourth line by Lisa
    
    

    在进行了上述更改之后,她添加了更改,并将其提交,并附带消息“合并 - 先放置 Bob 的更改,然后是我的”,如下截图所示:

    行动时间 - Lisa 解决合并冲突

  3. 在此之后,Lisa 使用常规的push命令将她的提交推送到我们的中央裸仓库(bare_collab),如下截图所示:行动时间 - Lisa 解决合并冲突

刚才发生了什么?

Lisa 已成功解决冲突,并通过将更改推送到中央裸仓库使其可用于团队的其他成员。

这应该给出一个如下流程图所示的提交树结构:

刚才发生了什么?

正如图中所示,C3(由 Lisa 提交的本地提交)和 C2(由 Bob 提交并共享的提交)正在合并以形成一个称为合并提交 C4 的新提交。

在任何给定时间点,您都可以从 GUI 模式和 CLI 模式获取这些图形表示。

GUI 模式 - 获取仓库的历史图表

从 Git Gui 的 Repository 菜单中选择Visualize all branch history。一旦 Gitk 打开,左上角即可看到您的仓库历史可视化。Lisa 的可视化如下截图所示:

GUI 模式 - 获取仓库的历史图表

CLI 模式 - 获取仓库的历史图表

在您的终端/控制台中,切换到 Git 仓库的位置,然后使用以下命令获取其历史的树形表示:

git log --graph

您将看到如下截图所示的表示:

CLI 模式 - 获取仓库的历史图表

行动时间 - 团队成员与中央裸仓库同步

  1. Bob 觉得已经有一段时间没有从裸仓库接收到更新了,于是他执行 pull 操作来获取最新的更改,结果如下所示:行动时间 - 团队成员与中央裸仓库同步

  2. 好的,最后别忘了源仓库,所有这些仓库的母仓库,用于更新。在从那里执行 git pull 之前,我们应当将 origin 指向裸仓库,然后执行 pull 操作。命令如下:

    git remote add origin /path/to/bare_collab
    git pull –u origin master
    
    

    这给我们提供了如下所示的输出:

    行动时间 - 团队成员与中央裸仓库同步

正如我们在 第四章的 行动时间 – 添加远程源 中学到的,分担负载 – 使用 Git 的分布式工作,添加远程仓库是一次性的操作。

我们可以手动打开文件或执行 git log 来查看更改在不同仓库中的生效情况。

刚刚发生了什么?

我们成功地将不同人员在同一个文件、同一行上同时所做的更改同步到了不同的仓库,从而实现了预期的协作工作环境。

总结

我们已经学到了:

  • 文件内容的差异

  • Git 在文本文件上的强大功能

此外,我们还学会了如何在合并时处理和管理冲突。

不仅如此,我们还进行了一次角色扮演,实践学习了如何建立一个协作工作环境,多个成员可以在同一个文件、同一个主题,甚至同一行上进行工作。我们还学会了如何将不同人员的工作整合在一起,形成一个最终的输出。

第七章. 平行维度——使用 Git 进行分支

Git 最著名并且广受赞誉的两个特点就是廉价的分支和合并。在本章中,我们将了解什么是分支,为什么你需要分支,以及何时需要分支。既然我们已经尝试过合并,曾在第六章《释放猛兽——Git 与文本文件》里使用合并解决过一个合并冲突,我们接下来将更进一步,了解如何在需要时合并分支。

所有这些内容都是从组织的角度进行探索的。我们还将学习并实践一些方法,通过以下方式简化我们的工作:

  • 为常用的长命令创建简单的别名

  • 将多个命令链式连接,以适应常用工作流程

什么是分支

Git 中的分支是一个功能,用于为不同的使用需求启动一个独立的、类似当前工作区的副本。换句话说,分支意味着从你当前的工作中分岔出来,进入一条新的道路,在不打扰主线工作的情况下,你可以继续做其他事情。

让我们通过以下示例更好地理解这一点。

假设你正在为公司某部门维护一个流程检查表,并且对其结构的高效性印象深刻,您的上司要求您在做出一些特定于该部门的小修改后,将该检查表分享给另一个部门。你会如何处理这种情况?

如果没有版本控制系统,一种明显的方法是保存文件的另一份副本,并对新文件进行修改,以适应其他部门的需求。使用版本控制系统,并结合你当前的知识水平,也许你会克隆仓库并对克隆的副本进行修改,对吧?

展望未来,可能会有需要/情况,要求你将已对某一副本所做的更改,合并到另一副本中。例如,如果你在一个副本中发现了拼写错误,它很可能也会出现在另一个副本中,因为两者共享相同的源代码。另一个想法是——随着你们部门的不断发展,你可能会意识到,你为其他部门创建的定制版检查表比你以前使用的版本更适合你们部门,因此你想将为其他部门做的所有更改合并到你的检查表中,并拥有一个统一的版本。

这就是分支的基本概念——一条独立于其他开发线的开发线,它们共享一个共同的历史/源代码,必要时可以合并。是的,分支总是以某些内容的副本开始,然后从那里开始独立发展。

几乎所有的版本控制系统都有某种形式的支持,以适应这种分歧的工作流程。但正是 Git 的速度和执行简便性让它领先于所有其他系统。这也是人们将 Git 的分支称为其杀手级特性的主要原因(我们将在下一章深入探讨 Git 分支的细节)。

为什么需要分支

为了理解为什么需要分支,我们来考虑另一种情况:你在一个团队中工作,团队中的不同成员为项目中的不同部分做出贡献。

你的整个团队最近发布了项目的第一阶段,并正在朝第二阶段努力。不幸的是,一个在早期测试阶段没有被质量控制部门发现的错误,在第一阶段发布后出现了(是的,我也经历过!)。

突然间,你的优先事项转向了首先修复错误,因此放下了你在第二阶段所做的工作,快速修复了第一阶段中识别出的错误。但是,切换上下文会破坏你的工作流程;这样的想法有时可能会非常昂贵。为了处理这些情况,你可以使用分支概念(参见下一部分的图示),它允许你在不互相干扰的情况下同时处理多个任务。

注意

仓库中可能有多个分支,但只有一个活动分支,也称为当前分支。

默认情况下,自从代码库创建之初,名为master的分支是活动分支,除非显式更改,否则它是唯一的分支。

命名规范

Git 对分支名称有一系列命名规则,以下是一些常见的命名错误:

  • 分支名称不能包含以下内容:

    • 空格或空白字符

    • 特殊字符,如冒号(:)、问号(?)、波浪号(~)、插入符号(^)、星号(*)和左括号()

  • 斜杠(/)可用于表示层级名称,但分支名称不能以斜杠结尾

    例如,my/name 是允许的,但 myname/是不允许的,而 myname\ 将等待输入进行拼接

    • 以斜杠(/)结尾的字符串不能以点(.)开头

      例如,my/.name 是无效的

    • 名称不能包含连续的两个点(..)

何时需要创建分支

使用 Git 时,关于何时可以/需要创建分支并没有硬性规定。你可以根据自己的技术、管理甚至组织原因来决定创建分支。以下是一些给你提供参考的情况:

  • 在软件应用开发中,分支通常用于自学/实验目的,开发人员需要在不影响已发布版本的应用程序的情况下尝试在代码上进行逻辑测试

  • 一些情况,比如为每个客户创建一个单独的源代码分支,每个客户需要对你当前的包进行一组独立的改进

  • 经典的情况是——团队中的某些人可能在修复已发布版本的错误,而其他人可能在开发下一个阶段/版本

  • 对于一些工作流,你甚至可以为每个提供输入的人创建独立的分支,最后将这些分支合并,生成一个发布候选版本

以下是一些工作流的流程图,帮助我们理解如何使用分支:

  • 进行 bug 修复时的分支结构可以如下图所示:![When do you need a branch 这说明了当你在处理 P2 时,发现 P1 中存在 bug,你不必放弃当前的工作,而是切换到 P1,修复 bug,然后再回到 P2。+ 每个推广的分支结构如下图所示:When do you need a branch

    这解释了如何在不同的阶段/推广中管理相同的一组文件。在这里,来自开发的 P1 已被发送到测试团队(测试团队将获得一个名为“测试”的分支),并且发现的 bug 会在开发分支(v1.1 和 v1.2)中报告并修复,最后与测试分支合并。之后,这将被分支为生产或发布版本,最终用户可以访问。

  • 每个组件开发的分支结构如下图所示:When do you need a branch

    在这里,每个开发任务/组件构建都是一个独立的新分支,完成后会合并到主开发分支中。

实践出真知:使用 Git 进行分支操作

我相信你已经对在处理 Git 仓库时,何时、为什么以及如何使用分支有了较好的理解。让我们通过创建一些用例来加深这一理解。

场景

假设你是你所在组织的培训组织者,负责在需要时进行培训。你正在准备一份人员名单,这些人基于之前的记录,可能需要接受商务沟通技巧的培训。

第一件事,你需要向被提名者发送电子邮件,确认他们在指定日期的可用性,然后从他们的经理处获得批准,分配资源。通过以往的经验,你知道从记录中选出的培训人员名单可能会因团队内的情况而发生最后时刻的变化。因此,你希望先发送初步名单给每个团队,然后在名单最终确定的同时继续工作。

行动时刻——在 GUI 模式下创建分支

每当你想使用 Git Gui 创建一个新分支时,按照以下步骤操作:

  1. 打开指定仓库的 Git Gui。

  2. 分支菜单中选择创建选项(或使用快捷键Ctrl + N),这时会弹出如下对话框:Time for action – creating branches in GUI mode

  3. 名称字段中输入分支名称,暂时保留其余字段为默认值,然后点击创建按钮。

刚才发生了什么?

我们已经学习了如何使用 Git Gui 创建分支。现在,让我们通过 CLI 模式中提到的过程,并在 Git Gui 中执行相应的操作。

行动时刻——在 CLI 模式下创建分支

  1. 在桌面上创建一个名为BCT的目录。BCT 是“商务沟通培训”的缩写。

  2. BCT目录下创建一个文本文件,并命名为participants

  3. 现在打开 participants.txt 文件并粘贴以下内容:

    财务团队

    • 查尔斯

    • 丽莎

    • 约翰

    • 斯泰西

    • 亚历山大

  4. 保存并关闭文件。

  5. 将其初始化为 Git 仓库,添加所有文件,并进行如下提交:

    git init
    git add .
    git commit –m 'Initial list for finance team'
    
    
  6. 现在,给这些人发邮件,然后给他们的经理发邮件,并等待最终确认的名单。

  7. 当他们花时间回复时,你应该继续处理下一个名单,比如营销部门。使用以下语法创建一个名为 marketing新分支

    git checkout –b marketing
    
    
  8. 现在打开 participants.txt 文件并开始在财务团队名单下方输入营销部门的名字,如下所示:

    营销团队

    • 科林斯

    • 琳达

    • 帕特里夏

    • 摩根

    在你找到营销团队第五个成员之前,你收到财务部门经理的最终名单,他表示只能为培训提供三个人,因为剩下的(亚历山大和斯泰西)需要处理其他关键任务。现在你需要更改财务名单并填入营销部门的最后一个成员。

  9. 在返回财务名单并进行修改之前,让我们先添加并提交为营销部门所做的更改。

    git add .
    git commit –m 'Unfinished list of marketing team'
    git checkout master
    
    
  10. 打开文件,删除亚历山大和斯泰西的名字,保存并关闭,添加更改并提交,提交信息为 财务团队的最终名单

    git add .
    git commit –m "Final list from Finance team"
    git checkout marketing
    
    
  11. 打开文件,添加营销团队的第五个名字,阿曼达,保存、添加并提交。

    git add .
    git commit –m "Initial list of marketing team"
    
    
  12. 假设为营销团队输入的相同名字已经确认;现在我们需要合并这两个名单,可以通过以下命令完成:

    git merge master
    
    
  13. 你将遇到如下截图所示的合并冲突:执行时刻 – 在命令行模式下创建分支

  14. 打开 participants.txt 文件并解决合并冲突,如在第六章 解锁怪兽 – 在文本文件上使用 Git 中所学,然后添加更改,最后提交。

刚才发生了什么?

在没有任何思维或数据丢失的情况下,我们已经成功地在处理第二个名单时采用了第一个名单的更改,借助分支的概念——两个列表相互独立,互不干扰。

正如讨论过的,分支的生命周期开始时是另一个事物的副本,然后它会有自己的生命。

在这里,通过执行 git checkout –b branch_name 我们从当前的位置创建了一个新分支。

注意

从技术角度来说,所谓的“现有位置”指的是 HEAD 所在的位置,这种我们本地创建的轻量级分支叫做主题分支(topic branches)。另一种分支类型是远程分支或远程跟踪分支(remote-tracking branch),它跟踪其他仓库中的工作。当我们学习克隆的概念时,已经接触过这一点。

命令 git checkout –b branch_name 等同于执行以下两个命令:

  • git branch branch_name:在指定位置创建一个新的分支,但仍然停留在当前分支

  • git checkout branch_name:从当前/活跃分支切换到指定的分支

当使用 Git Gui 创建分支时,切换过程会自动处理,这样你就会进入刚创建的分支。

git merge branch_name命令将当前/激活的分支与指定的分支合并,以包含相关内容。请注意,即使在合并之后,分支仍然存在,直到使用命令git branch –d branch_name删除它。

注意

如果你创建并操作了一个分支,但不想将该分支的内容与其他任何分支合并,并且希望删除整个分支,请在之前提到的命令中使用–D代替–d

要查看系统中可用分支的列表,可以使用命令git branch,如下面的截图所示:

发生了什么?

如截图所示,我们的BCT仓库当前可用的分支是marketingmaster,其中master是创建仓库时的默认分支。带有星号的分支是当前活跃的分支。为了便于识别当前分支,Git 会在括号中显示活跃分支(branch_name),如箭头所示。

通过这个练习,我们已经学会了创建、添加内容和在需要时合并分支。现在,为了直观地查看历史记录的变化,打开 gitk(在命令行界面中输入gitk,或通过 Git Gui 的Repository菜单选择Visualize All Branch History),然后查看左上角。它将显示如下截图中的历史记录:

发生了什么?

小贴士

作业

尝试根据何时需要分支一节中最后一个流程图的说明,建立一个仓库。创建一个名为“development”的主分支和五个组件开发分支,在进行定制后将这些分支合并。

.config 文件 – 使用快捷方式

正如名称所示,这个文本文件位于.git目录内,是我们项目/仓库的特定配置文件。它还可以包含你经常使用的命令别名。以下部分将展示如何添加一个别名的示例。

行动时刻 – 使用 CLI 添加简单别名

在命令行窗口中,输入以下内容:

git config --local alias.ad add
git config --local alias.st status

现在,打开仓库中的.config文件,用你喜欢的文本编辑器查看,并且在底部会看到以下几行:

[alias]
   ad = add
   st = status

发生了什么?

我们已经成功地为 Git 命令addstatus创建了别名。为了验证这一点,切换回命令行窗口,输入git st并观察输出,它将完全匹配你的git status命令。类似地,我们可以使用git ad作为git add命令的替代。

我们还可以将两个或更多命令链式连接在一个单一的别名下。让我们学习如何做到这一点。

该操作的时间到了——使用 CLI 链式命令与单一别名

如前所述,.config文件是一个纯文本文件,让我们通过直接打开并编辑它来熟悉这个文件,而不是通过命令行进行操作。

  1. 打开你喜欢的文本编辑器,并用它打开.config文件(如果你还没有这样做的话)。

  2. 由于前面章节中命令的执行,将会在文件底部创建一个名为[alias]的部分,在该部分下我们会有adst的条目。之后再添加一行并粘贴以下字符:

    ast = !git add . && git st
    bco = "!f(){ git branch ${1} && git checkout ${1}; };f"
    ct = "!f(){ git commit -m \"${1}\";};f"
    

    它应该如下所示:

    [alias]
    ad = add
    st = status
    ast = !git add . && git st
    bco = "!f(){ git branch ${1} && git checkout ${1}; };f"
    ct = "!f(){ git commit -m \"${1}\";};f"
    
  3. 切换回命令行并执行以下命令:

    Git bco check_branch
    
    
  4. 现在向你的版本库中添加一个名为testfile.txt的新文件并添加一些内容,然后执行以下命令:

    Git ast
    Git ct "Created test branch, file to practice alias functionality"
    
    

刚才发生了什么?

我们已经成功地将多个命令链式连接在一个别名下。

从现在开始,每当我们需要在这个版本库中创建分支时,可以使用Git bco命令。

同样,每当你需要添加所有更改并查看版本库状态时,我们可以使用以下命令:

Git ast

每当你需要在版本库中提交时,不需要使用git commit –m "your_commit_message_here",我们可以使用以下命令:

Git ct "your_commit_message_here"

注意通过命令行添加别名和我们最近直接修改文件之间的区别。我们添加的命令是直接的 Shell 命令,当它们插入到.config文件中时,必须以感叹号(!)作为前缀。

git add .会将所有更改的文件添加到你的版本库中,而&&符号则用来连接另一个命令,即git st与前一个命令连接。git st显示的是版本库的状态。因为我们已经为status创建了一个别名st,所以在这里我们可以方便地使用它。

不要害怕看到接下来的两行代码中那些大括号;你需要知道的是,我们已经写了一个 Shell 脚本,其中有一个函数f(),在这个函数内部,我们已经将命令链式连接以供执行。正如之前讨论的,任何 Shell 命令都必须以感叹号(!)作为前缀。

${1}是一个技术上称为变量的神奇对象,它的作用是获取用户输入的值(check_branch)并动态地将其插入到命令旁边,因此无论你在哪里使用${1},它都会填充为用户提供的值。

注意

请注意,我们所做的所有配置更改仅适用于某个特定仓库的 .config 文件,因此这些自定义设置将保持本地化。如果要使其全局生效,需要在你的全局 .gitconfig 文件中进行更改。如果你使用的是 Windows 系统,这个文件通常位于 C:\Users\your_username 目录下,而在 Mac 或 Linux 系统中则通常位于 ~/ 目录下。

执行动作时间 – 使用 GUI 添加复杂的别名

Git Gui 已经为你通常需要的几乎所有功能提供了快捷键,我们在学习过程中已经接触到这些内容,因此让我们来了解如何使用 Git Gui 链接命令。

  1. 打开 Git Gui 并从工具菜单中选择添加选项,这将弹出一个添加工具窗口,如下所示:Time for action – adding complex aliases using GUI

  2. 在相应的字段中输入以下值:

    字段名称 字段值
    名称 添加并查看状态
    命令 git add . && git status
  3. 点击添加按钮。

    现在你会看到新创建的别名作为菜单项出现在工具菜单中,如下图所示:

    Time for action – adding complex aliases using GUI

刚刚发生了什么?

我们已经实践了如何为我们经常使用的长命令创建便捷的别名。我们还学习并实践了如何将多个命令结合起来并按顺序执行,使用命令行和图形界面模式。

提示

作业

git log 创建一个简单的别名。

然后,创建一个由两个命令组成的链,并将其命名为 cloggit commitgit log),使得当你输入 git clog "my_commit_message_here" 时,你的更改将从待提交更改状态变为没有要提交的内容状态(这意味着已添加但未提交的更改现在已提交,且提交信息为提供的信息),然后列出所有提交及其相关详细信息(这些通常是使用命令 git log 时显示的内容)。

总结

我们学到了:

  • 分支是什么

  • 如何以及何时在不同的工作流中使用

我们还实践了如何在同一个仓库中操作不同部分而不相互干扰,并在需要时合并这些部分以整合内容。

此外,我们还探索了别名的使用,并实际演示了如何为常用的长命令创建简单的别名,以及如何将多个命令链在一起,适用于常用的工作流程。

第八章:幕后 – Git 基础知识的基础

那些在体验 Git 的强大功能后脸上露出惊叹表情的人,可能会对这背后的机制感到好奇。

本章专门为那些希望深入了解以下操作细节的用户而写:

  • init

  • add

  • commit

  • status

  • clone

  • fetch

  • merge

  • remote

  • pull

  • push

  • tag

  • branch

  • checkout

我们首先了解 Git 仓库的组成,然后分析 Git 如何智能地管理内容,最后概述 Git 如何通过关系之间的关系来存储和传输内容。

Git 的两个方面:管道(plumbing)和外壳(porcelain)

无论你的新车宣传册中有多少突出显示的特点,它都必须拥有一个用户友好的界面,让你真正能欣赏并享受它所提供的精致功能。虽然核心工作在内部完成,但外部界面起到了启用作用。

类似地,Git 在内层和外层都使用以下命令:

  • 管道命令:这些命令负责低级操作,这些操作构成了 Git 的基础。

  • 外壳命令:这些命令以易于理解且吸引终端用户的名称,在高层次上涵盖了底层管道操作。

我们在前面的章节中学到的命令属于外壳命令类型。让我们深入了解它们的幕后工作。

Git init

你知道这个命令会创建一个名为 .git 的新子目录,这是版本控制的源头。我们进一步探讨 .git 目录的内容,应该有如下截图所示的目录结构:

Git init

Hooks

Hooks 是可以注入到各种 Git 命令和操作中的自定义脚本。我们可以编写自己的钩子,钩子必须放在这个目录中。

在这个目录中,有一些在执行 git init 时自动创建的示例钩子,但在我们手动将 hook_name.sample 重命名为 hook_name 之前,它们不会被激活。要了解目录中各种钩子的详细信息,请在命令行中输入 git help hooks 查看帮助文档。

Info

仓库的附加信息记录在此目录中。目前,唯一的文件是名为 exclude 的文件。这个文件作为一个主清单,列出了要从 Git 跟踪中排除的文件。

听起来很熟悉,对吧?的确,.gitignore 文件执行的操作与此相同,不同之处在于,排除模式写入排除文件时,仅反映在本地仓库中,而不会出现在后续的克隆中;而当写入 .gitignore 时,它成为历史的一部分,可以接受其他 Git 操作,如 add、commit、merge、clone、pull、push 等。

Config

这个名字说明了一切;这个文本文件是我们项目/仓库特定的配置文件。我们在前面的章节中已经讨论了该文件的更细节的工作原理,但我们需要涵盖的内容超出了本书的范围。

这里是 Git 用来维护远程部分条目的地方,无论是从哪里克隆仓库,还是数据交换的来源。它还包含一些核心设置,比如仓库是否为裸仓库等。

描述

有一个名为 gitweb 的软件包,它随 Git 安装包一起提供,允许我们为 Git 仓库设置 Web 界面。这意味着可以通过任何网页浏览器访问仓库。

这个描述文件包含了仓库的用户定义描述,供 gitweb 程序使用,以显示给请求仓库列表的客户端。

对象

正如你正确理解的那样,像任何其他版本控制系统(VCS)仓库一样,Git 仓库实际上是一个数据库,包含了所有必要的数据,用于保留、再现和管理文件的修订历史,但 Git 处理这些操作的方式使其与其他版本控制系统有所不同。

这之所以可能,是因为 Git 以对象的方式处理其所有内容。Git 中有四种类型的对象,分别是 blobs、trees、commits 和 tags,正是通过这些对象,它才能实现这种操作。

Blob

我相信你对积木游戏一定很熟悉;我们在生活中的某个时刻都玩过。当你回想起来,你会记得不管你搭建的是哪种结构,它基本上都是由几个独立的积木块拼接而成的。当你完成玩耍或者希望保存尚未完成的结构,以便以后继续时,我们会把它放进一个盒子或封装内,安全存储。

类似地,当涉及到计算机上的数据处理时,无论是图像、音频或视频剪辑,还是 PDF 文档,它们本质上都是由多个二进制数据位构成的。二进制大对象blob)实际上就是一组二进制数据,这些数据被存储在一个盒子/封装内,作为一个整体供以后使用。

在这里,blob 存储任何类型的数据,无论其结构如何。它们只关注内容,而不关注内容的元数据——甚至不关注文件的位置或名称。

树对象是 Git 对目录及其内容结构的内部表示。它们类似于文件系统中的目录,指向文件和/或其他目录。在这里,Git 树对象可以指向 Git blob 或/和其他 Git 树对象。

提交

提交对象保存了所有对仓库内容进行更改时的元数据。元数据包括更改的作者、更改的提交者(是的,可能是两个人不同)以及他们的电子邮件地址、日期和时间。

标签

标签对象携带一个人类可读的名称,可以附加到其他对象上,通常是一个提交对象,以便于检索以及我们在之前章节中讨论的标签相关其他原因。

HEAD 就像一个指针,指向 Git 引擎当前所在的活动分支(我们正在工作的分支)以进行进一步操作。如果使用文本编辑器打开它,并且你当前在主分支(master)上,你将看到如下内容:

ref: refs/heads/master

如果你当前正在test_release分支上工作,你将会看到以下内容,依此类推:

ref: refs/heads/test_release

引用

如果你曾经好奇过,为什么从浏览器访问google.com173.194.35.39都能得到相同的 Google 搜索页面,你会意识到应该有某个地方有一个映射关系连接这两者。另一个简单的例子:想象一下你的考勤登记簿,每个人的名字都与一个独特的员工/学生 ID 绑定,这个 ID 可以用来在有相同名字的人中识别出某个人,反之亦然。

类似地,refs目录的作用是为 Git 在某些操作中提供引用。它存储仓库中重要节点的 SHA-1 ID,如标签和分支。标签的元数据存储在位于refs/tags的另一个目录中,而分支的元数据存储在位于refs/heads的不同目录中。

每个分支名称都是heads目录中的一个文件,这些文件的内容包含该分支创建时的提交的 SHA-1 ID(在 Git 术语中是父提交)。标签也是如此——每个标签名称都是tags目录中的一个文件,文件中包含一个 SHA-1 ID 作为引用。

警告 – 目录位于headstags中的结构

我们讨论过tagsheads目录中的文件,它们代表你在仓库中创建的标签和分支名称。如果你在headstags目录中看到一个或多个目录结构,不要感到困惑。

这只是分支或标签所赋予的层次结构名称的表示方式。通过查看以下示例,重点关注分支概念,标签也适用,事情会变得更清楚。

创建一个名为mybranch的分支(git branch mybranch)。这将会在heads/mybranch创建一个名为mybranch的文件,而创建一个具有层次结构名称的分支,如kamia/kashingit branch kamia/kashin),将会在heads/kamia/kashin创建一个名为kashin的文件。

到目前为止,我们探讨了一个新初始化的 Git 仓库的重要部分,这个仓库尚未进行任何提交。然而,还有一个关键角色叫做索引,当你向仓库添加内容时,它会被创建。

索引

索引文件是 Git 存储暂存区信息的地方,待提交。简而言之,索引文件的内容将成为你下次提交的内容。换句话说,这就是你保存希望提交到仓库的文件的地方。

Git——一个内容跟踪系统

我们理解 Git 如何看待数据非常重要;它不是通过文件名或文件在目录结构中的位置来识别文件,而是强调文件的内容。这意味着,当两个或多个文件的内容相同,不论它们位于仓库中的哪个位置,Git 会通过它们的哈希值来识别它们之间的关系。

注意

计算哈希是 Git 在永久存储任何数据之前的第一步。给定内容的哈希值在全球范围内是唯一的。这意味着,包含“Hello world”的文件在你我的计算机中的哈希值是相同的,甚至在任何其他人的计算机中也是如此。

通过找出相似性,Git 将内容放入一个单独的 blob 对象并进行存储。请注意,后台只存储一份内容副本,从而最大程度地减少硬件使用,并且在需要重现时,Git 可以利用存储在树对象中的元数据还原出精确的存储模式。

这种哈希计算会在需要时发生,在多个阶段中进行,因此,即使其中某个文件发生微小变化,也会生成一个新的哈希值,Git 会将其单独存储。由于这些过程(尤其强调)围绕内容进行,而不考虑文件的名称或位置,Git 通常被称为内容跟踪系统。

Git 添加

当执行add命令时,Git 会使用工作树中的当前内容更新索引(暂存你的更改),并为下次提交准备暂存的内容,涉及以下步骤:

  1. 计算内容的哈希值。

  2. 决定是创建新的内容,还是链接到现有的 blob 对象。

  3. blob 的实际创建或链接发生在此时。

  4. 创建树对象以追踪内容的位置。

此时,索引被认为持有工作树中内容的快照,准备进行下次提交。

如你所知,此命令可以在提交前多次执行。它仅在add命令运行时添加指定文件的内容;如果你希望将后续更改包含在下次提交中,必须再次运行git add以将新的内容添加到索引中。

注意

更重要的是,在以下图示中,blob 和树对象如何创建并通过各自的哈希 ID 进行链接的过程。

Git 添加

正如之前讨论的,树对象不仅可以指向一个 blob,还可以指向另一个树对象,从而形成一个层级网络,如下图所示:

Git 添加

Git 提交

当执行commit命令时,会创建一个提交对象,包含之前通过git add命令添加的内容/更改的元数据。元数据包括以下内容:

  • 提交更改的人名、相关的日期和时间以及时区设置

  • 提交更改的人名、相关的日期和时间以及时区设置

然后,创建的提交对象会与已经链接到 blob 的 tree 对象进行连接,从而完成版本控制过程,如下图所示:

Git commit

请注意,HEAD 包含的是分支名称,而不是它指向的提交的 SHA-1 ID。这是因为当分支中的提交数量和位置不断变化时,很难通过提交 ID 来识别一个分支,因此才有“分支移动”的说法。

注意

不必担心 blob 和 tree 对象,这些对象是在未提交时作为add操作的一部分创建的;它们会在几个月后作为垃圾回收过程的一部分被销毁。

现在,如果你执行git status,你会看到你已经暂存的更改不再处于暂存状态。

Git 状态

当执行status命令时,Git 会检查文件的路径和大小。如果没有差异,它将保持原样,但如果发现差异,它会继续计算哈希值,并检查与其他哈希值的关系,就像我们之前看到的那样。

文件路径比较会在以下阶段进行:

阶段编号 比较 相关状态信息
1 索引中的文件路径与最近的提交(HEAD 提交)相比 待提交的更改
2 索引中的文件路径与工作区相比 更改未暂存以供提交
3 工作区中未被 Git 跟踪的路径(且未被gitignore或排除文件忽略) 更改未暂存以供提交

第一个状态表示已经添加(暂存)但未提交的更改。因此,执行git commit将完成版本控制过程。

第二和第三种状态表示更改尚未添加(暂存)以供提交。因此,要完成版本控制过程,我们需要首先使用git add添加它们,然后使用git commit提交。

Git 克隆

当执行clone命令时,内部过程的顺序如下:

  1. 如果目标目录不存在,则创建它,并在其上执行git init

  2. 在目标仓库中为源仓库中的每个分支设置远程追踪分支(git remote)。

  3. 获取对象,引用(位于.git目录中)。

  4. 最后执行检出(checkout)。

Git 远程

当执行remote命令时,Git 会通过读取本地配置文件.git/config中的远程部分,列出添加到仓库的所有远程仓库。配置文件内容的示例如下:

[remote "capsource"]
url = https://github.com/cappuccino/cappuccino
fetch = +refs/heads/*:refs/remotes/capsource/*

capsource 是我们在向仓库添加新远程时给 URL 起的别名。在这一部分,有两个引用参数被捕获:

引用参数 描述
url 这是你想要在本地仓库中追踪、共享和获取内容的远程仓库的 URL。
Fetch 这是用来将远程的 refs(分支和标签)传递给 Git 以便进行追踪。默认情况下,它会追踪远程仓库中 refs/heads/* 下的所有 refs。这些内容被放置在本地仓库目录 capsource 下,路径为 refs/remotes/capsource/*

Git 分支

当执行 branch 命令时,Git 会执行以下步骤:

  1. 收集 .git/refs/heads/ 中的所有分支名称。

  2. 利用位于 .git/HEAD 中的条目找到当前活跃的工作分支。

  3. 以升序显示所有分支,并在活动分支旁边标记一个星号(*)。

请注意,这种列出的分支仅为本地仓库中的分支。当你想列出所有分支,包括存储在 .git/refs/remotes/ 中的远程追踪分支时,可以使用 git branch –a

Git 标签

当执行 tag 命令时,Git 会执行以下步骤:

  1. 获取引用的提交的 SHA-1 ID。

  2. 验证给定的标签名称与现有标签名称的重复性。

  3. 如果是新名称,它会根据命名规则验证名称。

  4. 如果名称符合规则,Git 会创建一个标签对象,并将给定名称映射到获取的 SHA-1 ID,存放在 .git/refs/tags/ 中。

下图展示了标签对象与其他对象的关联:

Git tag

Git 拉取

当执行 fetch 命令时,Git 会执行以下步骤:

  1. 检查 URL 或远程名称,确保它指向在命令 git fetch remote_name(或)url 中指定的有效 Git 仓库。

  2. 如果没有指定,它会读取配置文件,查看是否有默认的远程库。

  3. 如果找到,它会从远程仓库获取命名的 refs(分支和标签)以及它们相关的对象。

  4. 检索到的 ref 名称会存储在 .git/FETCH_HEAD 中,以便将来可能的合并操作。

Git 合并

在执行 merge 命令时,Git 会执行以下步骤:

  1. 根据指定的参数,从 heads 目录中识别两个合并候选分支。

  2. 查找两个头部的公共祖先,并将它们的所有对象加载到内存中。

  3. 对公共祖先与头部一进行差异对比(diff)。

  4. 应用与头部二的差异。

  5. 如果在公共区域(分支之间)有变化,Git 会用标记显示冲突并提醒用户解决冲突(期望用户解决冲突,添加更改并提交)。

  6. 如果没有冲突,合并这些内容,并进行一次合并提交,附上元数据说明。

Git 拉取

执行 pull 命令时,Git 会内部执行以下操作:

  1. 使用给定参数执行Git fetch

  2. 调用git merge将获取的分支头合并到当前分支。

Git push

执行push命令时,Git 将执行以下操作:

  1. 确定当前分支。

  2. 在配置文件中查找默认远程仓库的存在(如果没有找到,它会提示你在执行git push时提供远程仓库的名称或 URL 作为参数)。

  3. 了解远程仓库的 URL 及其跟踪的分支(heads)。

  4. 检查自上次从远程仓库获取更新以来,远程仓库是否发生了变化。

    • 获取远程仓库的引用列表(使用git ls-remote)。

    • 检查本地历史记录中是否有该列表中的条目。如果远程的引用是本地仓库历史的一部分,说明自上次从远程仓库获取/拉取更新以来没有其他变化。因此,Git 会允许你直接将更改推送到远程仓库。如果它不是本地仓库历史的一部分,Git 会理解自上次从远程仓库获取/拉取更新以来,远程仓库已有所变化。因此,它会要求你先执行git fetchgit pull,然后再进行推送。

Git checkout

当执行checkout命令时没有任何参数,Git 会执行以下步骤:

  1. 获取工作区中命名路径的内容。

  2. 从索引中获取相关对象。

  3. 使用索引中的内容更新工作区的内容。

然而,行为会根据所使用的参数有所变化。

参数 描述
-b 用于从指定的提交 ID 位置创建一个新分支。git checkout –b <your_branch_name>git checkout branch后接git checkout <branch_name>的简写。这条命令会在.git/refs/heads/中创建一个新的引用,指向特定的提交 ID。

| --track | 该参数用于设置上游配置,通常在使用–b参数创建新分支时使用。执行时,.git目录中的.config文件会新增一部分内容,内容如下: |

[branch "master"]
        remote = origin
        merge = refs/heads/master

当执行类似git checkout --track -b master origin/master的命令时,会发生这种情况。 |

关系之间的关系——Git 打包文件

我们看到了 Git 如何通过内容识别文件之间的关系,并智能地选择是为内容创建一个新 blob,还是引用现有的 blob。我们还理解到,即使内容有一点点变化,也会导致 Git 存储一个独立的 blob,因为 SHA-1 ID 会发生变化。

假设你有两个文本文件,每个文件 5MB,内容相同但存储位置不同。Git 会根据情况创建一个单独的 blob,因为相同的内容会生成相同的 SHA-1 ID,从而节省空间。

现在,向其中一个文件的内容添加一行。Git 将为已更改的第二个文件创建一个新的 blob(大小为 5+ MB)。观察到两个几乎相同的 5 MB 的 blobs,可能会引发一些问题。

  • 为什么 Git 为整个内容创建一个新的 blob?

  • 为什么不保留两个文件之间共享的旧的 blob,并为仅在第二个文件中引入的差异创建一个新的 blob,从而减少存储并提高效率呢?

好的,这些是很好的问题;Git 通过一种叫做packfiles的方式提供了答案。我们刚刚讨论的场景中创建的对象被称为松散对象(loose objects),Git 会自动地、偶尔地将这些松散对象打包成一个单一的二进制文件,称为 packfile。

传输 packfiles

Git 不仅支持在操作如 clone、fetch、push 和 pull 中传输 refs 及其相关的普通 blob、tree、commit 和 tag 对象,还支持传输 packfiles。从更高层次来看,Git 为在远程之间传输数据提供了两套协议。

  • 一个用于从客户端向服务器推送数据

  • 另一个用于从服务器获取数据到客户端

    实现方 进程调用 描述
    服务器端 Upload-pack git fetch-pack调用,它了解对方缺少哪些对象,并在打包后发送它们。
    客户端 Fetch-pack 负责从另一个仓库接收缺失的包。这个命令通常不是由最终用户直接调用,而是通过一个更高级的封装命令git fetch执行。
    服务器端 Receive-pack git send-pack调用,接收推送到仓库中的内容。
    客户端 Send-pack 负责通过 Git 协议将对象推送到另一个仓库。这个命令通常不是由最终用户直接调用,而是通过一个更高级的封装命令git push执行。

总结

我们学习了以下内容:

  • Git 仓库的结构以及每个对象在版本控制过程中所扮演的角色

  • 不同的对象以及 Git 如何智能地使用这些对象管理内容

此外,我们还详细了解了像 initaddcommitstatusclonefetchmergeremotepullpushtagbranchcheckout 等命令的内部实现,这些命令我们在前面的章节中使用过,帮助我们掌握了版本控制的概念。

不仅如此,我们还从更高层次上看到了 Git 如何不仅基于文件的完整内容理解文件之间的关系,还通过 packfiles 以部分内容的形式进行理解。

posted @ 2025-07-02 17:47  绝不原创的飞龙  阅读(54)  评论(0)    收藏  举报