第一讲 实验物理的大数据方法总论 DONE

Table of Contents

1 课前准备

  1. 配置准备 GNU 环境
  2. 安装 Git 和 Python

    apt install git python3
    

2 序论

这门课叫“实验物理的大数据方法”。出于一些灵活的考虑,这门课分成了两个部分,《实验物理的大数据方法(一)》和《实验物理的大数据方法(二)》。物理系和致理书院的同学要同时选(一)和(二);工物系和未央书院的同学必选(一),任选(二)。

课程成绩计入 GPA。

课程的标题的拟定,也经历了一番波折,斟酌许久才最终敲定。课程在最初提出与设计时没有太多的参考。纵观全世界,去年(2018年)仅伯克利开设一个类似的课程。所以说,这个课程里边可以参照借鉴的现成的课程比较少。因此,我希望同学们都能参与到教学建设的环节中。例如,你在上课遇到不懂的地方,或者是感觉讲的太慢/太快,肯定不是你的问题而是我的问题。大家一定要踊跃地提出自己的意见,因为课程是为同学们服务的。我的目标是让同学们在暑假小学期能够尽可能学到更多的知识,掌握一些新的技能。

2.1 教师

首先我是基科 2005 级,2009 年从清华毕业。之后 9 年时间我都在日本的神冈地下实验室,主要从事粒子物理实验工作,包括中微子和暗物质相关的实验。2018 年,我回到了母校工程物理系近代物理研究所。现在,我的主要工作是依托四川锦屏的地下实验室来筹划、设计和建造我们国家的中微子实验。同时,我也参与到中微子和暗物质的国际合作研究中,继续推进之前在日本的合作,并加入我们国家的下一代的江门中微子实验。如果同学们对粒子物理和中微子以及这些地下实验室感兴趣,欢迎课下与我一起讨论。

这门课也源起于我的个人爱好,在本科的时候属于业余爱好,现在属于半专业的爱好。它包括大数据分析,高性能计算,还有服务器的运营、维护、开发。在课程中我会倾尽所学与同学们分享,如果表达的不好,欢迎同学们提意见。

2.2 实验物理

这门课叫实验物理的大数据方法。要搞清楚课程的内容,首先要明确什么是实验物理。实验物理数理大类的同学可能在大一入学就知道:理论物理学家一般提出模型,从他的数学或者哲学角度出发思考,建立对大自然的模型。这个模型或者理论被提出之后,实验物理学家就会在自然界中探寻验证这些模型的线索,即预测的现象。在这一过程中,最重要的是可证伪性,即模型是否能够用实验数据证伪。实验物理就是通过采集数据,验证这些物理规律。

有一个说法,实验物理学家是非常费电的。如果大家忘了实验物理学家到底做什么,就看他的耗电量,耗电量很大的人基本上都是实验物理学家。比如,欧洲核子中心一年的耗电量是太瓦时量级。作为对比,北京的二环以内包括崇文宣武,人口 200 多万,大概只是他的 10 倍。一个实验室人口是千人量级,它的耗电量相当于 200 万人在一个城市里面的耗电量。实验物理本身要观察物理现象。通过控制变量来改变物理现象发生的环境,一般我们把它叫做狭义的实验,比如我们平时在实验室完成的实验。而对不能控制条件的实验,我们把它叫做观测,比如天文观测中造望远镜。严格上来讲,高能宇宙线的实验或者射电的实验都属于观测。实验物理要从取得的数据出发来进行统计推断,用统计学的知识来证伪物理规律假说。这是整个物理学科发展的规律。

2.3 大数据

“大”是相对的,所有你用一台计算机处理不完的数据,就叫做“大数据”。20年前的计算机处理不完的数据,现在或许可以处理了。可见“大数据”到底多“大”是“大”,一个相对的概念。在课程中,目前条件有限,给大家的数据不会大到一台电脑处理不完的程度。但是我们讲授的方法可以推广到非常大的计算规模。希望同学们在做练习和上课的时候能体会其中的方法。大数据作为流行语是一个商业的概念,目前人人都在讲,但是未必人人都知道。

大数据与大型的物理实验联系紧密。天体物理、等离子体物理、粒子物理的大型实验都要求非常大的计算量。探索极端环境下物理系统的行为,是日常生活中不易探测到的。它有很多噪音,即使应用各种技术压低噪音,也还会有残留。只有采集很多数据进行分析,才能找出那亿万分之一的信号。大型的物理装置产生的数据都是海量的,我国的FAST天眼望远镜,每年要产生 \(10^{15}\) 字节的数据。如何把这些数据转化成说脉冲星搜索结果这样的物理学结论,就需要应用大数据方法。

