It is difficult to overstate the importance of version control.

I believe that it is as important as the inventions of the chalkboard and of the book for
multiplying the power of people to create together.

—Mark Atwood
Director of Open Source Engagement,
Hewlett-Packard Company

版本控制

上面那段话是惠普公司开源主管给书籍《Git for Teams》写的序,他强调版本控制的重要性,认为其重要性不亚于黑板和书本的发明。那什么是版本控制呢?版本控制就是管理更新的历史记录,记录一个或若干文件的内容变化,以便将来查阅特定版本修订情况的系统,例如记录软件开发过程中添加或修改源代码的过程,回滚到特定版本,恢复误删的文件,你还可以比较文件的变化细节,查出是谁修改了哪个地方等等。Git 就是这样一款分布式的版本控制系统。

Git

Git 历史

Linux 内核开发是一项庞大的工程,在 Git 面世之前,使用的一直都是专有版本控制系统 BitKeeper。专有软件指的是在使用、修改上有限制的软件,导致一些 Linux 开发者反对使用专有性质的版本控制系统。在 2005 年的时候,有人试图破解 BitKeeper 的协议,被 BitMover 公司发现了,也是因此,BitKeeper 吊销了授权给 Linux 开发者免费使用的许可证。Linux 的创始人 Linus Torvalds 没有找到一个满意的解决方案,于是花了两周时间写了一个 Git 的原型,加上 Git 是由 Linus Torvalds 亲手开发的,在功能与性能上面都无可挑剔,程序员们都比较愿意接受。

版本控制系统的演变

本地版本控制系统

许多人习惯用复制整个项目目录的方式来保存不同的版本,或许还会改名加上备份时间以示区别。这么做唯一的好处就是简单,但是特别容易犯错。有时候会混淆所在的工作目录,一不小心会写错文件或者覆盖意想外的文件。为了解决这个问题,人们很久以前就开发了许多种本地版本控制系统,大多都是采用某种简单的数据库来记录文件的历次更新差异。 它的工作原理是在硬盘上保存补丁集(补丁是指文件修订前后的变化);通过应用所有的补丁,可以重新计算出各个版本的文件内容。

集中化版本控制系统

接下来人们又遇到一个问题,如何让在不同系统上的开发者协同工作? 于是,集中化的版本控制系统(Centralized Version Control Systems,简称 CVCS)应运而生。 以 Subversion 为代表的集中化版本控制系统将仓库集中存放在服务器之中,保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新的文件或者提交更新。 虽然它只有一个仓库方便管理,但是一旦开发者的环境不能连接到服务器,就无法获取最新的源代码,如果服务器宕机了导致数据丢失,那么开发者可能再也见不到源代码了。

分布式版本控制系统

于是分布式版本控制系统(Distributed Version Control System,简称 DVCS)面世了。Git 会把代码仓库完整地克隆下来,由于本地的开发环境中就有仓库,所以开发者不需要连接远程仓库就可以进行开发。 这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。 因为每一次的克隆操作,实际上都是一次对代码仓库的完整备份。更进一步,这类系统可以指定和若干不同的远端代码进行交互,你就可以在同一个项目中,分别和不同工作小组的人相互协作。

总结

集中化和分布式都有各自的优缺点,但随着 Git 和 GitHub 的普及,会有越来越多的人使用分布式版本管理系统,以后应该很少会用到集中化的版本管理系统,所以只需要学习分布式版本管理系统就可以了。

Git 基本工作原理

Git 直接记录快照

Git 和其他版本控制系统的主要差别在于 Git 对待数据的方法。其他大部分系统以文件变更列表的方式存储信息,这类系统将它们保存的信息看做是一组基本文件和每个文件随时间逐步累积的差异。

而 Git 不按照上面的方式对待或保存数据。每次你提交更新,或在 Git 中保存项目状态时,它主要对当时的全部文件制作一个快照并保存这个快照的索引。 为了高效,如果文件没有修改,Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件。 Git 对待数据更像是一个 快照流

Git 基本都是本地操作

不像集中化的版本管理系统,离线的时候只能修改文件,但是不能向数据库提交修改,而Git 可以直接读取在本地磁盘上的完整项目历史,本地存储的仓库也是完整的仓库,修改完文件之后,即使是离线状态,也可以向本地仓库提交修改。

