1. 为什么需要 Git?

1.1 没有 Git 会发生什么?

没有Git,文件版本混乱难以整理。在没学 Git 之前,很多初学者会通过不同的文件名来区别代码版本:

final.py
final_new.py
final_new2.py
final_真正最终版.py
...

这种通过文件名管理版本的方式存在很多问题。
例如:

  • 无法清晰追踪每次代码修改内容
  • 修改历史容易丢失
  • 多人协作时容易互相覆盖代码
  • 当代码出现 Bug 时,很难快速回退到之前稳定版本

随着项目越来越复杂,
传统的文件复制方式已经无法满足开发需求。

这就是 Git 产生的原因。

Git 简介:
Git 是一种分布式版本控制工具,
能够帮助开发者管理代码历史、追踪文件修改、
实现多人协作开发,并支持快速回退历史版本。


1.2 Git 与 SVN 的区别

目前常见的版本控制工具主要有:

  • SVN(集中式)
  • Git(分布式)

SVN(集中式)

SVN 的核心特点是:

所有代码都依赖中央服务器进行管理。

开发者本地通常只保存当前项目代码,
真正完整的版本历史存放在远程服务器中。

因此:

  • 如果服务器异常,可能无法提交代码
  • 断网情况下,很多操作会受到限制
  • 团队协作对中央服务器依赖较强

Git(分布式)

Git 采用分布式版本管理。

每个开发者本地都会保存完整仓库,
包括:

  • 代码文件
  • commit 历史
  • branch 信息

因此即使在断网环境下:

  • 依然可以 commit
  • 依然可以查看历史版本
  • 依然可以创建分支

Git 也因此更适合现代多人协作开发。


2. Git 核心概念

2.1 Git 四大区域

工作区 Working Directory

工作区指的是开发者当前真正编写和修改代码的目录。

例如:

project/
|— main.py
|- user.py
|- config.py

这些文件所在目录就是工作区。


你:

  • 新建文件
  • 修改代码
  • 删除文件

都发生在工作区


但这里有一个重点:
工作区不代表 Git 已经记录。

很多初学者认为:

我保存文件了
=
Git 已经保存版本了

这是错误的。
Git 并不会在文件保存时自动记录版本,
因此开发者需要通过 git add
主动将修改加入暂存区。

git add

暂存区 Stage

暂存区是 Git 中非常经典的设计之一,
也是很多初学者最容易困惑的地方。

暂存区到底是什么?
你可以把它理解为:”待提交清单“。

例如,你修改了以下文件:

login.py
user.py
config.py

但你现在,只想提交login功能。
于是你执行了git add命令:

git add login.py

这可以看成是:

把 login.py 放进“下一个提交名单”

为什么 Git 不直接 commit ?
因为 Git 希望你能“选择性提交”。

例如:
你今天改了:

文件 是否完成
login.py ✅ 完成
payment.py ❌ 写一半
test.py ❌ 调试代码

如果没有暂存区:

commit 会把所有修改一起提交

这会很混乱。

所以暂存区解决了“精确控制提交内容”, 这也是 Git 能够支持高质量版本管理的重要原因之一。


本地仓库 Local Repository

本地仓库用于保存 Git commit 历史记录。

当你执行:

git commit

Git 会生成一个 commit 对象。
保存:

  • 当前代码快照
  • 作者
  • 时间
  • commit message

需要注意的是:
只有执行 git commit 后,Git 才会真正生成历史版本。


远程仓库 Remote Repository

例如:

  • GitHub
  • Gitee
  • GitLab

通俗来说,它们就是代码同步中心。但远程仓库本质上仍然是 Git 仓库,只是托管在远程服务器中。


为什么需要远程仓库?
因为本地仓库只有你自己才能看到。

远程仓库可以:

  • 多人协作
  • 备份代码
  • Pull Request
  • Code Review

Git 四大区域流转

Git 的核心要领,就是掌握代码如何在四大区域流转。
在这里插入图片描述