从历史角度,“大数据”还没有在商业流行起来之前,实验物理学就已经每时每刻地应对大数据的问题。CERN 在 90 年代,给出了一份详细的报告,决策使用大型机或是民用级 Intel PC 机来进行科学计算。这个报告经过长时间的论证和争论,最后 CERN 决定在 LHC (Large Hadron Collider 大型强子对撞机) 上用 Intel 的民用计算机组成集群,对 LHC 的数据进行计算。2012 年 Higgs 粒子的发现也都是在民用级计算机集群上完成的。这是个人电脑及硬件集群成为科学计算主流的标志。当今,超级计算机国际排行榜上,大家都开始使用集群来达到世界顶级的计算能力。所以说,在大数据或者高性能计算发展的历程中,实验物理起到了非常核心的推动作用。

近年来,“大数据”在其他应用领域的蓬勃发展也推动着实验物理中新方法的引入和萌生。“大数据”在工业界流行,大家把数据科学方法用在了各种领域,解决了各种问题,获得了令人非常振奋的结果。它们又与理论计算机相结合,产生了许多新方法处理数据。比如,深度神经网络,商业概念叫“深度学习”,又反过来应用到实验物理,使我们对世界的认识又有了一个新的进步。

2.4 课程目标

本课程以实验物理为出发点,学习大数据方法的基础知识。希望通过学习这门课让同学们达成三点目标:

科学精神
大家做过基础物理实验,也处理过实验数据,要理解数据处理中的科学精神。
自学能力
从一开始养成良好的科研习惯,而且掌握典型的科学计算工具,并且能够通自学上手新工具。
使用工具
工具有各自的适用范围,不同的工作场景适合使用不同的工具。针对问题和任务来选择合适的工具。

2.5 课程计划

第一周
版本控制Git的入门,Python的入门;
第二周
Python 科学计算,可视化;
第三周
命令行工具;
第四周
关系代数与回归分析。

2.6 数据分析指导原则

数据分析指导原则对实验物理乃至其他实证性科学研究适用。这几个原则会贯穿始终。

“复现”原则。无论做什么样的研究,科研成果一定要能够被同行重复出来。不能我在这里测量精细结构常数是 1/137,误差很小,你在上海测得精细结构常数是 1/141。不仅需要在实验条件下可以重复,而且需要实验结果以人类语言,比如论文、报告,还要以计算机语言表达,计算程序需要公开。这样他人才可以重复你的结果。这是科学研究的最基本的精神,它和可证伪性伴随。一个理论不能正着说和反着说都正确,这就不具备可证伪性了。“复现”与“可证伪”是区分科学与伪科学的标志。今后大家如果在媒体上看到谁有了突破,你要先问两个问题:“他所验证的结论是可证伪的吗?他的结果可以被他人重复吗?”

“透明”原则。在处理数据时,往往需要经历多步,无法一蹴而就。数据分析的每一步中间结果都应由人类理解可以被直接阅读。否则如果我们不知道它是对是错,到最后一步才知道它错,就非常难以找到错误原因。

“一次”原则,或“一次且仅一次”原则。不论写文章,还是写程序,禁止进行复制粘贴操作。在需要对一段程序进行修改,完成另一个内容时,不要把大块的程序切下来。这样做叫“自我重复”,它的坏处在于当你发现这部分需要进行修改时,你已经复制到其他地方的程序不会跟着被自动修改。如果你忘记了已经复制了 9 处,却一共改了 8 处,那么当这个项目变得很大时,就非常难找到错误原因。一定是有意义的信息都只放在统一的地方。

“最佳工具”原则。尽量使用高级语言。如果使用一个工具很得心应手,并且它非常适合要做的事,就一定要使用它。即使这个工具和别人的不一样,我们要想办法把它和别人的联合起来。如果能做到这一点,我们就可以在面对任何任务时都挑选比较合适的工具。该用锤子的时候就用锤子,该用电锯的时候就用电锯。这样才能节省自己的时间,也能够最有效地实践上面的三个原则。

这门课会以 Python 为中心介绍数据处理。但是,Python 未必永远都是最好的工具。所以我们这门课不叫 “Python 数据处理与科学计算”,虽然现在几乎如此。但是不保证今后还用 Python 进行教学。

2.7 课程评价

平时作业占 65%,以 git.tsinghua.edu.cn 形式组织。平时作业以程序自动测试,还有 20% 是“白盒”测试。助教与我会读作业程序,看 Git commit 是否符合规则,是否养成良好的习惯。

大作业占 30%,取材于实验物理的不同场景,覆盖物理学的方方面面。大作业也可以由同学自行提出,非物理的学科中数据处理类的任务都可以做为大作业。大作业分两阶段,对应前两周和后两周。只选《实验物理的大数据方法(一)》两学分的同学,要完成前半部分。同时选了《实验物理的大数据方法(二)》总共 4 学分的同学,要完成所有的大作业。大作业有三个主题:

粒子物理实验,取材自 Ghost Hunter 中微子数据分析排位赛,竞赛结果可以课赛结合的形式作为大作业。也可以在竞赛的基础上继续提高。未参加过竞赛的同学,也可在网站上查阅物理背景。

天体物理观测。

凝聚态物理实验测量。

大家的物理课的进度都差不多,但是编程基础差异较大。希望基础较好的同学多帮助周围的基础薄弱的同学。

