为什么要用Git

想象你正在写一篇论文:

  • 论文_初稿.doc

  • 论文_修改1.doc

  • 论文_最终版.doc

  • 论文_最终版_绝对不改.doc

时间一长,你不知道哪个才是最新版,也不敢删掉旧的。Git 就是帮你管理这些“版本”的工具,它会记录每次修改的内容、时间、作者,让你能随时回到过去的某个状态,也能和别人并行合作,最后合并到一起。

核心概念先有个印象:

  • 仓库(Repository):一个被 Git 管理的项目目录,里面藏着完整的历史记录。

  • 提交(Commit):一次“保存快照”的操作,就像游戏的存档点。

安装并配置Git

环境:WSL2,Ubuntu24.04

打开终端,先更新包列表,再安装 Git:

sudo apt update
sudo apt install git -y

安装完成后,检查是否成功:

git --version

你应该会看到类似 git version 2.43.0 的输出(版本号以实际为准)。

配置身份

Git 每次提交都会记录“谁做的”,所以要先告诉 Git 你的名字和邮箱。这个信息会随提交一起被永久记录在历史里。

在终端依次执行(把名字和邮箱换成你自己的,建议邮箱使用 GitHub 注册邮箱):

git config --global user.name "你的名字"
git config --global user.email "你的邮箱@example.com"

--global 表示这个配置对你整个系统的当前用户生效,所有 Git 仓库都会默认使用这个身份。

验证一下配置是否生效:

git config --global --list

你会看到类似输出:

user.name=你的名字
user.email=你的邮箱@example.com

可选设置

为了让以后的输出更易读,建议打开命令行颜色高亮:

git config --global color.ui auto

在 Ubuntu 终端中,这会让 diff、status 等命令的输出用不同颜色标记增、删、改,看起来一目了然。

创建仓库与第一次提交

创建第一个项目目录

首先,我们在主目录下建一个练习文件夹,叫 my-first-repo

cd ~
mkdir my-first-repo
cd my-first-repo

初始化仓库

让 Git 接管这个目录:

git init

你会看到类似输出:

Initialized empty Git repository in /home/你的用户名/my-first-repo/.git/

这行字告诉你:仓库诞生了。它会在这个目录下创建一个隐藏文件夹 .git,里面存着 Git 需要的所有历史数据。用 ls -a 可以看到它。

重要认知git init 后,这个目录就变成了工作区(Working Directory),里面的 .git 文件夹就是仓库(Repository)。

Git 的三个区域

继续之前,必须先把这三个区域记在心里,这是 Git 的灵魂:

  1. 工作区(Working Directory)

    • 就是你能看到的项目文件夹,你直接在这里增、删、改文件。

    • Git 会监视这里的变化,但不会自动记录。

  2. 暂存区(Staging Area / Index)

    • 你希望被记录的那些修改,需要先“添加”到这个区域。

    • 可以理解为一张“准备提交的快照暂存清单”。

    • 使用 git add 会把文件从工作区送到暂存区。

  3. 仓库(Repository / Commit History)

    • .git 文件夹里的东西,藏着所有历史提交。

    • 使用 git commit 会把暂存区的内容永久保存为一个“提交”。

工作流程是:

工作区 ──(git add)──> 暂存区 ──(git commit)──> 仓库

记住这个流程,后面你就能理解每一个命令在干什么。

创建第一个文件

在工作区创建一个文件 hello.txt

echo "Hello, Git" > hello.txt

现在用 git status 看一眼仓库状态:

git status

输出会显示:

On branch master

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        hello.txt

nothing added to commit but untracked files present (use "git add" to track)

Git 告诉你:

  • 当前在 master 分支(这是默认的主分支名)。

  • 还没有任何提交(No commits yet)。

  • hello.txt 是未跟踪文件(Untracked),工作区有个新文件,但 Git 还没管它。

把文件加入暂存区

让 Git 开始跟踪 hello.txt

git add hello.txt

再运行 git status 看看变化,现在输出像这样:

On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   hello.txt

hello.txt 现在在暂存区 了,状态是 “new file”,准备被提交。

第一次提交

把暂存区的内容永久保存到仓库中:

git commit -m "第一次提交:添加 hello.txt"
  • -m 后面跟的是提交信息(commit message),用来描述这次做了什么修改。

  • 提交信息必须写,而且最好写清楚,这是以后看历史的你会感谢你的事。

