引言
版本控制
-
版本控制的必要性:在软件开发、文档撰写等项目中,我们经常需要追踪文件的每一次变更、回溯到历史版本,或与他人协同工作。传统的文件命名方式(如
report_v1.doc
,report_v2.doc
,report_final.doc
)极易导致版本混乱、修改丢失和协作困难。版本控制系统(Version Control System, VCS)正是为解决这些难题而设计的专业工具。 -
版本控制系统的类型:
- 集中式版本控制系统 (CVCS):如 Subversion (SVN),所有开发者都从一个中央服务器检出文件和提交更新。其缺点是必须联网才能工作,且中央服务器的单点故障风险较高。
- 分布式版本控制系统 (DVCS):如 Git,每个开发者都拥有一个包含完整历史记录的本地仓库。这使得离线工作成为可能,并极大地提升了数据安全性和操作速度。
Git 系统简介
-
Git 的起源:Git 由 Linux 内核的创造者 Linus Torvalds 于 2005 年开发。其初衷是为了更有效地管理庞大且复杂的 Linux 内核代码库,取代当时使用的商业版本控制软件。
-
Git 的核心特性:
- 速度与效率:Git 的绝大部分操作都在本地完成,无需网络延迟,因此响应速度极快。
- 分布式架构:每个克隆的仓库都是一个完整的备份,这为备份和恢复提供了极大的便利。
- 强大的分支模型:Git 的分支创建和合并操作轻量且高效,极大地鼓励了并行开发和功能实验。
- 数据完整性:Git 通过 SHA-1 哈希算法确保内容的完整性。所有文件和提交在存储前都会计算校验和,任何损坏或篡改都能被轻易发现。
Git 与 GitHub
-
工具与平台的关系:可以理解为,Git 是一个协议或工具,就像电子邮件协议(SMTP)一样;而 GitHub 则是一个基于该工具构建的服务平台,类似于 Gmail。Git 可以在任何地方独立运行,而 GitHub 则是托管 Git 仓库并提供协作功能的网站。
-
GitHub 提供的附加价值:GitHub 在 Git 的基础上,提供了图形化的用户界面、强大的协作功能(如 Pull Requests)、问题追踪(Issues)、项目管理、代码审查等一系列服务,构建了一个庞大的开发者社区。
-
其他代码托管平台:除了 GitHub,还有 GitLab、Bitbucket 等类似的服务平台,它们都使用 Git 作为核心技术。
环境搭建:安装与初始化配置
Git 的安装流程
由于笔者使用 Windows 系统,暂时只介绍 Windows 平台的安装流程
Windows 平台
- 访问git-scm.com
- 根据自己的系统下载相应的版本
- 运行安装程序
- 安装完成后,打开“开始”菜单,找到 “Git Bash” 并运行它,出现一个命令行窗口,即表示安装成功
初始化配置
安装完成后,需要设置用户名称和电子邮件地址。每一次 Git 提交都会使用这些信息,它们会被永久地嵌入到提交记录中。
打开打开 Git Bash(Windows)或终端(macOS/Linux),执行以下两条命令,请将引号内的内容替换为自己的信息。--global
参数表示正在设置全局配置,这台计算机上所有的 Git 仓库都会默认使用这个配置。
-
配置用户身份标识(用户名):
git config --global user.name "Your Name"
-
配置用户身份标识(邮箱):
git config --global user.email "youremail@example.com"
建议使用在 GitHub 或其他代码托管平台上注册的邮箱。
-
验证配置信息:
-
要检查配置,可以运行以下命令:
git config --list
-
这会列出所有的 Git 配置信息。请确保能看到刚才设置的
user.name
和user.email
。
-
核心操作与基础工作流
掌握 Git 的关键在于理解其背后的核心概念和基本操作流程。本章将详细介绍 Git 的三个核心区域,并演示从初始化仓库到创建第一个提交的完整过程。
- 三大核心区域:工作区、暂存区与本地仓库
- 工作区 (Working Directory):这是在计算机上能直观看到并进行编辑的文件夹。它包含了项目的所有文件,是进行代码编写、修改、添加或删除文件的场所。
- 暂存区 (Staging Area / Index):这是一个位于
.git
目录下的特殊文件,它记录了即将要被提交到本地仓库的文件快照信息。可以选择性地将工作区的某些修改放入这个清单,准备进行下一次提交。 - 本地仓库 (Local Repository):这也是一个位于项目根目录下的
.git
文件夹。它存储了项目的所有版本历史、元数据以及对象数据库。当执行git commit
命令时,Git 会将暂存区中的内容制作成一个永久的快照(即一次“提交”),并保存在本地仓库中。
1. 仓库的初始化
-
要对一个项目进行版本控制,首先需要将其初始化为一个 Git 仓库。
-
操作步骤:
-
通过命令行工具进入项目文件夹;
-
运行以下命令:
git init // 如果要将分支设置为远程仓库的主分支,可以强制命名 git init -b main
-
命令效果:执行该命令后,Git 会在当前目录下创建一个名为
.git
的子目录。这个目录包含了所有Git仓库必需的文件和元数据。从此,Git 便开始追踪该目录下的所有文件变更。
-
2. 标准工作流程:修改、暂存与提交
-
这是 Git 最基本也是最常用的工作循环。
-
第一步:查看状态
git status
- 这是在任何时候都应该首先使用的命令,它可以清晰地告诉你当前仓库的状态。
git status
会显示哪些文件被修改过、哪些文件是新增的(未被追踪的)、哪些文件已经被放入暂存区等。
-
第二步:添加至暂存区
git add
-
当在工作区完成了一些修改后,需要使用
git add
命令将其添加到暂存区,以备提交。 -
添加单个文件:
git add <filename>
-
添加所有已修改或新增的文件:
git add .
-
-
第三步:提交至本地仓库
git commit
-
将所有希望本次记录的更改都添加到暂存区后,就可以执行提交操作了。
-
运行以下命令,其中
-m
参数后的字符串是本次提交的说明信息,这是必不可少的git commit -m "必要的说明信息"
-
提交说明 (Commit Message):编写清晰、有意义的提交说明是一个至关重要的好习惯。
-
3. 提交历史的审查与追溯
-
提交完成后,可以使用
git log
命令来查看仓库的版本历史。 -
查看详细历史
git log
:此命令会按 时间倒序 列出所有的提交记录。每条记录包含:
- Commit Hash:一个唯一的 SHA-1 校验和,是该次提交的唯一标识符。
- Author:作者的姓名和邮箱地址(即在全局配置中设置的信息)。
- Date:提交的日期和时间。
- Commit Message:提交时填写的说明信息。
-
查看简洁历史
git log --oneline
:如果觉得默认的日志输出信息太多,可以使用此命令来查看一个更紧凑的版本,每条提交仅显示一行。
Git 分支:核心机制与应用
Git 极其重要的一个特性就是其轻量而强大的分支模型。分支允许在主开发线之外开辟一个独立的工作空间,进行新功能开发、Bug 修复或实验性尝试,而不会影响到主线的稳定。
1. 分支(Branch)的概念与应用场景
- 什么是分支? 在技术上,Git 的分支本质上是一个指向某次提交(Commit)的可移动指针。默认情况下,Git 创建的第一个分支名为
master
或main
。每当进行一次新的提交,这个指针就会自动向前移动,指向最新的提交。 - 分支的功用(为什么需要分支)?
- 隔离开发:假设你需要开发一个复杂的新功能,预计需要几天时间。如果直接在主分支上编码,那么在开发完成前,主分支将一直处于不稳定状态,这会严重影响团队其他成员的工作。通过创建一个新的“功能分支”,就可以在一个完全隔离的环境中工作,直到功能开发完毕、测试通过后,再将其合并回主分支。
- 并行工作:在一个团队中,不同的开发者可以创建各自的分支来同时进行不同的任务,互不干扰。
- 版本管理:通常会维护一个长期稳定的主分支(如
main
),用于发布生产版本。同时创建一个develop
分支用于日常开发。当需要发布新版本时,再从develop
分支合并到main
分支。这种工作流被称为 Git Flow,是业界流行的分支策略之一。
2. 分支管理的基础指令
-
列出分支:查看当前仓库中所有的本地分支。
git branch
- 当前所在的分支名前会有一个星号
*
标记。
- 当前所在的分支名前会有一个星号
-
创建新分支:
git branch <branch-name>
- 例如:
git branch feature-login
。这个命令只创建了新分支,但仍然停留在当前分支。
- 例如:
-
切换分支:
-
现代 Git 推荐使用
switch
命令,其语义更清晰。git switch <branch-name>
-
传统上使用
checkout
命令,至今仍广泛使用。git checkout <branch-name>
-
-
创建并立即切换到新分支:这是日常开发中最常用的命令之一。
-
使用
switch
:git switch -c <branch-name>
-
使用
checkout
:git checkout -b <branch-name>
-
-
删除分支:当一个分支的工作完成并已合并到主线后,通常可以将其删除.
git branch -d <branch-name>
-d
是--delete
的缩写,它只在分支被完全合并后才允许删除。如果分支上有未合并的提交,Git 会提示错误。- 如果一定要强制删除一个未合并的分支,可以使用
-D
大写参数。
3. 分支的合并策略
-
当在一个分支上的工作完成后,就需要将其成果合并回主开发线(例如
main
分支)。 -
操作步骤:
-
首先,切换回希望并入的目标分支。
git switch main
-
然后,执行
merge
命令,将指定分支合并过来。git merge <branch-name-to-merge>
-
-
合并类型:
- 快进式合并 (Fast-forward):如果目标分支(
main
)在创建功能分支后没有任何新的提交,那么合并时 Git 只会简单地将main
指针直接移动到功能分支的最新提交上。这个过程非常快,因为没有需要整合的工作。 - 三方合并 (Three-way Merge):如果在开发功能分支的同时,目标分支(
main
)上也有了新的提交,Git 就无法进行快进式合并。此时,Git 会执行三方合并。它会找到两个分支的共同祖先,并将两个分支各自的修改与共同祖先进行比较,然后生成一个新的“合并提交”(Merge Commit)。这个新的提交会有两个父提交。
- 快进式合并 (Fast-forward):如果目标分支(
4. 合并冲突的识别与解决
-
什么是合并冲突? 当两个不同的分支对同一个文件的同一部分进行了修改时,Git 无法自动判断应该保留哪个版本,这时就会产生合并冲突(Merge Conflict)。Git 会暂停合并过程,等待手动解决冲突。
-
解决冲突的流程:
-
识别冲突文件:执行
git merge
后,如果出现冲突,Git 会在命令行中明确提示。也可以使用git status
查看哪些文件处于冲突状态。 -
编辑冲突文件:打开冲突的文件,可以看到类似下面的标记:
<<<<<<< HEAD 这是当前分支(例如 main)的内容。 ======= 这是要合并过来的分支(例如 feature-login)的内容。 >>>>>>> feature-login
<<<<<<< HEAD
到=======
之间是当前所在分支(HEAD)的内容。=======
到>>>>>>>
之间是要合并过来的分支的内容。
-
手动解决:需要根据实际需求,决定保留哪部分内容,或者将两部分内容重新整合。解决冲突的关键就是删除 Git 添加的这些特殊标记 (
<<<<<<<
,=======
,>>>>>>>
),并将文件修改为最终想要的样子。 -
标记为已解决:当完成文件的修改并保存后,需要使用
git add
命令来告诉 Git,这个文件的冲突已经解决了。git add <conflicted-file-name>
-
完成合并:当所有冲突文件都通过
git add
标记为已解决后,最后执行一次git commit
来完成整个合并过程。Git 会自动生成一个默认的合并提交信息,也可以自行修改。
-
远程协作:与远程仓库的交互
到目前为止,我们所有的操作都局限在本地仓库中。为了与团队成员协作,或将代码备份到云端,我们需要与远程仓库(Remote Repository)进行交互。
1. 远程仓库(Remote)
-
什么是远程仓库? 远程仓库是托管在网络服务器上的项目版本库。它可以被团队中的多名成员访问,用于同步和分享彼此的工作成果。
-
作用:
- 团队协作:提供一个所有成员都可以访问的“中央”代码库,方便大家交换代码和协同开发。
- 代码备份:将本地的代码库完整地备份到云端,防止因本地设备损坏导致的数据丢失。
-
以 GitHub 为例:GitHub 是全球最大的代码托管平台。您可以在 GitHub 上免费创建公开或私有的远程仓库。本教程将主要以 GitHub 作为远程仓库的实例进行说明。
2. 远程仓库的连接与克隆
场景一:从零开始,加入一个现有项目
-
如果要参与一个已经在 GitHub 上存在的项目,最直接的方式就是使用
git clone
命令。 -
git clone <repository-url>
:此命令会完整地复制一个远程仓库到本地。它会自动创建一个与远程仓库同名的文件夹,下载所有版本历史,并自动将远程仓库的地址命名为origin
,设置好跟踪关系。git clone https://github.com/user/project.git
场景二:将已有的本地项目推送到远程
-
如果你已经在本地初始化了一个 Git 仓库并进行了一些提交,现在想把它发布到 GitHub 上。
-
步骤:
-
在 GitHub 上创建一个新的空仓库(不要勾选初始化 README 等文件)。
-
GitHub 会提供该仓库的 URL。
-
在本地仓库中,使用
git remote add
命令将本地仓库与远程仓库关联起来。git remote add origin https://github.com/user/new-project.git
origin
是远程仓库的默认别名,也可以使用其他名称。
-
可以使用
git remote -v
命令查看已配置的远程仓库信息。
-
需要注意的是,在 GitHub 上创建仓库时创建的主分支名称默认为
main
,而本地使用git init
初始化的仓库主分支名称默认为master
,如果想要将本地分支的内容直接推送到 GitHub 上的主分支,需要将两个命名统一。可以使用前文提到的
git init -b <name>
来强制命名本地分支,也可以在 GitHub 设置中修改创建仓库时的默认分支名称。如果在 GitHub 上创建仓库时勾选了初始化 README 等文件导致初始仓库不为空,第一次可能会推送不成功。此时需要先使用
git pull
命令将远程仓库合并到本地,再使用git pull
推送。
3. 数据同步:推送(Push)与拉取(Pull)
-
推送
git push
:将本地的提交分享到远程仓库。-
git push <remote-name> <branch-name>
:此命令会将本地指定分支上的所有新提交上传到远程仓库对应的分支。 -
首次推送一个新创建的本地分支时,通常需要使用
-u
(或--set-upstream
) 参数来建立本地分支与远程分支的跟踪关系。之后,就可以直接使用git push
而无需指定远程和分支名。# 首次推送 feature 分支并建立跟踪关系 git push -u origin feature # 之后在该分支上,可以直接推送 git push
-
-
拉取
git pull
:从远程仓库获取最新的更改并合并到本地。-
git pull <remote-name> <branch-name>
:此命令会查找远程仓库指定分支上的更新,将其下载到本地,并尝试与本地的当前分支进行合并。 -
如果已经建立了跟踪关系,可以直接使用
git pull
。git pull origin main
-
4. git pull
与 git fetch
的功能对比
- 这是初学者经常混淆的一对命令。理解它们的区别对于避免不必要的合并冲突至关重要。
git fetch
:只下载,不合并git fetch
命令会将远程仓库的最新历史记录下载到本地,但它不会修改当前的工作区或分支。它会将这些更新保存在一个特殊的命名空间下(例如origin/main
)。- 使用场景:当想要查看远程仓库有什么新的更新,但又不希望立即将这些更改合并到自己的工作中时,
fetch
是一个安全的选择。可以在fetch
之后,使用git log origin/main
查看远程分支的提交历史,或者使用git diff main origin/main
比较本地与远程的差异,然后再决定是否合并。
git pull
:下载并合并git pull
命令在本质上是两个命令的组合:git fetch
:从远程仓库下载最新的内容;git merge
:将下载下来的远程分支(如origin/main
)合并到当前的本地分支(如main
)。
- 使用场景:当确定希望立即获取远程更新并应用到本地工作时,
git pull
是一个方便的命令。但需要注意,如果本地和远程有冲突的提交,pull
会直接触发合并冲突。
操作撤销与版本回滚
在开发过程中,犯错在所难免。无论是写错了代码、提交了多余的文件,还是想彻底放弃某个功能的尝试,Git 都提供了多种撤销操作的手段。本章将介绍如何针对不同阶段的错误进行“反悔”。
1. 修订最近一次提交 git commit --amend
-
适用场景:刚刚完成一次提交(
git commit
),但是发现:- 提交说明(Commit Message)写错了或不够清晰。
- 漏掉了一两个文件没有添加到暂存区。
-
操作方法:
-
如果只是想修改提交说明,直接运行:
git commit --amend
这会打开默认文本编辑器,可以重新编辑上次的提交说明。
-
如果想将遗漏的文件也添加到上一次提交中,可以先
git add
这些文件,然后再运行--amend
:git add forgotten-file.txt git commit --amend
-
-
工作原理:这个命令并不会在原来的提交上进行修改,而是会创建一个全新的提交,用来替换掉刚才那次“错误”的提交。
-
⚠️注意:如果这个“错误”的提交已经被推送到了远程仓库,那么不建议使用
--amend
。因为修改历史会与远程仓库产生冲突,给团队其他成员带来麻烦。只对尚未推送到公共仓库的提交使用--amend
。
2. 撤销暂存区中的文件 git reset
-
适用场景:使用
git add
将文件添加到了暂存区,但后来又不想将这个文件的更改包含在下一次提交中了。 -
操作方法:
git reset HEAD <file-name>
HEAD
是一个指向当前分支最新一次提交的指针。这条命令的意思是,将暂存区里的指定文件恢复成和HEAD
指向的版本一样,也就是把它从暂存区里“拿出来”。
-
文件会从暂存区(Staged)状态变回已修改(Modified)但未暂存(Unstaged)的状态。在工作区对该文件所做的修改内容会被保留。
3. 废弃工作区的本地修改 git checkout
或 git restore
-
适用场景:在工作区修改了一个文件,但后来觉得这些修改完全是错误的,想彻底放弃,恢复到该文件最近一次提交时的状态。
-
操作方法:
-
传统方式:
git checkout -- <file-name>
-
现代方式(推荐):新版 Git 提供了
restore
命令,语义更清晰。git restore <file-name>
-
-
⚠️注意 :这是一个危险且不可逆的操作!它会用仓库中该文件的最新版本直接覆盖掉在工作区的所有修改。一旦执行,所做的本地修改将永久丢失,无法找回。
4. 历史版本的恢复策略:reset
或 revert
-
当需要撤销的不是某个文件的修改,而是整个或某几个历史提交时,就需要用到版本回退了。Git 提供了两种主要的回退方式:
reset
和revert
。 -
git reset
:移动指针,重写历史- 工作原理:
git reset
命令会直接移动当前分支的HEAD
指针以及分支指针到指定的某个历史提交上。根据参数不同,它对暂存区和工作区的影响也不同。 - 常用模式
git reset --hard <commit-id>
:这是最彻底也是最危险的模式。它会:- 将分支指针移动到
<commit-id>
。 - 将暂存区更新为
<commit-id>
的内容。 - 将工作区也强制更新为
<commit-id>
的内容。 这意味着从<commit-id>
之后的所有提交记录以及所有未提交的本地修改都会被彻底删除。
- 将分支指针移动到
- 适用场景:仅用于私有分支或未被推送到公共仓库的提交。当确定自己本地的某段提交历史完全是错误的,并且想彻底抹除它们时使用。绝对不要对已经推送到公共仓库的提交使用
git reset --hard
。
- 工作原理:
-
git revert
:创建反向操作,保留历史-
工作原理:
git revert
不会删除或修改任何历史记录。相反,它会分析指定的那个历史提交,然后创建一个全新的提交,这个新提交的内容刚好是用来抵消掉那个历史提交所做的所有更改。 -
操作方法:
git revert <commit-id>
-
适用场景:这是在公共分支上撤销提交的标准且安全的方式。因为它不改变项目历史,只是在历史的末尾追加一次“反向提交”,所以对于已经推送到远程并被团队成员拉取的提交,使用
revert
不会造成历史冲突。其他成员只需正常git pull
即可同步这次撤销操作。
-
-
总结对比:
特性 git reset –hard git revert 历史记录 修改(删除)现有历史 不修改历史,而是追加新历史 操作结果 HEAD 指针和分支指针移动到过去 在当前分支顶端创建一个新的“反向”提交 安全性 危险,会丢失数据 安全,保留所有记录 适用范围 仅限本地私有提交 公共、共享的提交
高级功能与技巧
此处内容暂不详细学习,仅做了解。
- 使用
.gitignore
排除文件;- 配置该文件以指定不需要纳入版本控制的文件或目录。
- 版本标记(Tagging)的应用;
git tag
:为特定的提交(例如软件发布版本)创建永久性的标记。
- 工作进度的临时储藏(Stashing)
git stash
:临时保存当前工作目录和暂存区的状态。
- 提交历史的重写(Rebasing)
git rebase
:用于合并一系列提交,以创造一个更线性的提交历史。
总结
1. 核心概念回顾
-
三大区域:我们所有的本地工作都在工作区进行,通过
git add
将选定的更改放入暂存区,最后通过git commit
将这些更改永久记录到本地仓库。 -
分支:分支是 Git 的核心,它允许我们创建独立的工作空间来开发新功能或修复问题,而不影响主线的稳定性。
git branch
,git switch
,git merge
是我们的主要工具。 -
远程协作:通过
git clone
,git push
,git pull
等命令,我们可以与 GitHub 等远程平台进行交互,实现团队协作和代码备份。
2. 常用命令速查表
任务分类 | 命令 | 功能描述 |
---|---|---|
配置 | git config --global user.name "Name" |
设置全局用户名 |
git config --global user.email "Email" |
设置全局用户邮箱 | |
创建仓库 | git init |
在当前目录初始化一个新仓库 |
git clone <url> |
从远程地址克隆一个仓库 | |
日常操作 | git status |
查看仓库当前状态 |
git add <file> 或 git add . |
将文件更改添加到暂存区 | |
git commit -m "Message" |
将暂存区内容提交到本地仓库 | |
git log 或 git log --oneline |
查看提交历史 | |
分支管理 | git branch |
列出、创建或删除分支 |
git switch <branch> 或 git checkout <branch> |
切换到指定分支 | |
git switch -c <branch> 或 git checkout -b <branch> |
创建并切换到新分支 | |
git merge <branch> |
将指定分支合并到当前分支 | |
远程同步 | git remote -v |
查看已配置的远程仓库 |
git fetch |
从远程仓库下载最新历史,不合并 | |
git pull |
从远程仓库下载并合并 | |
git push |
将本地提交推送到远程仓库 | |
撤销操作 | git commit --amend |
修改最后一次提交 |
git restore <file> |
放弃工作区的修改 | |
git revert <commit-id> |
创建一个新提交来撤销历史提交 | |
git reset --hard <commit-id> |
(危险)回退到某个历史版本 |
3. 编写高质量的 Commit Message (选学)
业界公认的格式:
<type>: <subject>
[optional body]
[optional footer]
-
<type>
(类型):说明本次提交的类别,常用的有:feat
: 新功能 (feature)fix
: 修复 bugdocs
: 文档变更style
: 代码格式(不影响代码运行的变动)refactor
: 重构(既不是新增功能,也不是修改 bug 的代码变动)test
: 增加测试chore
: 构建过程或辅助工具的变动
-
<subject>
(主题):用一句话简要描述本次提交的目的,不超过 50 个字符。 -
body
(正文):可选。对本次提交进行更详细的描述,解释“为什么”要这样修改。 -
footer
(脚注):可选。通常用于关闭 Issue (例如Closes #123
)。
示例:
feat: Add user login functionality
Implement the user login API endpoint and basic validation.
The endpoint accepts username and password, and returns a JWT token upon successful authentication.
Closes #42