如果你的精力太旺盛了,上课太简单了,可以尝试多做几个大作业。

自定义大作业的要点是:问题描述,学科背景,数据输入输出,评分标准。可以由同学们自己提出。

2.8 参考资料

Think Python,Python 科学计算讲义,在命令行进行数据处理,大蓝书。

The art of Unix programming:自由软件界的教父级人物,以道家思想剖析了 Unix 类系统中程序设计的优美和永恒性。到底是什么样的,它里面给出了很多切实的建议。我们这门课的透明性原则就是从这本书来的。

Learn X in Y minutes,你可以看到很多例子,改写成自己的例子,很适合初学者。

2.9 Python

课程围绕 Python 展开,但是又不是 Python 程序设计。课程带领大家循序渐进地做一些 Python 练习。Python 是一门解释型语言,相对于编译型语言(C/C++)更容易调试。非计算机专业的同学有这样一门语言比较容易,日常工作比较舒服。Python 语法简明,很多是英文单词,与伪代码神似,即使外行也比较容易读懂或猜到意思。因此 Python 的书写效率比较高,易于快速的写出不那么差的程序。如果你要进一步优化,可能要花很多时间。但是对于大部分的工作,即使是科学的硬核工作,写出一个差不多的程序就已经够用了。计算机性能的发展实在是太快了,是人类跟不上的。5 年前还要进行不断优化,5 年后一个差不多的程序可能胜任。Python 正好适应这样的趋势。

Python 可以直接调用多语言库。在学习物理,特别是计算物理时,会碰到 Fortran 或 C 程序。如果做统计分析,可能会用到 R 程序。如果大家组成一个团队,有的同学喜欢这个语言,有的同学喜欢另一个语言。Pyhon 可以作为各语言之间传唤的媒介,或者叫“胶水语言”,即把各种程序粘合在一起。Python 可调用很多程序的库,即使这个库是其他程序写的,也可以用 Python 程序调用它的功能。这非常易于和已经有的工具进行组合,而且可以有效地防止团队协作中的偏好冲突,还大大丰富了 Python 生态的功能。

一个 Python 程序,很可能不是最优的。在实际工作中,遇到了一个必须优化的地方,可能会达到 Python 效率的极限。此时可以把这个核心部分替换成 Fortran 或 C,就可以进一步优化程序的运行效率。故而有这样的策略:面对一个任务,先写正确的可以运行的程序,然后定位耗时最多的点,针对这里进行优化;如果优化到了极致还不够,则使用其他语言替代。这个策略适用于一切科学计算问题,可渐进地完善,而不是停滞于非黑即白的卡死状态。在团队协作中,很多时候这些细节就决定了成败,因此 Python 是团队协作的最佳工具。

此外,相对于 Matlab 等专门的科学计算语言,Python 是一个通用语言。它的功能不局限于科学计算和研究,而且在生活中的方方面面都可以使用。它的软件库丰富,可以完成非常多其他的功能。正是由于这些优点,Python 近年在科学计算领域得到了广泛应用。

2.10 POSIX

POSIX,Portable Operating System Interface,是关于计算机操作系统的国际标准。操作系统是在计算机上运行的基本系统,在硬件与人类之间建立桥梁。如果我们在 POSIX 国际标准的环境里写一个科学计算程序,依此得到了一个科学成果,那么全世界的其他人,不管用什么操作系统,只要满足 POSIX,就都可以复现出我们的结果。反过来,如果一个环境只能是在某一个编译器的某一个版本下才能得出正确结果,只要换一个地方换一台电脑结果就错了,这就不是好的科学研究。

在学习中,要尽量的使用国际通用的环境,学习其中好用的工具,建立一个工具箱。满足 POSIX 的操作系统有 GNU/Linux,macOS,或者其他的类 Unix 系统。Microsoft 的 Windows 不满足 POSIX 标准,但是可以使用 Windows Subsystem for Linux (简称 WSL)扩展来在 Windows 上实现 POSIX 环境。

正在使用 GNU/Linux 系统的同学不必作任何准备,请帮助周围的同学设置环境。macOS 的用户可以阅读 FAQ。Windows 用户先尝试安装 WSL,把课程的程序环境建立起来。

非常高兴,大家都成功配置了环境。这个过程比预想的时间要长,这也是常见的情况。思想是一种,然操作起来是另一种。计算机未必能够理解思想,传递信息时会有问题。幸运的是,大家可以上课坐在一起共同解决问题。否则很可能一个问题卡三四天。

2.11 编辑器

编辑器是书写程序的基本环境,我们大多数撰写、修改和调试都会在编辑器中完成,它占据大部分计算机操作的时间。有一个称手的编辑器将极大提升工作效率和体验。初学者尤其应当注意选择编辑器,打造适合自己的良好工作环境,才可以迅速进入状态。