执行后你会看到:

[master (root-commit) xxxxxxx] 第一次提交:添加 hello.txt
 1 file changed, 1 insertion(+)
 create mode 100644 hello.txt

这意味着:

  • master 分支上生成了第一个提交(root-commit 表示它是整个历史的根)。

  • 一个文件改变了,添加了一行内容。

  • 提交的哈希值(一串十六进制字符)已经生成,唯一标识这次提交。

查看历史

现在看看仓库里记录了什么:

git log

输出类似:

commit a1b2c3d4e5f...(一串哈希值)
Author: 你的名字 <你的邮箱@example.com>
Date:   Mon Apr 27 10:00:00 2026 +0800

    第一次提交:添加 hello.txt

你看到了:

  • 提交的唯一哈希值(不用记全,前几位就能区分)

  • 作者和日期信息,正是你第一章配置的身份

  • 提交信息

q 键可以退出 log 的浏览模式。

巩固流程

趁热打铁,再做一轮完整的修改→暂存→提交。

  1. 修改文件:向 hello.txt 追加一行

echo "Git is fun" >> hello.txt
  1. 查看状态:

git status
  1. 查看具体改动:

git diff

能看到你添加了什么内容(以 + 开头的绿字)。

  1. 加入暂存区:

git add hello.txt
  1. 提交:

git commit -m "添加第二行:Git is fun"
  1. 再次查看历史:

git log

现在会看到两次提交,从新到旧排列。最新的在上面。

深入暂存区与文件管理

接下来会教你后悔药怎么吃——修改错了怎么撤,提交反悔了怎么退,还会让你真正看透 git diff 的各种用法。先确认一下历史:

cd ~/my-first-repo
git log --oneline

你应该能看到类似两条记录(哈希值会不同):

a1b2c3d 添加第二行:Git is fun
e5f6g7h 第一次提交:添加 hello.txt

git diff 的三种常用姿势

git diff 是“对比神器”,不同参数对比不同区域。

  1. 工作区 vs 暂存区git diff,无参数)

先制造一个工作区修改:

echo "第三行:learning diff" >> hello.txt

现在修改只存在工作区,未暂存。运行:

git diff

输出会显示什么?以 + 开头的绿字是你新增的内容,前面带有 @@ 的行是位置标记。这个命令对比的是工作区和暂存区(如果暂存区为空,就是和最新提交比)。
因为暂存区现在还是原来的版本,所以你能看到新增的“第三行”。

  1. 暂存区 vs 最新提交git diff --staged

先把刚才的修改加入暂存区:

git add hello.txt

现在再运行 git diff 会发现没有任何输出,因为工作区和暂存区内容一致了。但你想看 暂存区和最新提交相比有什么变化,就用:

git diff --staged

(也可以用 git diff --cached,一个意思。)
这次你会看到刚才添加的 +第三行:learning diff,说明暂存区比当前提交多了这一行。

  1. 工作区 vs 最新提交git diff HEAD

在工作区再追加一行(暂存区没变):

echo "第四行:will be unstaged" >> hello.txt

现在工作区比暂存区多了一行,暂存区又比最新提交多了一行。直接比较工作区和最新提交:

git diff HEAD

你会看到两条新增行:一条是之前暂存的“第三行”,另一条是刚才加的“第四行”。
HEAD 总是指向当前分支的最新提交,所以这个命令跳过了暂存区,直接拿工作区和最后一次 commit 比。

小总结

  • git diff → 工作区 vs 暂存区

  • git diff --staged → 暂存区 vs HEAD

  • git diff HEAD → 工作区 vs HEAD

撤销工作区的修改

假设你现在发现“第四行”写错了,想丢弃工作区的这个修改,恢复成暂存区(或最新提交)的样子。在丢弃之前,再看一眼状态确认:

git status

你会看到 hello.txt 既有暂存的修改(绿色 “modified”),又有未暂存的修改(红色 “modified”)。现在我们丢弃工作区的修改(即刚才加的那行“第四行”):

git checkout -- hello.txt

或者新版本 Git 也可以使用等价命令 git restore hello.txt。执行后,查看文件内容:

cat hello.txt

