Git——《精通Git》笔记(一)

第 1 章 入门

关于版本控制

理解版本控制:我们需要他的原因就是我们希望能够记录对一系列文档操作的历史,便于查看修改记录,历史版本,甚至是在出错或者其他情况回滚到过去版本。

P1 “使用版本控制系统,你可以将文件或者整个项目恢复到先前的状态,还可以比对掌握文件随时间的吧 ing 呢个,查看什么人最后做出的更改。”

大型项目需要多人共同完成时,版本控制又提供了很可靠的多人协作的方式,每个人对项目的操作都可以作为一个版本记录其中,而且不同版本的提交留下的历史也为日后出现问题的时候找到对应负责人提供了可信途径(笑)。

本地版本控制系统

上古时代的VCS(Version Control System 学点英文),开始计算机通信还没像现在互联网这种程度时候,本低版本控制也是一个必备需求。RCS,一个常用 VCS 工具,Mac OS X 操作系统中,rcs 命令会使RCS在磁盘上以一种特殊的格式保存补丁集(patch set , 即记录文件之间的差异)通过叠加补丁来恢复到某个状态。

集中式版本控制系统

当多人开发协作流行起来,本低版本控制不能满足需求,集中式版本控制系统(Centralized Version Control System, CVCS)应运而生

特点:依赖网络、依赖中心仓库、集中管理、单点故障难以处理

分布式版本控制系统

为了解决集中式的依赖在线和中心仓库交互、单点故障等问题,分布式版本控制系统(Distributed Version Control System, DVCS)被开发出来。

特点:无严格意义上的中心仓库,完整镜像(完整备份),

Git 简史

Git 产生于 Linux 内核这个超大规模开源软件项目的维护过程中,开始 Linux 内核开发者社区使用的是 BitKeeper 专有分布式版本控制系统,后来关系破裂 B 收回软件的使用权,所以 Linux 开发社区(尤其是 Linux 之父林纳斯)汲取了原软件的经验和不足,开发出了自己的控制系统,最初目标是实现:

  • 速度快
  • 设计简洁
  • 对于非线性开发有力支持(大数量分支)
  • 完全的分布式设计
  • 能够有效处理像 Linux 内核这种超大型项目(速度及数据量)

Git 特性介绍

介绍 Git 对比其他版本控制系统的差异,以及一些 Git 独特的地方。

快照而非差异

Git 最大不同在于对待数据的方式,很多版本控制系统(CVS、Subversion、Perforce)记录的是一些文件和在文件上随时间做的改动(记录变化量)。Git 记录的是快照,抓取快照后存储一个指向该快照的引用,变动的记录变动后的文件信息,不变动不记录,留下原先的链接,整体宏观上是非线性的,数据变动在快照到快照这个快照流中体现。

几乎所有操作都在本地执行

在本地有本地仓库,自己对文件修改并记录版本时,一般无需从网络和其他计算机获取信息,无需考虑网络延时的开销。只有需要通过 Web 交互信息(比如上传至代码托管平台,对比与远程仓库差异)或者和他人写作的时候,在修改完成后再进行网络传输(这时本地已经完成了文件的修改,本地仓已经为最新版本)

Git 的完整性

关于完整性,Git 存储前会进行校验和计算,校验使用 SHA-1 散列(嗯密码学相关),改一点散列值都会产生很大变化~。

P6 “因为用途及广,你在 Git 中到处都会看到这种散列值。实际上,Git 并不是通过文件名在数据库中存储信息,而是通过信息的散列值。”

第 2 章 Git 基础

获取 Git 仓库

1
git clone

支持 HTTP 协议,ssh 传输协议等。

一些常见操作如git status查看当前状态,添加文件git add会直接让新文件变为暂存文件,同时,也可以暂存文件,是个多功能指令。编辑文件会由未修改状态变为已修改状态,已修改状态经过暂存变为已暂存状态。git commit会进行提交,执行的是上次git add的暂存文件版本,即在上次暂存后又做修改,这些修改是不会被提交的。

忽略文件

.gitignore 文件种列出匹配模式,这些匹配的文件不会被纳入 Git 仓库的文件提交。

查看变更