Git 保证完整性

Git 中所有数据在存储前都计算校验和,然后以校验和来引用。这意味着不可能在 Git 不知情时更改任何文件内容或目录内容。 若你在传送过程中丢失信息或损坏文件,Git 就能发现。Git 用 SHA-1 散列(hash,哈希)来计算校验和,这是一个由 40 个十六进制字符组成的字符串,基于 Git 中文件的内容或目录结构计算出来。实际上,Git 数据库中保存的信息都是以文件内容的哈希值来索引,而不是文件名。

Git 的三种状态

Git 有三种状态,你的文件可能处于其中之一:已提交(committed)、已修改(modified)和已暂存(staged)。 已提交表示数据已经安全保地存在本地数据库中。 已修改表示修改了文件,但还没保存到数据库中。 已暂存表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中。

由此引入 Git 项目的三个工作区域的概念:Git 仓库、工作目录以及暂存区域。

Git 仓库目录是 Git 用来保存项目的元数据和对象数据库的地方。 这是 Git 中最重要的部分,从其它计算机克隆仓库时,拷贝的就是这里的数据。

工作目录是对项目的某个版本独立提取出来的内容。 这些从 Git 仓库的压缩数据库中提取出来的文件,放在磁盘上供你使用或修改。

暂存区域是一个文件,保存了下次将提交的文件列表信息,一般在 Git 仓库目录中。 有时候也被称作索引,不过一般说法还是叫暂存区域。

基本的 Git 工作流程如下:

  1. 在工作目录中修改文件。
  2. 暂存文件,将文件的快照放入暂存区域。
  3. 提交更新,找到暂存区域的文件,将快照永久性存储到 Git 仓库目录。

如果 Git 目录中保存着特定版本的文件,就属于已提交状态。 如果作了修改并已放入暂存区域,就属于已暂存状态。 如果自上次取出后,作了修改但还没有放到暂存区域,就是已修改状态。

Git 安装

macOS 与 Linux

macOS 与 Linux 一般都预装了 Git。

Windows

可以在 Git 官网下载安装包。

Git 初始设置

首先设置使用 Git 时的姓名和邮箱地址:

1
2
git config --global user.name "YOUR NAME" // 设置姓名 
git config --global user.email "YOUR EMAIL" // 设置邮箱地址

这些信息会被存在 ~/.gitconfig 文件中,设置完后可以使用 cat ~/.gitconfig 命令来查看这些信息。这里设置的姓名和邮箱会用在 Git 的提交日志中。

Git 基本操作

初始化仓库

要使用 Git 来进行版本管理,就需要先初始化 Git 仓库,尝试创建新目录来初始化仓库:

1
2
3
mkdir ~/Desktop/git_repo // 在桌面创建 git_repo 文件夹
cd ~/Desktop/git_repo // 进入 git_repo 文件夹
git init // 初始化仓库

如果初始化成功了,会提示 Initialized empty Git repository in /Users/xyzlab/Desktop/git_repo/.git/ 等内容,此时会在文件夹下生成 .git 目录,这个目录里存储着管理当前目录内容所需的仓库数据。我们使用的这个文件夹称为工作目录,也称工作树,文件的编辑等操作在工作树中进行,然后记录到仓库中,以此管理文件的历史快照。如果想将文件恢复到原先的状态,可以从仓库中调取之前的快照,在工作树中打开。

查看仓库状态

工作树和仓库在被操作的过程中,状态会不断发生变化。可以使用 git status 命令查看当前状态。

可以看到当前我们在 master 分支下,并且没有提交(commit),提交是指记录工作树中所有文件的当前状态,也就是说该仓库还没有记录任何提交。我们新建一个名为 README.md 的文件,再次查看仓库的状态:

可以看到在 Untracked files 中出现了 README.md 文件,如果只是在工作树中创建了文件,它不会被 Git 管理,还需要将其添加至暂存区中并提交。

向暂存区中添加文件

要想让文件称为 Git 管理的对象,就需要使用 git add 命令将其添加到暂存区域,这是一个提交之前的临时区域,在添加到暂存区中之后,我们已经可以看到文件变为已暂存的状态:

向仓库提交更新

我们可以使用 git commit 命令将当前暂存区中的目录实际保存到仓库的历史记录中:

命令后面的参数 -m 是对此次提交的概述。