注意程序编辑器(editor)不是文书处理器(word processor,如 WPS,Word),排版功能对程序没有帮助。终端上的常见编辑器有 GNU nano、Vim、GNU Emacs,其中 Vim 和 Emacs 都有对应的图形版本。功能强大的编辑器往往不容易上手,因此个人往往会形成对某一编辑器特定的偏好。Vim 与 Emacs 的用户阵营相对稳定,形成了在社区中长达几十年的“圣战”,清华大学的网络技术与开源软件协会的宣言“一个可以兼容 Vim 与 Emacs 的组织”正是讲了这一点。最近,Visual Studio Code (简称 VSCode)也加入了“圣战”的阵营,因为更容易入门迅速发展。

终端上的编辑器受限于终端模拟器自身提供的功能,但是适应范围广,常用于短期的编辑任务。Vim 和 Emacs 的终端版本都有不错的功能。VSCode 没有终端版,要求在图形界面工作。在终端里有编辑需求时,可以通过客户端连接到编辑器。VSCode remote 提供了这样的功能,可以在 Windows 上兼容它的 WSL 环境,也可以通过 SSH 编辑远程主机上的文件。Emacs 有类似的 Tramp 模块提供同样的功能,而 Vim 用户一般更喜欢在终端环境中直接执行。

2.12 版本控制

版本控制会贯穿本课程的各个细节,包括每个作业和大作业。

举个例子来说明版本控制。你和室友要写一个小论文,你对室友说“我写第一章你写第二章,我把今天的版本给你,你收到之后在我的基础上改。”但是室友忘了,在你昨天的版本上改了。于是出现了冲突,在昨天的基础上,你有一个改动,室友也有一个改动。此时需要手动融合,你看一下他都改了什么,再把它手动地放到你的版本里。这是非常痛苦的过程,而且容易出错,也是小组合作不悦的原因。此时最佳工具是“版本控制” (version control),顾名思义即给事物赋予版本。如“第一版”,“第二版”,“1.5版”,“1.7版”。版本控制是一个能够让一个原本不带版本的文件或资料带有版本的方法。

2.12.1 石器时代

在上古的石器时代,版本控制是这样的:我今天写了一个实验报告,起文件名叫 v1。晚上我改了一下,为了区分防止搞混,文件名叫v2。睡觉之前,又改了几个错别字,我觉得它还不是 v3,就把它叫做 v2.2。我把实验报告发给队友 xbd 了,他更新之后防止跟我的 v2.2 搞混了,就在给我的文件名上再加了一个日期,发回给我。

这是原始的自发的版本控制思想。

2.12.2 青铜时代

在青铜时代,POSIX 环境里出现了两个非常重要的两个工具, diffpatchdiff 的作用是把今天的文件与昨天的文件做差,把差分结果保存下来。 patch 把差分结果应用到旧文件上。

这个两个工具彻底改变了版本控制。比如,有一个公共版本,队友修改了第一章,得到了“差分2”的版本,他手里面握着一个“差分2”。我是加了第二章,得到了“差分1”。把我改的第二章和队友改的第一章合并起来,是目标。 patch 最大的创新是把把“差分1”与“差分2”加起来,或者把 “差分2”应用到“差分1”之上。一个公共版本之上的两个差分,非常像矢量运算的平行四边形法则,“差分1”和“差分2”具有可交换性质。 diffpatch 自动化了这个过程,只要调用工具即可完成。

可以想象从一个公共版本出发,5 位同学一起合作,他们分别写 5 个不同的功能。完成后把 5 个差分叠加起来,就合并成一个最终的版本。

2.12.3 铁器时代

铁器时代出现了控制服务。有一个中心的服务器,每个人都跟服务器交换差分。比如,我做了一个更新,给服务器推送一个差分。我想要其他人的更新,就从服务器上接收一个差分,更新我本地的版本。

在铁器时代,全球范围内自发的大项目产生了。比如说 GNU 的自由软件运动,以及 Linux 的内核,它们都得益于这样全球协作系统,使得全世界的人都可以向服务器提交差分。服务器把所有人做的工作都统合起来。

2.12.4 当代

当代的版本控制是分布式的,跟铁器时代的区别是它不需要中心服务器。即使没有服务器,即使我们两个都是普通用户,我们也可以直接交换差分,调用工具自动进行。我们将使用 Git,它是分布式的版本控制的优秀代表。

3 Git

Git 非常重要。生活中的痛点莫过于,之前写的报告找不回来了,上周的程序被覆盖掉了。一个人经过认真的思考,发明了非常巧妙的解题方法,并写出程序,效果拔群非常厉害。他想再进一步,继续优化算法,修改和重构程序。但是经过两个星期,他发现优化得不太对,新程序反而没有两个星期之前的效果好。但是之前的程序没有保存,没有办法再回到两个星期之前的高度了。他特别的难受,“复现”原则被破坏了。比如我在两个月前解决了哥德巴赫猜想,但是我忘了,怎么办?现在你到底信不信呢,这是很深刻的学术道德问题。大家一定注意,不要出现这种情况。