git diff命令可以查看哪些变更没有被暂存,哪些已暂存的变更正待提交?输出的是补丁(patch)。带--staged或者是--cached参数,表示查看暂存区对比提交的变更,不带参数表示的是当前未暂存的与暂存区的比较,即一后一前。

提交变更

git commit会打开指定的文本编辑器(可以是 Vim,Emacs,VSCode)添加 message 后提交暂存区内容。git commit -a命令传入-a 选项会跳过暂存区,直接将所有跟踪文件加入暂存区后提交。

移除文件

先从暂存区移除,然后再提交,同时也会从工作目录中删除。直接 rm 移除的时候不会有记录,git rm才可以。

移动文件

Git 不会显式跟踪文件的移动,不过 Git 很聪明,能明白。

git mv xx xxx实际上完成了下面这些命令

1
2
3
mv xx xxx
git rm xx
git add xxx

无论改名还是git mv,Git 都能推断出来这是重命名操作。

查看提交历史

git log可用参数有很多,比如-p按补丁格式显示每个提交引入的更改,--start显示每个提交中被更改的文件的统计信息,--graph用于显示图表展示分支和合并的信息。--pretty用一种可选格式显示提交,选项有 oneline、short、full、fuller 和 format(用于指定自定义格式)。

撤销操作

修改,覆盖上一次提交的信息

git commit --amend上述命令会提交暂存区内容,如果你在上次提交之后并没做任何该懂,那么提交快照就不会有变化。

撤销已暂存文件

git reset命令会恢复到已经修改但未暂存的状态(如果是直接添加文件则会回到未添加的状态)。

撤销对文件的修改

git checkout --<file>会撤销修改并把文件恢复到上次提交时的状态(或是刚克隆仓库后的状态,或是一开始再工作目录时的状态)。

需要注意的是,git checkout -- 是一条危险的指令。执行该命令后,任何对该文件的修改都会丢失,因为上述命令用之前的版本文件做了覆盖,除非你确信不需要这些文件,否则不要使用该条命令。

远程仓库的使用

git clone ... <repo> [<dir>] dir 可以理解成别名,git remote查看远程分支,使用-v 参数,会显示 Git 存储的每个远程仓库对应的 URL。

添加远程仓库拉取与推送

git remote add <name> <url>显示添加一个远程仓库并起名字,根据官方文档,名字是必要的。git fetch <remote-name>从远程仓库拉取数据,项目进行到某个阶段,需要与他人分享你的工作成果时,需要把变更推送到远程仓库,git push [remote-name] [branch-name]将本地分支推送到远程分支,推送 push 的要求详见第三章。

检查远程仓库

git remote show [remote-name]会给出 URL 地址和分支的跟踪信息,一些差异性信息(分支的增删)等。

删除和重命名远程仓库

git remote rename <oldname> <newname>将远程仓库的名称修改,git remote rm <remote-name>迁移地址、删除某个镜像或者协作者退出时的情况,用于删除某个远程仓库。

标签

git tag列举标签。加-l 参数可以模式匹配。

创建标签

轻量级(lightweight)标签儿话注释(annotated),一般推荐创建注释因为包含了全部的信息。

注释标签:git tag -a v1.4 -m "my version 1.4"

轻量标签:git tag v1.4-lw 即不需要-a、-m 参数。

Git 别名

git config --global alias.ci commit 这样以后就可以用 git ci 代替 git commit 了。

第 3 章 Git 分支机制

分支机制简述

当你发起提交时,Git 存储的是提交对象(commit object),其中包含了指向暂存区快照的指针。

暂存操作会对加入暂存区提交的每个文件计算校验和(SHA-1 散列值)并把文件的当前版本保存到 Git 仓库中(blob 对象)。而进行 commit 提交时,Git 会先为每个子目录计算校验和,然后再把这些树对象保存到 Git 仓库中。Git 随后会创建提交对象,其中包括元数据以及指向项目根目录的树对象的指针,以便有需要的时候重新创建这次快照。

个人理解:一次提交内容是一个树模型而不同提交又有父提交的指针也形成一个树模型?