查看提交日志

git log 命令可以查看以往仓库中的提交日志,包括可以查看谁在什么时候进行了提交等等:

还可以在 git log 后面加上目录名、文件名等内容显示指定目录、文件的提交日志。

更改提交

为什么把 Git 称为时间机器,是因为它可以很方便地在版本之间进行穿梭。我们可以使用 git reset --hard 命令加上目标时间的哈希值就能完全恢复至该时间点的状态。

有时候发现已提交的内容中存在一些小错误比如拼写错误等,再提交一次显得比较多余,此时可以提交一个修改,并将这个修改包含到前一个提交之中,压缩成一个历史记录,使用 git rebase -i HEAD~2 即可。

查看文件差别

git diff 可以查看工作树与暂存区域之间的差别。

我们在 README.md 文件中添加一些内容,再使用 git diff 来查看当前工作树与暂存区域的区别,由于我们还没有向暂存区域添加内容,所以会显示工作树与最新提交之间的差别:

+ 号表示的是新添加的行, - 号表示的是被删除的行,从 @@ -0,0 +1 @@ 中可以看到只新增了一行。当向暂存区域添加文件之后,再次使用 git diff 会发现并没有显示内容,这是因为工作树与暂存区域中的内容是相同的,所以并不显示,可以使用 git diff HEAD 命令查看与最新提交之间的差别:

Git 分支

master 分支是 Git 默认创建的分支,基本上所有的开发都是围绕着 master 分支进行的。但当进行并行开发的时候,可以使用分支,往往会有多个最新代码的状态。不同的分支中,可以进行不同的开发,等到分支开发之后可以与 master 分支合并。可以使用 git branch 来查看所有分支,前面的 * 号代表当前处于的分支:

使用 git branch 加分支名来创建分支,再使用 git checkout 加分支名切换分支,也可以直接使用 git checkout -b 来创建并切换到新的分支:

我们在新的分支 feature-A 中修改一些内容并提交,观察与 master 分支之间的区别,可以看到新的分支不会影响到 master 分支的开发,可以创建多个分支,在不互相影响的情况下同时进行多个功能的开发:

Git 合并分支

假设 feature-A 分支的功能已经开发完毕,想要把它跟 master 分支进行合并就可以先切换到 master 分支再使用 git merge --no-ff feature-A 命令进行合并,使用 git log —graph 可以直观地看到分支内容已经被合并:

因为两个分支中的内容并没有冲突,可以很愉快地合并,如果分支中的内容有冲突的话,需要先把冲突解决了才能合并分支。

GitHub

什么是 GitHub?

近年来这个开源这个词逐渐被大众所熟知,字面意思就是开放源代码。GitHub 正是促成开源的平台之一,它方便来自全世界的开发者进行代码的交流,因此也被戏称为全球最大同性交友网站。GitHub 是一个 Git 仓库的托管服务,但它还有更多的功能来帮助开发者进行协同开发。GitHub 的创始人之一 Chris Wanstrath 创建 GitHub 的初心就是拥有一个 Git 仓库的托管服务方便自己与朋友分享代码。需要注意的是,Git 和 GitHub 不是同一个东西。正如上面提到的,GitHub 是 Git 仓库的托管服务,而 Git 则是使用 Git 仓库进行版本管理的软件,熟练掌握 Git 可以让你更方便地使用 GitHub。

Octocat

Octocat(章鱼猫) = Octopus(章鱼) + Cat(猫)

这只章鱼猫是 Github 的吉祥物,官方甚至还有一个专门展示不同造型的 Octocat 的网站:GitHub Octodex

GitHub 快捷键

在 GitHub 页面中使用 Shift + / 快捷键可以显示当前页面的快捷键:

GitHub UI

菜单栏

从最左边开始依次是:

  1. GitHub LOGO

    点击之后会进入 GitHub 首页

  2. 搜索栏

    输入想要搜索的仓库名、用户名、代码片段等进行搜索

  3. Pull requests

    查看用户的 Pull requests

  4. Issues

    查看用户的 Issues

  5. Marketplace

    查看 GitHub 可集成的插件,比如 Travis CI 持续集成服务、Coveralls 代码覆盖率检测服务、Code Climate 代码分析报告服务等

  6. Explore

    发现 GitHub 上热门的项目

  7. Notifications

    用户通知,当新建 Issue、被评论、进行 Pull Request 等时都会收到通知

  8. Create a New…

    可以创建新的 Repository、Gist、Organization、Project 等内容

  9. Account Settings

    可以查看一些自己的个人信息,比如 profile、repositories、projects、stats、gists 等,还有账号设置的入口

    主页面