怎么解决?如果用石器时代的方法,是把两周之前目录存到另一个地方,再开始改。但这就有了重复,把很多程序复制粘贴出很多份了。今后如果有一个改动,希望改所有的备份,就会出现不一致。导致我们迷迷糊糊的在找程序的时候,百思不得其解,“我明明改了,但是怎么没改”。现实生活中经常会出现这种情况——不要这样做,这违背了“一次”原则。

使用 Git,养成良好的习惯,能解决以上所有问题。Git 是由 Linux 的发明人 Linus Torvalds 发明的。目前它支撑了全世界 5000 人以上的松散社区,在开发 Linux 操作系统的内核。它是“最佳工具”,一方面实现了 5000 名以上的人的协作,而就个体而言,实现了一个人与过去和未来自己的对话。这门课上,作业都通过 Git 提交,希望同学们能够体验优秀的工具。

3.1 基础概念

Git 把时间轴切成了若干个存档点,例如,在 12345 个存档点中有三个文件。我们改了文件 A 和文件 C,存档得到版本二。第四次,我们只改了 A1 和 B,得到版本四和版本五,以此类推。这五个存档点是否违背了“一次”原则呢?没有,Git 只存了 1、2 和 2、3 之间的差分,实际上以最简洁的方式把整个历史保存下来了。

3.2 Git 的状态

Git 有很多命令,初学者难以一次记全,使用时可以参考 Git cheatsheet。一个实际的 Git 控制的版本的例子是我们的讲义。它有不同的版本,可以用 git log 看到改动的历史。使用 tig 浏览,能看到每个差分,在图中红色的是删掉了的,绿色的是添加的。

Git 一共有三种状态,刚才看到的是“已提交”的状态,一共有 5 个版本。这些版本制作的过程,分三种状态。第一种是“已提交”,即这个版本已经存好了;第二种是“已修改”,即在前一个版本提交之后又做了别的改动。第三种“已暂存”,即我们修改了之后,使用 git add 把修改的一部分作为提交,标记成“已暂存”。使用 git commit 把“已暂存”的文件送到新的“已提交”状态上。

整个逻辑是:最开始 Git 仓库在原初状态,不存在文件。我们先加这个文件,打一个标记,放在“暂存”区域下次提交。我们可以修改“已提交”的文件,把它变成“已修改”的状态,如果给它标记成“已暂存”,就是等待提交的状态,提交之后就又变成“已提交”的状态。每次创造一个新的版本,都是经历了这样的过程。整个 Git 就是这样三个状态的这样循环,每次循环得到一个“已提交”的版本,成为下一步工作的基础。这给大家的一个项目推进的理念:步步为营,小步快跑,一点一滴地迭代。

3.3 Git 仓库

在作业中,有一个公共的仓库,取名为 upstream 。每人一个作业,分别对应独立的远程仓库,对应取名为 origin 。每个人从 origin 进行 git clone 到本地。本地与 origin 通过 pullpush 交换信息。在本地 git remote add tpl ... 可添加 upstream 的地址为 tpl ,从而与之通信。此时 tplorigin 相对于本地,处于同等地位,都是一种远程仓库,即 git remotegit fetch tpl 的作用是把 tpl 的远程仓库的内容,拉到本地的 git 仓库中。 本地的 git 仓库存在于 .git/ 文件夹下。它是一个隐藏的路径,用来存储 git 状态等内部信息, .git/config 可被编辑器修改配置。在 GNU 环境中,一切以 . 开头的文件和文件夹都默认不显示,具有隐藏属性。只有本地的文件夹中的文件是我们能直接使用的,它们对应 git 的“已修改”、“待提交”和“已提交”等状态。而历史中的提交信息都保存在 .git/ 中,它再与远程仓库通信,同步信息。 git add 本质上是把工作目录中的文件暂存到 .git 中,而 git commit 是操作 .git 中的暂存信息,创建提交。 远程的仓库,特别是专用服务器上的仓库,都不配有工作目录,只有一个 .git 路径。这样的仓库叫做“裸库” bare repository 。这也是为什么我们习惯把远程仓库的结尾都加一个 .git 的名字,因为它的角色与本地的 .git 相同。 git fetch tpl 所起的作用是把 tpl 中的内容下载到 .git/ 中,但是独立存在,不与本地 git 整合。

Git 同步的时候,每个人有各自的计算机,需要进行分布式的通信,不管有多少个 Git 仓库,它都可以互相传递这种差分量。这样每个人在本地的劳动,都可以系统性地跟其他人分享。 如果有几台机器,它们之间可以使用 SSH 协议传递差分。

3.4 Git Merge