你会发现“第四行”消失了,文件内容回到了暂存区保存的版本(只有三行)。再用 git diff HEAD 看,现在只显示暂存区里的“第三行”,说明工作区已经恢复到了暂存区的状态。

注意:这个操作会永久丢失工作区的未提交修改,而且很难恢复(除非用 IDE 的本地历史)。请一定确认要丢弃再执行。

取消暂存

当前的暂存区里还保存着“第三行”的修改。如果你决定:先不把第三行放在这次提交里,但保留在工作区,该怎么操作?

我们需要把文件从暂存区移回工作区。

git reset HEAD hello.txt

(新版本 Git 也可以用 git restore --staged hello.txt
运行之后,git status 你会看到:

  • “Changes to be committed” 里不再有 hello.txt

  • hello.txt 出现在 “Changes not staged for commit” 里,而且还是被修改过的状态

也就是说,文件从暂存区退回了工作区,修改本身没有丢失。

此时如果你又后悔了,可以再次 git add 把它暂存回去。如果你连这个修改都不想要了,可以用上一步的 git checkout -- hello.txt 彻底丢弃。

git reset 的三种模式

比撤销文件更彻底的,是撤销一次提交。我们先创建一个“临时”提交来演示。

先把刚才的第三行暂存并提交:

git add hello.txt
git commit -m "添加第三行:learning diff"

现在 git log --oneline 会显示三个提交。

接下来,我们要“退回”到上一个提交。git reset 可以移动当前分支指针,有三种常用模式:

模式

效果

提交

暂存区

工作区

--soft

只撤销提交,改动留在暂存区

回退

保留

保留

--mixed(默认)

撤销提交和暂存,改动留在工作区

回退

清空

保留

--hard

全部撤销,工作区也清掉

回退

清空

清除

1. --soft:提交后悔了,但想重新修改提交信息

执行(注意:把 HEAD~1 指向上一个提交):

git reset --soft HEAD~1

git status 查看状态,你会看到 hello.txt 的修改仍然在暂存区(绿色,等待提交),但 git log 里“添加第三行”那个提交已经消失了。这就像刚 git add 完,还没来得及 commit。你可以修改提交信息,或添加其他文件后再提交。

2. --mixed(默认):提交错了,想把修改推回工作区重新编辑

先重新提交回去,以便演示:

git commit -m "第三行(临时)"

现在再次回退,这次不给模式(默认就是 --mixed):

git reset HEAD~1

git status,你会发现:

  • 暂存区是空的

  • hello.txt 的修改在工作区,“not staged”

这就是让你重新整理要提交的内容,再 addcommit

3. --hard:彻底放弃,回到干净状态(危险)

我们故意创建一个新文件并提交,然后彻底丢弃它。

echo "临时垃圾文件" > junk.txt
git add junk.txt
git commit -m "添加一个不要的提交"

现在回退:

git reset --hard HEAD~1

结果:

  • git log 看,那个提交消失了

  • junk.txt 被彻底删除,工作区干干净净

这就是 --hard 的威力:它会让你的工作区和暂存区完全和回退到的提交一致,未提交的修改也会丢失。一般情况下,不要轻易用 --hard,除非你十分确定可以丢弃。

分支的世界

分支是 Git 真正强大的地方——你可以在一条独立的线上开发新功能,不影响主线的稳定,成熟后再合并回去。

想象你在玩一种可以存档的冒险游戏。主线剧情是 master(或现在叫 main),当你走到某个关卡,突然想试试“如果我先去拿隐藏道具会怎样”,你就可以在那个时间点创建一个分支,在里面自由探索。就算玩砸了,主剧情毫发无损;如果探索成功,就把分支的进展合并回主线。

在 Git 里,分支仅仅是一个指向某个提交的轻量级指针。创建分支几乎瞬间完成,完全不占什么空间。

默认的主分支名在较新的 Git 中是 main,但你的仓库可能还是 master(取决于版本和配置)。我们这里以 master 为例,如果你看到的是 main,命令中替换即可。

查看分支

先看看当前仓库有哪些分支:

git branch

输出:

* master

前面的 * 表示你当前所在的分支——master

创建分支

假设我们想给 hello.txt 添加一个“作者署名”功能,但不想直接在主线上改。我们创建一个叫 feature/author 的新分支:

git branch feature/author

git branch 再看一下分支列表:

  feature/author
* master

新分支出现了,但 * 还停留在 master 上。创建分支并不会自动切换过去。

切换分支

要进入新分支工作,需要切换过去。推荐使用较新的命令 git switch(Git 2.23+),语义更清晰:

git switch feature/author

你也可以使用传统的 git checkout feature/author,效果一样。现在再用 git branch 查看分支,* 已经移到 feature/author 前面了,表示你正在该分支上。

也可以同时创建并切换,用:

git switch -c 分支名
# 或
git checkout -b 分支名

在分支上工作和提交

现在你在 feature/author 分支上。让我们修改 hello.txt,在文件末尾添加一行署名:

echo "Author: GitLearner" >> hello.txt

git status 查看状态,暂存并提交:

git add hello.txt
git commit -m "在 feature/author 分支添加作者署名"

git log --oneline 看一下这个分支的日志,你会看到,feature/authormaster 多了一个新提交。切回 master 看看文件:

git switch master
cat hello.txt

你会发现,master 上的 hello.txt 完全没有那句 “Author: GitLearner”。因为那个修改只存在于 feature/author 分支上。这就是分支隔离的魔力。

合并分支

现在你觉得“署名功能”已经开发好了,想把它合并回主分支。首先确保你当前在想要合并到的目标分支上(即主分支 master):

git switch master

然后执行合并:

git merge feature/author

你会看到类似输出:

Updating a1b2c3d..f6g7h8i
Fast-forward
 hello.txt | 1 +
 1 file changed, 1 insertion(+)

关键词:Fast-forward(快进合并)

为什么会快进?

因为 master 自从分出 feature/author 之后,自己没有产生任何新提交。feature/author 相当于在 master 的基础上直线往前走了几步。合并时,Git 只需要把 master 的指针直接“快进”到 feature/author 的最新提交即可,无需创建额外的“合并提交”。

此时的提交历史是一条直线:

第一次提交 → 添加第二行 → 添加第三行 → 在 feature/author 分支添加作者署名

此时 masterfeature/author 指向同一个提交。你可以用 git log --oneline 验证。再查看 hello.txt,署名已经出现了。

合并后分支清理

功能分支完成使命后,可以安全删除:

git branch -d feature/author

查看分支列表,只剩 master。如果分支没有被合并,用 -d 会拒绝删除(防止误删),此时若确定不需要,可用 -D 强制删除。

理解“分叉”合并(非快进)

快进合并很干净,但现实里往往各个分支会同时推进,产生分叉历史。我们模拟一次。

  1. 创建并切换到新分支 feature/sign

git switch -c feature/sign
  1. 在这个分支上修改,在 hello.txt 添加一行 “Sign: Best regards”:

echo "Sign: Best regards" >> hello.txt
git add hello.txt
git commit -m "在 feature/sign 分支添加签名"
  1. 切回 master,在 master 上也做一个修改,比如新建一个文件 readme.md

git switch master
echo "This is a README" > readme.md
git add readme.md
git commit -m "在 master 添加 README"

现在两个分支各自往前走了。看一下图景:masterfeature/sign 在 “添加作者署名” 这个提交之后分叉了。查看图形化日志(也可以不加 --graph,但图形很直观):

git log --oneline --graph --all

你会看到类似:

*  (HEAD -> master) 在 master 添加 README
| * (feature/sign) 在 feature/sign 分支添加签名
|/
* 在 feature/author 分支添加作者署名
* 添加第三行:learning diff
...

合并分叉:产生合并提交

现在把 feature/sign 合并回 master
确保当前在 master

git merge feature/sign

这一次,因为两个分支各自有新提交,Git 无法直接快进。它会生成一个 合并提交(merge commit),会把两个分支的修改合并到一起。

你会进入一个提交信息编辑界面(通常是 nano 或 vim)。如果是 nano,直接 Ctrl+X 退出保存就行;如果是 vim,按 :wq 保存退出。Git 已经默认填好了 “Merge branch ‘feature/sign’”。

合并完成后,再看看日志图:

git log --oneline --graph --all

你会看到类似:

*    (HEAD -> master) Merge branch 'feature/sign'
|\
| * (feature/sign) 在 feature/sign 分支添加签名
* | 在 master 添加 README
|/
* 在 feature/author 分支添加作者署名
...

这就是典型的功能分支开发与合并。