这是登录 GitHub 之后看见的页面,左边的 Repositories 展示的是自己的仓库,中间正在加载的内容是当前 Follow 的用户和已 Watch 的项目的活动信息,因为 GitHub 是以人为本的,你对一个用户感兴趣,你可以 Follow 他,他 Star 了什么项目或是 Follow 了谁,东欧会出现在你的 News Feed 当中。最右边最上面就是 Broadcast,主要用于接受 GitHub 公司发来的通知或是使用技巧,比如当前的就是 GitHub 声明开通赞赏功能。

个人信息

最左边的是自己的头像,下面可以设置自己最近的状态,接着是一些相关的个人信息,包括所属公司、邮箱地址、个人网站、隶属的 Organizations 等。

正中间最上面的菜单栏分别是:

  • Overview

    Popular Repositories 会显示公开仓库中受欢迎、有很多 Star 的项目,当然如果没有 Star 它依然也会显示。

    正中间的绿色会显示你的 Public Contributions,一格表示一天,记录当日用户对拥有读取权限的仓库的大致贡献度。贡献度的衡量标准包括发送 Pull Request 的次数、写 Issue 的次数、进行提交的次数等等。颜色越深代表贡献度越高,一名程序员绿色的天数阅读,说明他对 GitHub 越熟悉。

    下面的 Contribution Activity 会按照顺序显示具体贡献的链接。

  • Repositories

    显示你的仓库,现在 GitHub 免费用户也可以创建私人仓库了

  • Project

    显示你的项目

  • Stars

    你点过 Star 的项目,也可以当收藏夹使用

  • Followers

    关注你的 GitHub 用户

  • Following

    你关注的 GitHub 用户

GitHub 功能

Gist

Gist 功能主要用于管理及发布一些小的代码片段等,比如一些小的脚本代码或是代码示例:

Star

Star 相当于点赞,你觉得一个项目不错,可以给它点上一个 Star,Star 数目越高,说明这个仓库越受欢迎。同时 Star 也可以充当收藏夹的功能,可以在自己的个人页面中看给哪些项目点过 Star:

Watch

Watch 和 Star 不同,点击 Watch 仓库后,该仓库的更新信息会显示在 News Feed 中。

Fork

Fork 功能是从其他用户那里拷贝一份仓库到自己的账户下面,这两个仓库是不同的,一个属于原作者,一个属于你,是完整地克隆了一份。

Pull Request

Pull Request 是用户 Fork 代码并修改之后,向对方仓库发送采纳请求的功能,也是 GitHub 的核心功能。比如在修改了一个 Bug 或是新增了功能之后,可以向对方仓库请求合并。

Issue

在软件开发过程中,使用 Issue 用于 BUG 报告、功能添加、方向性讨论等等的管理是一个不错的选择,管理 Issue 的系统称为 BTS(Bug Tracking System),一般在以下场景使用 Issue:

  • 发现软件的 BUG 并报告
  • 有问题向作者提问、讨论
  • 事先列出之后准备实施的任务

也可以为 Issue 添加标签,比如 bugenhancementquestion 等,也可以创建里程碑以便管理任务,比如实施了多少 Issue 并 Close 之后可以算作新的版本。在 GitHub 中可以直接使用 # 加 Issue 编号进行引用。

Wiki

Wiki 是一个用于项目文档展示的功能。

Projects

Project 类似于一个看板,默认分为 Inbox、In progress、Done 等标签页:

Insights

Insights 可以查看该仓库软件开发的活跃度,比如近期创建了多少 Pull Request 或 Issue,还有其他的标签页可以显示该仓库的一些相关统计信息:

GitHub Pages

GitHub Pages 主要用于在 GitHub 上托管静态 HTML,以便发布项目的 Web 页面。GitHub Pages 可以绑定独立域名,并且现在已经支持 HTTPS,可以结合这个功能搭建静态博客。

GitHub Action