Git 合并的概念是相对分支而言。分支是提交历史的分岔。不同的仓库如果从共同的基础起始,进行独立的开发,就会形式多个分支。Git 合并指把不同的分支重新合起来。如果不同分支中的提交,满足加法的交换律,那么合并操作就可以自动完成。合并后的状态,包括了被合并的两个分支的所有提交。 tplmaster 分支合并,只要先 git fetch tpl , 再 git merge tpl/master 即可。 分支与分支是平等的,本地与远程也是相对的,从远程来看,两者角色互换。因此不同的仓库也是平等的。这是 git 的去中心化的版本控制思想的核心。分支与仓库的概念独立,合并是对分支而言,不论分支在本地还是远程,都可以参与合并操作。 如果两个分支中的提交不满足交换律,自动合并就无法完成。 git 会尽可能把可以自动合并的部分自动完成,把剩下的部分标记为冲突 conflict。它们由人工干预,决定按照什么样的顺序和如何写新的代码把两部分功能统合在一起。 git 使用 >>> <<< === 等来标注冲突,解决冲突的具体体现是使用编辑器改动它们,最终去除。解决冲突这是团队协作中常见的工作。 这种高效的并合方案,使得跨国的异步合作成为可能,从而催生了散落在世界各地的软件开发团队。分布式的版本控制器,如 Git ,是当今最有效的工具。课程所使用的 Debian 操作系统环境,就是由世界各地的志愿者以公开透明的形式协作研发出来的。

3.5 Git 的基本命令

3.5.1 diff

git diff 是查看改动。它的文档可以在 man git diff 查到,里面有 git diff 的用法。这些文档很长,是 Reference Manual,最适合用来当作字典查阅。

3.5.2 status

git status 是查看状态,同样可以在 man git status 查到说明。 Git 仓库所处的“已提交”、“已修改”、“已暂存”都可以通过 git status 查看。

3.5.3 log

git log 是查看历史,从这个命令可以看到修改的历史。为了防止数据坏掉,它有一个校验码,有作者、时间和改动的内容。

3.5.4 pull, push

git pull 是从远程把差分都接收过来。=git push= 是把本地的差分推送到远程。这是团队协作中交换差分的基本方法。

4 作业

git.tsinghua.edu.cn 上,看到“Self Introduction”的作业。作业中有三个文件 README 说明文件, grade.py 评分程序, introduction.txt 是要改的文件。

4.1 SSH Key

在进行这些操作之前,需要把本地的 POSIX 环境跟 git.tsinghua.edu.cn 账号关联起来。使用 ssh 的密钥来对 ssh 通信协议进行鉴权认证。SSH 产生非对称密钥对,一个私有一个公有。之后,每人留私有部分,把公有的部分交给 git.tsinghua.edu.cn 。这就相当于我们身上有个虎符,git.tsinghua.edu.cn 用公有的部分来找你,你掏出一个私有的部分,如果对上了就可以改动 git.tsinghua.edu.cn 里面相应的仓库。

生成ssh密钥,需要使用 ssh-keygen ,义为"ssh key generator"。它会告诉我们即将生成公钥私钥对,下面输入放在哪里,默认即可。所生成的密钥形式,是RSA2048。两个新的文件在 HOME 目录下的 .ssh 下面,一个是 id_rsa ,这个不应该给大家看,因为是私有的。另一个是公有的,把公有的复制下来,加到 git.tsinghua.edu.cn 里面,SSH and GPG keys。

验证配置,打 ssh -T git@git.tsinghua.edu.cn ,如果得到了 Welcome to GitLab, @xxx! , “xxx”是你的用户名,就说明已经成功了,git.tsinghua.edu.cn 已经跟我们的密钥(虎符)对上了。在 ssh -T git@git.tsinghua.edu.cn 需要打一个 “Yes”,其逻辑是,我们把虎符给了 git.tsinghua.edu.cn,它要验证我们,我到底是不是我。但是我们也要验证这个它是不是它。它会给我们一个提示,git.tsinghua.edu.cn 给了我们一个虎符但是我们验证不了,因为是第一次用它,打“Yes”就接受了这样一个密码。接受了之后,就保证了每次跟 git.tsinghua.edu.cn 通信都必须得看到这个密钥才证明 git.tsinghua.edu.cn 真的,才会给它通行。

4.2 例子

把作业 git clone 下来。把已有的程序 clone 到本地

$ git clone git@github.com:physics-data/aplusb-heroxbd.git
Cloning into 'aplusb-heroxbd'...
remote: Enumerating objects: 29, done.
remote: Counting objects: 100% (29/29), done.
remote: Compressing objects: 100% (19/19), done.
remote: Total 29 (delta 4), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (29/29), 4.45 KiB | 4.45 MiB/s, done.
Resolving deltas: 100% (4/4), done.
git clone -q git@github.com:physics-data/aplusb-heroxbd.git
cd aplusb-heroxbd
ls
aplusb.py  data  grade.py  README.md

我一个程序 aplusb.py ,这是一个极简的 Python 程序,随后我们学习它的语法和语义。我将第一个 input 赋予变量 a , 第二个 input 赋予变量 b ,完成了对它的修改。

此时 git diff 可以显示在上一个 commit 之后做的改动。