HEAD 指针指向当前所在的本地分支,checkout 实际上就是切换 HEAD 指向的位置,这样的好处是一个 checkout 实际上可能是不同次提交的不同分支,能使我们关注分支而非提交。

分支切换会更改工作目录文件 切换分支时。工作目录的文件会被改变。如果你切换到较旧的分支,工作目录会被恢复到该分支上最后一次提交的状态。如果 Git 再当前状态下无法干净地完成恢复操作,就不会允许你切换分支。

基本的分支与合并操作

基本分支操作

git checkout -b iss53 -b 参数表示新建分支同时移动当前分支到该分支,实际上相当于两条命令执行git branch iss53git checkout iss53

当继续修补 bug 但是手头分支还没有完成地时候,利用 git 我们可以先 checkout 到问题分支(假设这里是 master 分支),新建 hotfix 分支修补问题。

修补问题完成后 checkout 到 master 分支使用git merge合并 hotfix 到 master,由于在 master 分支是分化出来的 hotfix 分支的直接上游,合并不会存在冲突,合并时出现"fast-forward"提示。这时候分支 master 和 hotfix 在一个提交版本上(指向位置相同),我们现在不需要 hotfix 分支了,git branch -d删除这个分支。

修补问题完成后,我们就可以继续回到原先开发分支继续工作啦!

基本合并操作

在早先有分叉历史的合并过程中,采用三方合并(被并入的提交快照,要合并的提交快照和共同祖先),合并完成后会新建一个提交指向新建的快照(而非像之间 fast-forward 一样只是移动快照的指针)。合并提交的特殊性在于它拥有不止一个父提交。提示为"Merge made by the 'recursive' strategy"。

值得注意的是,Git 会自己判断最优的公共祖先并将其作为合并的基础。

分支管理

git branch,不带参数列出分支列表,带参数-v 可查看每个分支上的最新提交信息。

git branch --merged会显示哪些分支并入了当前分支,相应的--no-merged 参数则显示未并入的分支。

与分支有关的工作流

长期分支

方便的三方合并机制,使得分支合并是个容易的操作。多个开放的分支分别用于开发周期的不同阶段很合适。

比如一些开发者所采用的master分支只存放稳定版代码,即已经发布版本或即将发布版本的代码,developnext的平行分支用于开发或是用于测试代码的稳定性,达到稳定性则合并到master分支中去。这种工作流很明显稳定分支会在提交历史中较为靠后,而前沿的开发分支较为靠前。筒仓(work silo)既视感,几经迭代就放到更稳定的筒仓中。

主题分支

适用于短期实现某一特定功能相关工作的分支。短期的外延,然后再合并到主干分支。每个分支的更改都与它的目标特性相关,使得代码审查等活动中俄能够更容易读懂所做的更改。

无需考虑先后顺序,并行开发无负担且同时易于选择。

远程分支

指针存在本地但无法移动,网络通信时自动更新,有些像书签,其表示形式是(remote)/(branch)

git fetch命令会更新远程分支指针(就理解成更新另一个人对分支路径的修改),origin 只是默认命名,我们还可以有很多个远程 remote。

推送

当需要同别人共享某个分支上的工作成果时,就要把它推送到一个具有写权限的远程仓库。本地分支不会自动同步到远程仓库,必须要显式地推送那些你想要与他人共享的分支。这样一来,你可以使用私有分支做一些不想与他人共享的工作,而仅仅推送那些需要与别人协作的主题分支。

跟踪分支

基于远程分支创建的本地分支会自动称为跟踪分支(tracking branch)。或者有时候也叫作上游分(upstream branch)。

跟踪分支与远程分支直接关联。如果你正处在一个跟踪分支上并键入git pushGit 会知道要将数据推送到哪个服务器上的哪个分支(pull 同理),这实际上是我们经常看到的操作。

checkout 到某个远程分支名相同但是本地还没有的分支时候,Git 会帮你创建跟踪分支。

拉取

git fetch 命令会拉取本地没有的远程然后最新更新数据。只是读数据,并不会影响本地的工作目录。

变基

在 Git 中,要把更改一个分支整合到另一个分支,有两种主要方式:合并(merge)和变基(rebase)。

工作原理