2.2 Git 数据流

理解Git的数据流,能够帮助你快速理清 Git 工作原理。


Git 数据流本质

工作区
↓ git add
暂存区
↓ git commit
本地仓库
↓ git push
远程仓库

举个真实例子

假设你正在开发:

FastAPI 登录功能

step1:修改代码
def login():
    return {"msg": "login success"}

现在代码只存在于工作区,
Git 还没有正式记录这次修改。


step2:git add
git add login.py

意思是:

“这个文件已经准备好提交”

此时文件会被加入暂存区。


step3:git commit
git commit -m "feat: add login api"

现在Git 才真正生成历史版本,并进入:
本地仓库。


step4:git push
git push

通过push,代码同步到远程仓库。

2.3 Git 为什么需要暂存区?

Git 设计暂存区,是为了让开发者能够“精确控制提交内容”。开发过程中,一个项目往往会同时修改多个文件。但有些功能可能已经完成,有些代码仍在开发中。

因此 Git 提供了暂存区(Stage),用于存放“已经确认准备提交”的代码,从而避免一次性提交所有修改内容。


举个工程例子

你修改:

login.py
payment.py
test.py

但现在只有 login.py 完成了。
你只要将 login.py 加入暂存区,然后完成提交。

git add login.py
git commit

这样 commit 只包含:

login 功能

这有什么好处?
  1. commit 更清晰:
    每一次的 commit只做一件事,即把暂存区的文件提交。

  1. 更适合回滚:
    如果 login功能有Bug,只需要回退 login 的commit,这样可以快速回退到之前稳定的版本。

  1. 更适合团队协作:
    Git 的 版本管理使得 Code Review 更清晰。

2.4 Git 的快照机制

很多初学者误认为:Git 保存“文件变化”。
实际上:
对于未变化的文件,Git 会复用已有对象,从而减少存储空间占用。
每一次 commit,都代表项目在当前时间点的完整状态。


什么叫快照?

例如:
commit1

print("hello")

commit2

print("hello world")

Git 会认为:

这是两个时间点的完整状态

而不是:

"第二次只增加了 world"

这就是 Git 的快照思想。


为什么快照机制强?

因为:

  1. 回滚速度快:直接切换快照即可。
  2. branch 创建成本低:因为branch 本质只是一个 commit 指针,而不是复制整个项目。

2.5 commit、branch、HEAD 本质

commit 是什么?

很多初学者以为:

commit = 提交动作

其实不完整。


commit 本质是:“某一时刻代码状态的快照”。每个 commit 都会记录父 commit 信息,从而形成 Git 的版本链。
commit 里面保存:

  • 文件状态
  • 作者
  • 时间
  • message
  • 父 commit
branch 是什么?

很多人误解:

创建 branch = 复制一份代码

实际上,Git 的 branch 本质上是:

branch 是指向 commit 的可移动指针

举例

main -> commit3

新增 commit:

main -> commit4

main 指针移动了。

所以:
Git 创建分支几乎没有成本,因为它只是个指针。


HEAD 是什么?

HEAD 本质上是一个特殊指针,
用于表示当前正在检出的 commit 或 branch。

通常情况下:
HEAD -> main

意思是:当前正在main 分支工作。


当 commit 时,HEAD 会和 main 一起移动。
例如:

HEAD -> main -> commit4

新增 commit 后:

HEAD -> main -> commit5

3. Git 基础操作

3.1 初始化仓库

在使用 Git 管理项目之前,都需要将当前目录初始化为 Git 仓库。

git init

执行完成后, 当前目录下会生成一个隐藏文件夹:
在这里插入图片描述
在这里插入图片描述

这个目录保存了 Git 的版本数据、commit历史、branch信息、暂存区文件等等。
此时完成目录的初始化后,Git 才会开始对当前目录进行版本管理。
在这里插入图片描述

我在git_demo目录下已经创建了一个空的 Git 仓库,可以看到命令行提示符末尾出现了 (master),说明当前在 master 分支上。