说这个前先说明一下持续集成(Continuous Integration)、持续交付(Continuous Delivery)、持续部署(Continuous Deployment)的内容:

持续集成

传统的软件开发中,集成过程通常在每个人完成各自的工作后进行。这是一件耗时较长而且比较痛苦的事情。持续集成就是当新的代码被提交之后,立刻进行构建、测试并反馈结果,来确定新的代码能否被集成。持续集成把原本放在开发最后阶段的内容,到现在只要有新代码提交便可以进行。需要注意的是,开发者需要编写单元测试代码。GitHub 中具有代表性的持续集成服务器就是 Jenkins

持续交付

虽然通过持续集成,代码通过了测试,但是它并不能直接作为生成环境的代码,因为它并没有在类生产环境(production-like environments)中被测试和验证。持续集成是持续交付的基础,持续交付意味着会把集成完的代码自动分发到类生产环境中进行测试,一般把在不同环境下部署、测试这个过程称为 deployment pipeline,一般 deployment pipeline 会有许多不同的环境。在每个环境下,代码都会进行不一样的测试,这样有助于增强对代码的信心。特别重要的是,代码只会在前一个环境测试通过之后再进入下一个环境进行测试,这样开发者也可以很快定位到错误的内容。如果代码没有问题,可以继续手动部署到生产环境中。

持续部署

要实现持续部署,首先要进行持续交付。持续部署会把上一步的内容自动部署到生产环境。

DevOps

DevOps 是 Development 和 Operations 的组合。DevOps 是促进技术人员之间合作的一种文化。具体而言,在软件交付和部署的过程中协作交流,目标是为了更快、更可靠地发布更高质量的软件。

下图是一种传统的方式:

拥有所谓 DevOps 文化的组织的共同特征是:

  • 自主的多技能团队
  • 测试和发布自动化
  • 这些多技能的成员都有共同目标

自动化对于执行持续部署和 DevOps 的组织越来越重要,组织必须转向自动化过程,因为手动流程太容易出错且效率低下,DevOps 文化通常与持续交付相关联,因为它们都旨在增加开发人员和运营团队之间的协作,并且都使用自动流程来更快、更可靠地构建、测试和发布软件:

GitHub Actions 可以用来作为 CI/CD 使用,但它并不仅仅只是部署和发布,它其实是一组 docker 容器所组成的 workflow,可以为项目构建自动化的工作流;每个 action 都是在一个独立的 docker 容器中运行,而 action 又构成了 workflow。

Netlify

如果你比较懒,可以使用 Netlify 快速地实现前端的自动部署,只需要登录 GitHub 账号选择仓库即可,还可以选择部署分支,自定义打包命令及打包目录等,同时也支持 HTTPS。

GitHub 远程仓库

GitHub 上连接已有仓库时的认证,是通过使用了 SSH 的公开密钥认证方式进行的。我们现在来创建公开密钥认证所需要的 SSH Key 并将其添加至 GitHub:

1
ssh-keygen -t rsa -C "YOUR EMAIL" // 请将 YOUR EMAIL 改成 GitHub 账户邮箱

默认将文件保存至 ~/.ssh 文件夹中,id_rsa 文件是私钥,id_rsa.pub 是公钥,接下来在 GitHub SSH and GPG keys 中添加公钥即可:

添加完成后可以使用 ssh -T git@github.com 来进行测试。

从远程仓库获取

git clone 仓库地址 用于从远程仓库上克隆项目,GitHub 仓库地址可以在项目页中 Clone or download 中查看。这里顺便提一下 Clone 和 Download 的区别,Download 的是以 ZIP 形式打包下载,单纯把文件下载到本地,并不是一个 Git 仓库,所以无法通过 Git 来查看日志或是对仓库进行修改。

git pull 用于获取最新的远程仓库代码。

推送至远程仓库

使用 git push 推送仓库至远程仓库,建议先从 GitHub 生成仓库并克隆到本地再推送。

GitHub Desktop

GitHub Desktop 是 GitHub 官方出的一款桌面应用,方便于 Git 仓库的管理,包括内容的差异查看、更新提交、克隆仓库等等,可以在 GitHub Desktop 官网 下载:

参考资料

Pro Git

Resources to learn git

GitHub Guides

GitHub Leanring Lab

The Product Managers’ Guide to Continuous Delivery and DevOps