git diff
diff --git a/aplusb.py b/aplusb.py
index b1042af..c6cd1b5 100644
--- a/aplusb.py
+++ b/aplusb.py
@@ -1,5 +1,10 @@
 # TODO: read two ints from standard input
 # HINT: use input()

+a = int(input())
+b = int(input())
+
 # TODO: print the result
-# HINT: use print()
\ No newline at end of file
+# HINT: use print()
+
+print(a+b)

aplusb.py 原是空文件,只有助教给的提示,更改是增加了输入 ab 以及输出 a+b 的行。

另外我们用 git status 可以看到 aplusb.py 被更改(modified)。

git status
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   aplusb.py

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

使用 git add 把改动累加起来,再看一下 status。

git add aplusb.py
git status
On branch master
Your branch is up to date with 'origin/master'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   aplusb.py

git add 把文件做了一个 "staged" 的标记,它将用于commit(对应于提示中的 Changes to be committed )。

git commit -m "加上 a 与 b 的读入并输出两者之和"
[master d4e354b] 加上 a 与 b 的读入并输出两者之和
 1 file changed, 6 insertions(+), 1 deletion(-)

-m 后面接对这个改动的说明。 如果不跟随 -m 参数, git 将调用编辑器供我们输入说明。

使用 git log 可以给出 commit 的历史,其中第一条是我们刚刚提交的。

git log
commit d4e354ba1c63e9689ef253aada00aedd7f50758e (HEAD -> master)
Author: Benda Xu <heroxbd@gentoo.org>
Date:   Wed Feb 16 20:27:23 2022 +0800

    加上 a 与 b 的读入并输出两者之和

commit 1abf3e5528c824f3e8b1bf873dfc6222bd3fcefb (origin/master, origin/HEAD)
Author: Shengqi Chen <i@harrychen.xyz>
Date:   Tue Aug 18 16:36:58 2020 +0800

    Update GitHub Classroom Autograding Workflow

commit f34ce2373540c7092b8b35fef57c0e5776414f9d
Author: Shengqi Chen <i@harrychen.xyz>
Date:   Tue Aug 18 16:36:57 2020 +0800

    Update GitHub Classroom Autograding

commit 943846fc6cd4999f990c734fba69ab4666f73fc5
Author: Shengqi Chen <i@harrychen.xyz>
Date:   Tue Aug 18 16:36:21 2020 +0800

    GitHub Classroom Autograding Workflow

commit 3191ca5f179f441241e4c3f659d88ef403806a0d
Author: Shengqi Chen <i@harrychen.xyz>
Date:   Tue Aug 18 16:36:20 2020 +0800

    GitHub Classroom Autograding

commit 865818ac4f46382fd52f983c0deaafca9d6e788d
Author: Chen <jiegec@qq.com>
Date:   Tue Aug 18 16:36:18 2020 +0800

    Initial commit

通过 git show 确认 commit 。

git show
commit d4e354ba1c63e9689ef253aada00aedd7f50758e (HEAD -> master)
Author: Benda Xu <heroxbd@gentoo.org>
Date:   Wed Feb 16 20:27:23 2022 +0800

    加上 a 与 b 的读入并输出两者之和

diff --git a/aplusb.py b/aplusb.py
index b1042af..c6cd1b5 100644
--- a/aplusb.py
+++ b/aplusb.py
@@ -1,5 +1,10 @@
 # TODO: read two ints from standard input
 # HINT: use input()

+a = int(input())
+b = int(input())
+
 # TODO: print the result
-# HINT: use print()
\ No newline at end of file
+# HINT: use print()
+
+print(a+b)

git diff 的对比可见这正是我们刚做的修改。

4.3 Git 的三个阶段

为什么 Git 要有三个阶段?理论上两个阶段就够了,用 git commit -a 会自动把所有改动的文件 stage 并且 commit,把后两步合并成一步。

三个阶段很重要,增强了 commit 的可定制性。如果有两个文件做了修改,而我们想把修改分成两个 commit,就可以使用 git add 其中一个文件有选择性地定制 commit 的内容。有时我们会在既有代码上做试验,有些需要通过 commit 保存下来,有些只是临时的操作不必保留。有时试验中我们不觉做了很多修改,希望把它们分解成逻辑上相对独立的部分。这此情形下三步的操作模型就很实用。

Git 的历史和说明是非常重要的信息。尤其是在经过了1年,5年,10年之后回顾理解过去的工作时,或者有队友学习既有代码时,逻辑清晰层次分明的 Git commit 有极高的价值。

4.4 覆盖未 commit 的改动

对不需要 commit 的改动,可以通过 git checkout 来退回上一 commit,覆盖我现有文件。这是一个很危险的操作,执行命令时,一定注意。这可能是一天的心血,误操作进行 checkout 把有用的修改覆盖了会造成很大的损失。checkout 影响的是“已修改”阶段的文件,不影响“待提交”阶段的文件。

4.5 退回至“已修改”状态

add 的逆向操作是 reset,用于把在“待提交”阶段的文件退回至“已修改”状态。

git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   aplusb.py