3.2 文件状态机制

Git 会持续追踪文件状态变化。
一个文件在 Git 中, 通常会经历以下几种状态:

状态 含义
untracked 未被 Git 跟踪
modified 文件已修改
staged 已加入暂存区
committed 已提交到本地仓库

Git 的很多命令,本质上就是在操作文件状态转换。


untracked - 未跟踪

例如,我在git_demo目录下新建login.py文件:

touch login.py

接着执行:

git status # 查看当前目录下所有文件状态

Git Bash会显示

untracked files

在这里插入图片描述

这说明 Git 发现了新文件,但是还没有正式追踪它。


modified - 已修改

如果一个已经被 Git 管理的文件发生修改,Git 会认为文件内容已经变化,此时文件进入modified 状态。但Git 仍然没有生成新版本,因为没有执行addcommit操作。

例如:
login.py 原本内容是:

def login():
    return "git add success"

现在修改为:

def login():
    return "modified"

使用git status命令查看当前状态,login.py文件已转入modified状态。
![[Pasted image 20260510012852.png]]


staged - 已暂存

将login.py文件加入暂存区:

git add login.py

文件进入Stage暂存区,此时 Git 会认为login.py文件已经准备好提交。
在这里插入图片描述


committed - 已提交

将 login.py 的修改提交到本地仓库,生成Git历史版本:

git commit -m "feat: add login api"

提交之后,Git 返回:当前所在的分支名、这次提交的Commit ID前7位(即版本号)以及提交的信息。
注:
(root -commit) 表示这是该仓库的第一个提交(根提交),没有父提交。
1 file changed表示有 1 个文件发生了变化,0 insertions(+)表示插入了 0 行代码(因为 login.py 是空文件),0 deletions(-)表示删除了 0 行代码。
100644是Linux 文件权限码,表示这是一个普通文件。- create mode表示文件被创建并加入版本控制

查询文件状态,Git 会告诉你,当前没有待提交的修改,工作区很干净,暂存区已清空。
![[Pasted image 20260510014158.png]]


3.3 查看状态与历史

在实际开发中,我们可能会想知道当前有哪些文件被修改、哪些文件已经加入暂存区、最近提交了哪些版本…
这些都需要通过 Git 提供的历史查看命令完成。


git status

在3.2文件状态机制,我们已经使用过。

git status

这个命令会告诉你:

  • 当前所在分支
  • 哪些文件发生修改
  • 哪些文件已加入暂存区
  • 是否存在未跟踪文件

git log

git log用于查看commit历史记录。
这里会显示:

  • 完整的版本号- commit id
  • 作者
  • 提交时间
  • commit message
    在这里插入图片描述

git log 常用参数

–oneline

表示每次提交信息只占一行。

git log --oneline

Git 会以更简洁的方式返回日志:
076b435:最新一次提交的 Commit ID(前7位)
(HEAD -> master):HEAD 指向当前分支 master,表示我现在就在这个提交上
feat: add register api:最新提交的信息
f799369:上一次提交的 Commit ID(前7位)
feat: add login api:第一次提交的信息
在这里插入图片描述

–online --graph --all

表示图形化显示提交数,以及显示所有分支。

git log --oneline --graph --all

目前只有一个 master 分支,所以图形化没有完整体现。
在这里插入图片描述

这个命令非常适合查看branch与merge结构,后面学习分支时会经常使用。


git reflog

git reflog用于记录 HEAD 指针移动历史。
以下操作,都会导致 HEAD 移动,如:

  • 代码提交 commit
  • 版本穿梭 reset
  • 分支切换 checkout
  • 分支合并 merge
    而 reflog 会记录这些行为。
    在这里插入图片描述

注:
(HEAD -> master):指当前 HEAD 指向 master 分支的这个提交
HEAD@{0}:这是最近一次 HEAD 移动的记录(编号从0开始)
commit: feat: add register api:操作类型是提交,提交信息是这个
f799369:上一个提交的 Commit ID
HEAD@{1}:这是倒数第二次 HEAD 移动的记录
commit (initial): feat: add login api:操作类型是初始提交(第一个提交)


