Github工作原理与常用命令
0. 关于本文
本文写给包括自己在内的 Github 小白。主要参考:
1. Git 的工作原理和底层逻辑
1. Git 是一个分布式版本控制系统,每个开发者都有一个完整的仓库副本,可以独立进行开发、提交和查看历史记录。
版本控制系统
有了版本控制系统,你就可以将选定的文件回溯到之前的状态,甚至将整个项目都回退到过去某个时间点的状态,你可以比较文件的变化细节,查出最后是谁修改了哪个地方,从而找出导致怪异问题出现的原因,又是谁在何时报告了某个功能缺陷等等。 使用版本控制系统通常还意味着,就算你乱来一气把整个项目中的文件改的改删的删,你也照样可以轻松恢复到原先的样子。 但额外增加的工作量却微乎其微。
分布式版本控制系统
最早的叫本地版本控制系统,比如用复制整个项目目录的方式来保存不同的版本,或许还会改名加上备份时间以示区别。这么做唯一的好处就是简单,但是特别容易犯错。有时候会混淆所在的工作目录,一不小心会写错文件或者覆盖意想外的文件。还有一种叫做RCS,现今许多计算机系统上都还看得到它的踪影。 RCS 的工作原理是在硬盘上保存补丁集(补丁是指文件修订前后的变化);通过应用所有的补丁,可以重新计算出各个版本的文件内容。
后来随着互联网的发展,为了让不同系统上的开发者协同工作,集中化的版本控制系统应运而生。这类系统,诸如 CVS、Subversion 以及 Perforce 等,都有一个单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新的文件或者提交更新。多年以来,这已成为版本控制系统的标准做法。集中式的好处是每个人都可以在一定程度上看到项目中的其他人正在做些什么。 而管理员也可以轻松掌控每个开发者的权限,并且管理一个 CVCS 要远比在各个客户端上维护本地数据库来得轻松容易。然鹅,坏处就是万一中央服务器单点故障,比如宕机一小时,那么在这一小时内,谁都无法提交更新,也就无法协同工作。 而且如果中心数据库所在的磁盘发生损坏,又没有做恰当备份,毫无疑问你将丢失所有数据——包括项目的整个变更历史,只剩下人们在风中凌乱。 本地版本控制系统也存在类似问题,只要整个项目的历史记录被保存在单一位置,就有丢失所有历史更新记录的风险。
于是!分布式版本控制系统(Distributed Version Control System,简称 DVCS)面世了。 在这类系统中,像 Git、Mercurial 以及 Darcs 等,客户端并不只提取最新版本的文件快照, 而是把代码仓库完整地镜像下来,包括完整的历史记录。 这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。 因为每一次的克隆操作,实际上都是一次对代码仓库的完整备份。
分布式版本控制系统中每个人手里都有完整的代码仓库和完整的历史记录。
于是,许多这类系统都可以指定和若干不同的远端代码仓库进行交互。籍此,你就可以在同一个项目中,分别和不同工作小组的人相互协作。 你可以根据需要设定不同的协作流程,比如层次模型式的工作流,而这在以前的集中式系统中是无法实现的。
Git的诞生
Git 诞生于一个极富纷争、大举创新的年代。
Linux 内核开源项目有着为数众多的参与者。 绝大多数的 Linux 内核维护工作都花在了提交补丁和保存归档的繁琐事务上(1991-2002年间)。 到 2002 年,整个项目组开始启用一个专有的分布式版本控制系统 BitKeeper 来管理和维护代码。
到了 2005 年,开发 BitKeeper 的商业公司同 Linux 内核开源社区的合作关系结束,他们收回了 Linux 内核社区免费使用 BitKeeper 的权力。 这就迫使 Linux 开源社区(特别是 Linux 的缔造者 Linus Torvalds)基于使用 BitKeeper 时的经验教训,开发出自己的版本系统。 他们对新的系统制订了若干目标:
- 速度
- 简单的设计
- 对非线性开发模式的强力支持(允许成千上万个并行开发的分支)
- 完全分布式
- 有能力高效管理类似 Linux 内核一样的超大规模项目(速度和数据量)
自诞生于 2005 年以来,Git 日臻成熟完善,在高度易用的同时,仍然保留着初期设定的目标。 它的速度飞快,极其适合管理大项目,有着令人难以置信的非线性分支管理系统。
2. Git 以快照的方式记录项目的状态,每次提交(commit)都是一个完整的项目快照,而不是差异(diff)存储。
那么,Git 究竟是怎样的一个系统呢?
Git 和其它版本控制系统(包括 Subversion 和其他类似的)的主要差别在于 Git 对待数据的方式。 从概念上来说,其它大部分系统以文件变更列表的方式存储信息,这类系统(CVS、Subversion、Perforce 等等) 将它们存储的信息看作是一组基本文件和每个文件随时间逐步累积的差异 (它们通常称作 基于差异(delta-based)的版本控制)。
基于差异的版本控制只记录文件哪里改了。
然鹅,我们的 Git 不按照以上方式对待或保存数据。反之,Git 更像是把数据看作是对小型文件系统的一系列快照。 在 Git 中,每当你提交更新或保存项目状态时,它基本上就会对当时的全部文件创建一个快照并保存这个快照的索引。 为了效率,如果文件没有修改,Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件。
在Mac OS中,找到隐藏的.git文件,可以看到Object文件夹下按十六进制的顺序储存着诸多版本的文件快照(只有改动过的)。
因此,Git 对待数据更像是一个快照流,这是 Git 与几乎所有其它版本控制系统的重要区别。 因此 Git 更像是一个小型的文件系统,提供了许多以此为基础构建的超强工具,而不只是一个简单的 VCS。 稍后我们在讨论 Git 分支管理时,将探究这种方式对待数据所能获得的益处。
Git 拷贝下所有改变了的文件。
另外,在 Git 中的绝大多数操作都只需要访问本地文件和资源,一般不需要来自网络上其它计算机的信息。 如果你习惯于所有操作都有网络延时开销的集中式版本控制系统,Git 在这方面会让你感到速度之神赐给了 Git 超凡的能量。 因为你在本地磁盘上就有项目的完整历史,所以大部分操作看起来瞬间完成。
举个例子,要浏览项目的历史,Git 不需外连到服务器去获取历史,然后再显示出来——它只需直接从本地数据库中读取。 你能立即看到项目历史。如果你想查看当前版本与一个月前的版本之间引入的修改, Git 会查找到一个月前的文件做一次本地的差异计算,而不是由远程服务器处理或从远程服务器拉回旧版本文件再来本地处理。
3. Git 使用对象存储,将数据存储为 blobs、trees 和 commits 等对象。所有对象通过 SHA-1 哈希值进行标识,确保数据完整性和防篡改。
Git 中所有的数据在存储前都计算校验和,然后以校验和来引用。 这意味着不可能在 Git 不知情时更改任何文件内容或目录内容。 这个功能建构在 Git 底层,是构成 Git 哲学不可或缺的部分。 若你在传送过程中丢失信息或损坏文件,Git 就能发现。
Git 用以计算校验和的机制叫做 SHA-1 散列(hash,哈希)。 这是一个由 40 个十六进制字符(0-9 和 a-f)组成的字符串,基于 Git 中文件的内容或目录结构计算出来。 SHA-1 哈希看起来是这样:
24b9da6552252987aa493b52f8696cd6d3b00373
Git 中使用这种哈希值的情况很多,你将经常看到这种哈希值。 实际上,Git 数据库中保存的信息都是以文件内容的哈希值来索引,而不是文件名。
你执行的 Git 操作,几乎只往 Git 数据库中 添加 数据。 你很难使用 Git 从数据库中删除数据,也就是说 Git 几乎不会执行任何可能导致文件不可恢复的操作。 同别的 VCS 一样,未提交更新时有可能丢失或弄乱修改的内容。但是一旦你提交快照到 Git 中, 就难以再丢失数据,特别是如果你定期的推送数据库到其它仓库的话。
这使得我们使用 Git 成为一个安心愉悦的过程,因为我们深知可以尽情做各种尝试,而没有把事情弄糟的危险。
Git 有三种状态,你的文件可能处于其中之一: 已提交(committed)、已修改(modified) 和 已暂存(staged)。
- 已修改(modified):已修改表示修改了文件,但还没保存到数据库中。
- 已暂存(staged):已暂存表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中。
- 已提交(committed)::已提交表示数据已经安全地保存在本地数据库中。
这会让我们的 Git 项目拥有三个阶段:工作区、暂存区以及 Git 目录。
Git 项目拥有三个阶段:工作目录、暂存区域以及 Git 仓库.
- 工作区:是对项目的某个版本独立提取出来的内容。 这些从 Git 仓库的压缩数据库中提取出来的文件,放在磁盘上供你使用或修改。
- 暂存区:是一个文件,保存了下次将要提交的文件列表信息,一般在 Git 仓库目录中。 按照 Git 的术语叫做“索引”,不过一般说法还是叫“暂存区”。
- Git 仓库目录:是 Git 用来保存项目的元数据和对象数据库的地方。 这是 Git 中最重要的部分,从其它计算机克隆仓库时,复制的就是这里的数据。
基本的 Git 工作流程如下:
- 在工作区中修改文件。
- 将你想要下次提交的更改选择性地暂存,这样只会将更改的部分添加到暂存区。
- 提交更新,找到暂存区的文件,将快照永久性存储到 Git 目录。
如果 Git 目录中保存着特定版本的文件,就属于 已提交 状态。 如果文件已修改并放入暂存区,就属于 已暂存 状态。 如果自上次检出后,作了修改但还没有放到暂存区域,就是 已修改 状态。 在 Git 基础 一章,你会进一步了解这些状态的细节, 并学会如何根据文件状态实施后续操作,以及怎样跳过暂存直接提交。
4. 分支(branch)是 Git 的核心概念之一,允许开发者在不同分支上进行并行开发。分支之间可以合并(merge)或变基(rebase)以整合更改。
5. 索引(index)或暂存区(staging area)用于暂存更改,准备提交到仓库。git add
命令用于将更改添加到暂存区,git commit
用于将暂存区的更改提交到本地仓库。
2. GitHub 的工作原理
- GitHub 提供 Git 仓库的托管服务,允许开发者将本地仓库推送到远程仓库,并从远程仓库拉取更改。
- GitHub 提供丰富的协作功能,包括 Pull Request、Issues、Wiki、项目管理工具等,帮助团队协作开发和管理项目。
- 另外,GitHub 提供直观的 Web 界面,允许用户浏览代码、提交、分支、标签等,还可以进行代码审查和讨论。
- GitHub 可以与持续集成/持续部署(CI/CD)工具集成,自动构建、测试和部署代码。
3. GitHub 常用命令总结
3.1 配置和初始化
- 配置用户信息:
1 2
git config --global user.name "Your Name" git config --global user.email "your.email@example.com"
- 初始化仓库:
1
git init
- 克隆仓库:
1
git clone https://github.com/username/repository.git
3.2 基本操作
- 查看当前状态:
1
git status
- 添加更改到暂存区:
1 2
git add filename git add .
- 提交更改:
1
git commit -m "Commit message"
- 查看提交历史:
1
git log
3.3 分支操作
- 查看分支:
1
git branch
- 创建新分支:
1
git branch new-branch
- 切换分支:
1
git checkout new-branch
- 创建并切换到新分支:
1
git checkout -b new-branch
- 合并分支:
1
git merge new-branch
- 删除分支:
1
git branch -d old-branch
3.4 远程操作
- 查看远程仓库:
1
git remote -v
- 添加远程仓库:
1
git remote add origin https://github.com/username/repository.git
- 推送到远程仓库:
1
git push origin branchname
- 拉取远程更改:
1
git pull origin branchname
- 强制推送(慎用):
1
git push origin branchname --force
3.5 解决分歧和冲突
- 设置合并策略:
1 2 3
git config pull.rebase false # 使用 merge git config pull.rebase true # 使用 rebase git config pull.ff only # 仅使用 fast-forward
- 拉取并合并:
1 2 3
git pull origin branchname --no-rebase # 合并 git pull origin branchname --rebase # 变基 git pull origin branchname --ff-only # 快速前进
4. Github 分支名字
master :Git 的默认分支名字。它并不是一个特殊分支,跟其它分支完全没有区别。之所以几乎每一个仓库都有 master 分支,是因为 git init
命令默认创建的时候是这个名字,并且大多数人都懒得去改动它。
origin :云端仓库服务器的默认名字。
origin/master:是当本地的 master 分支同步到服务时的名字。如果分支名称为 Alita, 当分支同步到服务器时,会看到服务器上的名字为 origin/Alita。
这张动图的演示了 Git 本地创建新分支并同步到服务器的过程。
- GIT 初始化:本地默认分支叫 main、服务器默认名为 origin
- 本地创建新仓库
- 本地仓库更新、同步到本地分支 main
- 本地分支 master 同步到服务器上、服务器节点变成 orgin/master 本地创建分支名为 branch 更新本地分支 branch 本地分支 branch 同步到服务器上、服务器节点变成 orgin/branch 更新本地分支 master 用本地分支 master 更新服务器节点 orgin/master 本地分支branch合并到主干 master