要取消这个“待提交”的修改,可以使用 git reset

git reset aplusb.py
git status
Unstaged changes after reset:
M	aplusb.py
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   aplusb.py

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

我们看到 aplusb.py 退到了“已修改”状态,命令中的提示是"unstaged"。

4.6 把 commit 推送到远端

大家注意 status 中有一行 “Your branch is ahead of 'origin/master' by 1 commit.”提示我们本地的仓库比远端多了一个 commit,还告诉我们 “use "git push" to publish your local commits”。

git push
Enumerating objects: 5, done.
Counting objects:  20% (1/5)

Counting objects: 40% (2/5) Counting objects: 60% (3/5) Counting objects: 80% (4/5) Counting objects: 100% (5/5) Counting objects: 100% (5/5), done.

Delta compression using up to 256 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 355 bytes | 355.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To github.com:physics-data/aplusb-heroxbd.git
   1abf3e5..de27c4f  master -> master

这样就把 commit 推送到了远端。注意在本地与远程交换差分时,commit 是传递的单位。未 commit 的修改,不论在“待提交”还是“已修改”状态,都不能通过 git 交换。

再看 status

git status
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   aplusb.py

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

注意“Your branch is up to date with 'origin/master'.”两者已经同步完成。

4.7 撤销已有 commit

要改动已有 commit ,该怎么办?

简单方式是 git revert 。它自动生成一个相反的差分,把某一个差分“湮灭”掉,例如在我的仓库中,

git log -n 2
commit de27c4f69c894458d51430986360b8c0db527fa5 (HEAD -> master, origin/master, origin/HEAD)
Author: Benda Xu <heroxbd@gentoo.org>
Date:   Sun Jun 26 21:42:14 2022 +0800

    加上 a 与 b 的读入并输出两者之和

commit 1abf3e5528c824f3e8b1bf873dfc6222bd3fcefb
Author: Shengqi Chen <i@harrychen.xyz>
Date:   Tue Aug 18 16:36:58 2020 +0800

    Update GitHub Classroom Autograding Workflow

我想去掉代号为 de27c4f69c894458d51430986360b8c0db527fa5 的 commit。这个代号是差分的哈希值,无歧义时,取前几位亦可。

git revert de27c4f6 --no-edit
[master f6066f5] Revert "加上 a 与 b 的读入并输出两者之和"
 Date: Sun Jun 26 21:58:51 2022 +0800
 1 file changed, 1 insertion(+), 3 deletions(-)
git log -n 3
commit f6066f54aac1bb29aec313b28cb9f0facc90612b (HEAD -> master)
Author: Benda Xu <heroxbd@gentoo.org>
Date:   Sun Jun 26 21:58:51 2022 +0800

    Revert "加上 a 与 b 的读入并输出两者之和"

    This reverts commit de27c4f69c894458d51430986360b8c0db527fa5.

commit de27c4f69c894458d51430986360b8c0db527fa5 (origin/master, origin/HEAD)
Author: Benda Xu <heroxbd@gentoo.org>
Date:   Sun Jun 26 21:42:14 2022 +0800

    加上 a 与 b 的读入并输出两者之和

commit 1abf3e5528c824f3e8b1bf873dfc6222bd3fcefb
Author: Shengqi Chen <i@harrychen.xyz>
Date:   Tue Aug 18 16:36:58 2020 +0800

    Update GitHub Classroom Autograding Workflow

这样 de27c4f6 差分就被撤销了,由于撤销也是一种对仓库的更改,因此它对应一个新的 commit,并且有历史记录。

4.8 改造本地历史记录

如果我们不想看到历史记录,希望把 commit 清除掉,这时可以用的命令是 git rebase 。它背后的逻辑更加复杂,建议初学者在对 git 有了深刻的理解和丰富的经验之后再考虑使用 rebase

4.9 改造远端的历史记录

除了 git revert ,初学者不要尝试改造远端的历史记录。

“不好,我 push 上去的 commit 太丑陋了,是我职业生涯的污点”,即使此时,也不要尝试改造远端的历史记录,人人都犯过错,请使用 git revert

不要这样做,时间是有方向的,这个世界是有熵的,不要尝试清除记忆。实际上,想象这样的场景:一个项目,两个队员,commit 1,commit 2,以及一个错误的 commit 3,都已经 push 到远端。 git revert 新添加了一个 commit 3 反向差分,队友不论在哪个 commit 都可以通过 git pull 与我保持同步。否则,如果队友已经同步到 commit 3,但是我反悔,通过某种手段把 commit 3 消除了,那么队友会显示它超前于远端,他可能把 commit 3 重新 push 上来。如果我在这之前加了 commit 4,远端的历史变成了 commit 1,2,4,这与队友的 commit 1,2,3 产生冲突,他必须手动处理才能解决。这样的操作会给合作带来困扰,团队越大涉及的队友越多,损失越大。

永远不要尝试改造远端的历史记录。

Author: 续本达

Created: 2023-04-22 Sat 00:02

Validate