git log VS git reflog

命令 显示内容 特点
git log commit 的提交历史(仓库的状态快照) 只显示当前分支能追溯到的提交
git reflog HEAD 的操作历史(HEAD 的移动记录) 显示所有本地操作,包括被 reset、checkout 等操作引用的提交

git reflog可以帮助我们找回丢失的commit。
举例,我通过:

git reset --hard 版本号
或者
git reset 版本号

撤回 commit,回到register.py没有被提交的版本。
在这里插入图片描述

再使用:

git reflog

查看所有操作,找到原先的版本号,最后在回到rehgister.py被提交了的版本。
在这里插入图片描述


3.4 文件比较与撤销

Git 不仅可以管理版本,还可以帮助开发者对比修改与撤销错误操作。

git diff - 文件比较

git diff 用于查看文件修改内容。
返回结果包括:

  • 哪些代码发生变化
  • 代码的变换位置
  • 哪些内容被删除
  • 哪些内容被新增

举例:
我在 register.py添加了register()方法:

def register():
    return{"success"}

再使用

git diff register.py

查看修改内容:
![[Pasted image 20260510150248.png]]

Git 对比了暂存区中的版本(a/register.py)和工作区的版本(b/register.py),告诉我-0,0原文件中从第0行开始,共0行(空文件),+1,2新文件中从第1行开始,共2行,发现我在空文件添了那样两行代码。

必须要注意的是git diff比较的是:工作区 与 暂存区。

git diff --cached 则比较的是 暂存区 与 本地仓库。

举例:
register修改完后,我没有将其加入暂存区,所以此时比较暂存区与本地仓库没有任何输出。

在这里插入图片描述

当我将 register.py 加入暂存区后,输入:

git diff --cached register.py

Git 才会返回比对结果:
![[Pasted image 20260510151524.png]]


工作区撤销

如果文件还没有加入暂存区,可以使用:

git restore 

撤销对工作区的修改。

例如:
将register.py文件内容修改为:

def register():
    return{"bug"}

但我们想要将文件恢复到上一次的转态时间,就可以执行:

git restore register.py

同时 register.py 的状态变为modified
![[Pasted image 20260510152404.png]]

实际上,restore就是用最近版本覆盖当前工作区。


暂存区撤销

如果文件已经执行add操作,可以使用:

git restore --staged 

来将文件移出暂存区。

举例,我们将 register.py 将入暂存区,可以看到它是绿色,当前文件在暂存区,等待提交。
![[Pasted image 20260510153403.png]]

接着执行移出暂存区操作:

git restore --staged register.py

register.py 变为红色,文件不在暂存区了,只在工作区。
![[Pasted image 20260510154122.png]]


commit 回退

即前文提到过的版本穿梭。

git reset --hard 版本号
或者
git reset 版本号

可以配合git reflog使用,帮助你快速回到版本。

版本穿梭是 Git 中最容易误操作的命令之一,它会直接影响:

  • HEAD
  • branch
  • 暂存区
  • 工作区

不过 Git 提供了reset 的三种模式,避免全部修改。

模式 HEAD 暂存区 工作区
soft 回退 保留 保留
mixed 回退 清空 保留
hard 回退 清空 清空

–sotf

只移动 HEAD。

git reset --soft 版本号

它会使得 commit 消失,但保留了暂存区和工作区。
适用场景是,你需要修改你的commit message。


–mixed

默认模式

git reset --mixed 版本号
等价于
git reset 版本号

它会使 commit 消失,暂存区清空,但工作区保留。
这是最常用的 reset 模式。


–hard

这是一个很危险的模式。

git reset --hard 版本号

它会使 commit 消失,暂存区清空,工作区恢复。
也就是说,工作区代码也会被覆盖,修改会直接丢失。

更多推荐