首先找到两个要整合的分支(你当前所在的分支和要整合到的分支)的共同祖先,然后取得当前所在分支的每次提交引入的更改(diff),并把这些更改保存为临时文件,这之后将当前分支重置为要整合到的分支,最后在该分支上依次引入之前保存的更改。

潜在危害

总结成一句话:不要对已经存在于本地仓库之外的提交操作执行变基操作。否则会带来不同分支上相同时间相同内容的混乱。(因为远程仓库并不会跟着进行变基操作)。

第 4 章

协议

本地协议、HTTP(智能与非智能)、SSH 协议等。不再做详细介绍。

在服务器上搭建 Git

以 Linux 为例

开始配置时,先把已有的仓库导出成一个新的裸仓库git clone --bare my_project my_project.git

将裸仓库放置在服务器上,假定想要放置在远程服务器/srv/git 目录下(前提时具备 SSH 访问权限)。scp -r my_project.git user@git.example.com:/srv/git,把裸仓库复制到该目录。

其他用户具有读权限的时候,可以 clone,具有写权限的时候可以推送数据。

赋予写权限

1
2
3
ssh user@git.example.com
cd /srv/git/my_project.git
git init --bare --shared

就完成了运行一台 Git 简单服务器的全部操作。

第 6 章 GitHub

SSH 访问

使用 SSH 远程连接,则需要配置一个公钥,在设置里面 SSH keys 部分配置。

为项目做贡献

Fork 操作,派生项目,推送修改,然后创建拉取请求,将改动写回原始仓库。想要创建拉取请求,点击 Fork 按钮即可。

GitHub 流程

  1. 从 master 分支中创建一个主题分支。
  2. 提交一些修改来改进项目。
  3. 将该分支推送到 GitHub 上的项目中
  4. 在 GitHub 上创建一个拉取请求
  5. 进行讨论,根据情况继续提交修改
  6. 项目拥有者合并或关闭拉取请求

项目维护

项目管理

一般对于单个项目而言,没有太多的管理事务可做,不过其中有几点值得一提。

更改默认分支

在 Options 标签下的个人仓库设置页面中修改,之后所有主要操作都会使用你所选择的分支作为默认分支,包括进行仓库克隆操作时默认检出的分支。

移交项目

个人仓库设置中 Transfer ownership 选项可以完成这项操作。(放弃或交由组织管理)

组织管理

这一节暂且不管了,多了一些组织团队管理以及审计的功能

GitHub 脚本化

第 10 章 Git 内幕

本章早读晚读都可以,展示 Git 内部工作细节以及实现。

底层命令和高层命令

git init会初始化一个.git 目录,几乎包含了 Git 存储和操作的所有内容。备份或者克隆,把这个目录复制到别处就可以了。本章讨论的内容基本上都在该目录下。

Git 对象

Git 按照内容寻址,那,意味着核心是“键-值”数据存储。插入内容-返回键值,键值检索-返回相应内容。

objects 目录下有一个文件,这是 Git 最初存储内容的方式:一份内容一份文件,以内容的头部的 SHA-1 校验和作为文件名。子目录采用 SHA-1 的前两个字符为名,文件名用剩余 38 个字符命名。

可以调用底层git cat-file将内容取回,-p 参数会将内容打印,-t 会给出类型(目前已知有 commit 还有 blob 还有 tree 对象)。

树对象

Git 存储内容的方式类似 Unix 文件系统,但有一些简化,所有内容被存储为树对象和 blob 对象。树对相对应 Unix 目录项,blob 对象基本上对应 i 节点或文件内容。单个树对象包含一个或多个树条目,每个条目包含一个指向 blob 对象或子树的指针以及相关的模式、类型和文件名。

(有机会学习一遍 Unix 和 Linux 的文件系统的表述,记住树对象目录是储存的指针。)

提交对象

git commit-tree命令指定单个树对象的 SHA-1 以及父提交对象(如果有),创建提交对象。

对树对象的一系列操作(底层操作),我们实际上可以创建了一个 Git 仓库,这实际上也正是当我们执行 git add 和 git commit 命令时,Git 所执行的操作(底层的)。

blabla 剩下的以后慢慢填坑吧。