目录
- 前言
- [一、.git 目录结构深度剖析](#一、.git 目录结构深度剖析)
-
- [1.1 .git 目录是什么](#1.1 .git 目录是什么)
- [1.2 objects 目录------Git 存储的核心](#1.2 objects 目录——Git 存储的核心)
-
- [1.2.1 提交前的 objects 目录](#1.2.1 提交前的 objects 目录)
- [1.2.2 第一次提交后的 objects 目录](#1.2.2 第一次提交后的 objects 目录)
- [1.2.3 再次修改提交后的 objects 目录](#1.2.3 再次修改提交后的 objects 目录)
- [1.3 Commit ID 的本质](#1.3 Commit ID 的本质)
- [二、查看提交历史:git log](#二、查看提交历史:git log)
-
- [2.1 基本用法](#2.1 基本用法)
- [2.2 日志中的关键字段解析](#2.2 日志中的关键字段解析)
- [2.3 日志信息的重要性](#2.3 日志信息的重要性)
- 三、版本恢复与底层原理
-
- [3.1 Git 只记录"修改动作"](#3.1 Git 只记录"修改动作")
- [四、Git 忽略文件机制与 .gitignore 详解](#四、Git 忽略文件机制与 .gitignore 详解)
-
- [4.1 .gitignore 的核心作用与配置逻辑](#4.1 .gitignore 的核心作用与配置逻辑)
- [4.2 规则生效验证与"已跟踪"陷阱](#4.2 规则生效验证与“已跟踪”陷阱)
- [五、Git Push 原理与远程仓库别名机制](#五、Git Push 原理与远程仓库别名机制)
-
- [5.1 git push 与 git push origin main 的区别](#5.1 git push 与 git push origin main 的区别)
- [六、Git 多平台协同开发与冲突解决实战](#六、Git 多平台协同开发与冲突解决实战)
-
- [6.1 双平台开发场景与核心痛点](#6.1 双平台开发场景与核心痛点)
- [6.2 Windows 环境下的 Git 操作基础](#6.2 Windows 环境下的 Git 操作基础)
-
- [6.2.1 Git Bash:类 Linux 的命令行体验](#6.2.1 Git Bash:类 Linux 的命令行体验)
- [6.2.2 TortoiseGit:可视化的版本控制](#6.2.2 TortoiseGit:可视化的版本控制)
- [6.3 协同开发的标准工作流](#6.3 协同开发的标准工作流)
-
- [6.3.1 正常提交流程(无冲突)](#6.3.1 正常提交流程(无冲突))
- [6.3.2 验证同步结果](#6.3.2 验证同步结果)
- [6.4 深入理解代码冲突(Conflict)](#6.4 深入理解代码冲突(Conflict))
-
- [6.4.1 冲突的产生](#6.4.1 冲突的产生)
- [6.4.2 解决冲突的步骤](#6.4.2 解决冲突的步骤)
- [6.5 常用命令速查与辅助工具](#6.5 常用命令速查与辅助工具)
-
- [6.5.1 核心命令清单](#6.5.1 核心命令清单)
- 结语


🎬 云泽Q :个人主页
🔥 专栏传送入口 : 《C语言》《数据结构》《C++》《Linux》《蓝桥杯系列》《笔试算法》《AI赋能》《STM32》《Python》
⛺️遇见安然遇见你,不负代码不负卿~
前言
大家好啊,我是云泽Q,欢迎阅读我的文章,一名热爱计算机技术的在校大学生,喜欢在课余时间做一些计算机技术的总结性文章,希望我的文章能为你解答困惑~
一、.git 目录结构深度剖析
本文衔接上文~
1.1 .git 目录是什么
每次 git clone 后,项目文件夹里都会隐藏着一个 .git 目录(用 ls -al 才能看到)。这个目录就是 Git 的灵魂,里面存储了所有的版本控制数据、配置信息、提交历史等。
通过 tree .git 可以查看其完整结构:
.git
├── branches/ # 分支相关(已废弃,基本不用)
├── config # 仓库的配置文件
├── description # 仓库描述信息
├── HEAD # 指向当前所在分支
├── hooks/ # Git 钩子脚本(各种事件的触发器)
│ ├── applypatch-msg.sample
│ ├── commit-msg.sample
│ ├── pre-commit.sample
│ ├── pre-push.sample
│ └── ...
├── index # 暂存区(执行 git add 后的数据存这里)
├── info/
│ └── exclude # 排除文件(类似 .gitignore)
├── logs/ # 日志目录
│ ├── HEAD
│ └── refs/
│ ├── heads/
│ │ └── master
│ └── remotes/
│ └── origin/
│ └── HEAD
├── objects/ # 对象数据库(核心!存储所有提交、文件、目录快照)
│ ├── 13/
│ ├── 25/
│ ├── 34/
│ ├── be/
│ ├── ec/
│ ├── info/
│ └── pack/
└── refs/ # 引用目录(分支和标签的指针)
├── heads/
│ └── master # master 分支指向的 commit id
├── remotes/
│ └── origin/
│ └── HEAD
└── tags/
1.2 objects 目录------Git 存储的核心
objects 目录是 Git 最核心的数据存储区。每执行一次 git commit,Git 就会在这个目录下生成新的对象文件。
1.2.1 提交前的 objects 目录
刚克隆下来、还没做任何修改时:
bash
tree .git/objects/
# 输出:
# .git/objects/
# ├── 13
# │ └── 9f53c10d9e66117708c69f539629205b9e3335
# ├── 25
# │ └── 9148fa18f9fb7ef58563f4ff15fc7b172339fb
# ├── 34
# │ └── 2354e19393ccfe2e55459fac052a97f2a8d77e
# ├── be
# │ └── 615096e37bae01c3e5397c3ead42d2e03151b3
# ├── ec
# │ └── 5af8158d994d41b190fdc250c874767f40b2f4
# ├── info/
# └── pack/
# 7 directories, 5 files
此时有 5 个对象文件,对应的是初始化仓库时创建的那 3 个文件(.gitignore、README.en.md、README.md)以及相关的目录树和 commit 对象。
1.2.2 第一次提交后的 objects 目录
执行 git add . 和 git commit -m "进度条代码" 后:
bash
tree .git/objects/
# 输出:
# .git/objects/
# ├── 13/9f53c10d9e66117708c69f539629205b9e3335
# ├── 17/6ab6ee35e1d27c2295446062de862656426447
# ├── 25/9148fa18f9fb7ef58563f4ff15fc7b172339fb
# ├── 27/ef2e426ab3bbf778c1df47b81290b33c4c157f ← 新增:commit 对象
# ├── 34/2354e19393ccfe2e55459fac052a97f2a8d77e
# ├── 48/c423dad011ccd16a051a587133f8bdee7c2077 ← 新增
# ├── 86/1b0919484f89a322b8319edeabfe2e309cbc1e ← 新增
# ├── 96/58abe1609acf32bf2ca7d368894ee830b2d2c8 ← 新增
# ├── 99/4008d3fd6e9baf56c8421b26fc3f45d3f8728b ← 新增
# ├── be/615096e37bae01c3e5397c3ead42d2e03151b3
# ├── c9/615096e37bae01c3e5397c3ead42d2e03151b3 ← 新增
# ├── ec/5af8158d994d41b190fdc250c874767f40b2f4
# ├── info/
# └── pack/
# 13 directories, 11 files
可以看到,提交后 objects 目录下新增了多个对象文件。这些新增的对象包括:
- blob 对象:存储每个被修改文件的具体内容。
- tree 对象:存储目录结构(哪些文件在哪些目录下)。
- commit 对象:存储提交信息(作者、时间、日志、父 commit 引用等)。
1.2.3 再次修改提交后的 objects 目录
再修改 main.c,再次 git add . 和 git commit -m "修改":
bash
tree .git/objects/
# 输出:
# .git/objects/
# ├── 13/9f53c...
# ├── 17/6ab6ee...
# ├── 25/9148fa...
# ├── 27/ef2e42...
# ├── 34/2354e1...
# ├── 48/c423da...
# ├── 86/1b0919...
# ├── 96/58abe1...
# ├── 99/4008d3...
# ├── be/615096...
# ├── c9/99c8aa18970d8989490d792f8aa705b5e0258d ← 新增:新的 commit 对象
# ├── db/849ff6e51595b0f3649a6016706a5f4bb81f34 ← 新增:新的 blob 对象(main.c 新内容)
# ├── ec/5af815...
# ├── info/
# └── pack/
# 16 directories, 14 files
每次提交都会生成新的对象文件,而旧的对象文件永远保留,这就是 Git 版本追溯的底层原理。
1.3 Commit ID 的本质
每次提交产生的 Commit ID (也叫修改 ID),比如 27ef2e4、db849ff,这些看起来像乱码的短字符串,本质上就是 objects 目录下对应文件夹的名字。
比如 commit ID 是 27ef2e426ab3bbf778c1df47b81290b33c4c157f,对应的文件路径就是:
.git/objects/27/ef2e426ab3bbf778c1df47b81290b33c4c157f
Git 把完整的 40 位 SHA-1 哈希值的前 2 位作为目录名,后 38 位作为文件名,这样做的目的是避免单个目录下文件过多导致性能下降。
二、查看提交历史:git log
2.1 基本用法
bash
git log
输出示例:
commit db849ff6e51595b0f3649a6016706a5f4bb81f34
Author: 云泽 <2392709001@qq.com>
Date: Mon Sep 7 14:36:08 2026 +0800
修改
commit 27ef2e426ab3bbf778c1df47b81290b33c4c157f
Author: 云泽 <2392709001@qq.com>
Date: Mon Sep 7 14:29:50 2026 +0800
进度条代码
commit 342354e19393ccfe2e55459fac052a97f2a8d77e
Author: 云泽 <2392709001@qq.com>
Date: Mon Sep 7 05:34:04 2026 +0000
Initial commit
2.2 日志中的关键字段解析
每条提交记录包含以下信息:
- commit:提交 ID(修改 ID),用于唯一标识这次提交。未来如果想恢复到某个历史版本,就是用这个 ID 来操作。
- Author :作者信息,显示的是
git config中配置的用户名和邮箱。 - Date:提交时间。
- 日志信息(最后一行) :就是
git commit -m后面写的内容。
2.3 日志信息的重要性
日志信息绝对不能胡写!
好的日志应该清晰描述这次提交做了什么。比如:
- ✅
"修复用户登录时密码校验的边界条件bug" - ✅
"新增进度条模块:process.c/process.h" - ❌
"修改" - ❌
"111" - ❌
"asdfgh"
混乱的日志会被同行鄙视。更严重的是,面试官可能会去查看你的 GitHub/Gitee 提交记录,如果满屏都是"修改""123""测试",印象分会大打折扣,且在实际业务工作中乱写日志,大概率是会被同事问候的。
三、版本恢复与底层原理
3.1 Git 只记录"修改动作"
Git 不会保存文件的完整快照,而是只记录每次修改了什么。比如:
第五行 删除 XXXX
第六行 修改 mian -> main
第七行 增加 printf("helloworld");
Git 记录的是"第5行删除了什么、第6行把mian改成了main、第7行增加了printf",而不是把整个文件重新存一份。这种方式极大地节省了存储空间。
四、Git 忽略文件机制与 .gitignore 详解
4.1 .gitignore 的核心作用与配置逻辑
在 Git 版本控制中,.gitignore 是一个至关重要的配置文件。它的核心功能非常明确:告诉 Git 哪些文件或目录不需要被跟踪,也不需要被提交到仓库中。在实际开发中,我们的项目目录下往往充斥着大量的编译产物、临时文件、日志文件或者特定操作系统的配置缓存。如果把这些文件都提交上去,就会让仓库体积迅速膨胀
.gitignore 文件的编写遵循通配符规则。例如,使用 *.o 可以忽略所有后缀为 .o 的目标文件,*.exe 可以忽略所有可执行文件,*.log 则能过滤掉日志文件。当我们在终端执行 git add . 命令时,Git 会自动读取当前目录下的 .gitignore 规则,凡是匹配到的文件,Git 都会视而不见,不会将它们加入暂存区。
结合在 Visual Studio 等 IDE 中开发 C++ 项目的实际场景,这一点尤为重要。

当我们创建一个名为 test_09_06_01 的项目时,IDE 会自动生成一系列辅助文件。除了核心的源代码 test.cpp 外,还会产生 test_09_06_01.vcxproj(项目工程文件)、test_09_06_01.vcxproj.filters(筛选器文件)以及 test_09_06_01.vcxproj.user(用户个人配置)。更关键的是,编译过程中会生成 x64 或 Debug/Release 这样的文件夹,里面存放着 .obj、.exe、.tlog 等中间产物。
对于团队协作而言,只有 test.cpp 这样的源代码是必须共享的。像 x64 文件夹里的内容,或者是 .vcxproj.user 这种包含个人路径配置的敏感文件,绝对不应该进入版本库。因此,我们需要在根目录下创建一个 .gitignore 文件,写入如下规则:
gitignore
# 忽略 Visual Studio 编译输出目录
x64/
Debug/
Release/
# 忽略编译产生的中间文件和可执行文件
*.obj
*.exe
*.tlog
*.log
# 忽略用户个人配置文件
*.vcxproj.user
通过这样的配置,无论你在 Windows 下如何编译,执行 git status 时,那些杂乱的编译文件都不会再出现,仓库始终保持干净整洁。
4.2 规则生效验证与"已跟踪"陷阱
在使用 .gitignore 时,还有一个极易踩坑的细节需要注意:.gitignore 只能忽略那些从未被 Git 跟踪过的文件。
如果一个文件已经被 git add 并 commit 到了仓库中,也就是它已经处于"已跟踪"状态,那么此时你再在 .gitignore 中添加针对该文件的忽略规则,是完全无效的。Git 会继续跟踪该文件的后续修改。
例如,假设你之前不小心把 test.txt 提交到了仓库,后来觉得这个文件不该被管理,于是你在 .gitignore 里加了 *.txt。你会发现,当你修改 test.txt 后,git status 依然会提示你有修改未提交。这是因为 Git 的索引中已经记录了该文件。
要解决这个问题,必须先将文件从 Git 的缓存区(Index)中移除,但保留本地文件。正确的操作步骤如下:
bash
# 从 Git 追踪列表中移除文件,但不删除本地物理文件
git rm --cached test.txt
# 或者批量移除所有已跟踪但现在需要忽略的文件
git rm -r --cached .
# 重新添加文件(此时 .gitignore 规则才会生效)
git add .
# 提交更改
git commit -m "Remove tracked files and apply gitignore rules"
只有执行了 git rm --cached 之后,.gitignore 中的规则才会真正对该文件生效。
五、Git Push 原理与远程仓库别名机制
5.1 git push 与 git push origin main 的区别
很多人在使用 Git 推送代码时会有疑问:为什么有时候直接敲 git push 就能成功,而有时候在别的教程里却写着 git push origin main?这两者到底有什么区别?
其实,git push 是 git push origin main 的一种简写形式,但这种简写是有前提条件的。
当你通过 git clone 命令从远端克隆一个仓库到本地时,Git 会在本地自动做两件事:
- 记录远端地址 :它会把你克隆来源的那个 URL 地址记录下来,并默认赋予一个别名叫
origin。 - 建立分支关联 :它会自动将本地的
main(或master)分支与远端的origin/main分支建立"上游"关联。
所以,在这个标准场景下,你输入 git push,Git 实际上是在帮你补全参数,它在心里想的是:"哦,你要推送到默认的 origin 远端,推送到当前分支对应的远程分支去。"
但是,如果你的本地仓库不是克隆来的,而是自己在本地通过 git init 初始化的,或者你需要将代码推送到多个不同的远端仓库(比如同时推送到 Gitee 和 GitHub),那么 git push 这种简写可能就会失效或产生歧义。这时,你就必须显式地指定远端别名和分支名称:
bash
# 显式指定推送到 origin 远端的 main 分支
git push origin main
这里的 origin 并不是什么关键字,它只是一个约定俗成的默认别名。你可以把它改成任何名字,比如 gitee、github 或者 my_server。
六、Git 多平台协同开发与冲突解决实战
6.1 双平台开发场景与核心痛点
在真实的软件开发过程中,我们很少只在一个操作系统下工作。比如你可能习惯在 Windows 下写代码;但到了服务器部署或者某些特定开发环节,又必须在 Linux 环境下操作。更常见的情况是,团队成员之间有人用 Windows,有人用 Linux,大家需要协同开发同一个项目。
这就引出了一个非常现实的问题:当 Windows 和 Linux 两个平台同时向同一个远程仓库(如 Gitee)提交代码时,如何保证代码的一致性?
我们假设一个场景:张三在 Linux 上写代码,李四在 Windows 上写代码,他们都要把代码提交到同一个 Git 仓库。这时候,如果处理不好,就会出现"两个账号"或者"两个本地版本"打架的情况。我们需要解决的核心问题就是:如何让两个不同平台的本地仓库,与远端仓库保持同步,并在发生修改冲突时正确合并。

6.2 Windows 环境下的 Git 操作基础
对于习惯图形化界面的同学来说,Windows 下的 Git 操作其实非常友好。当你安装好 Git 后,在任意文件夹(例如桌面上的 test_code 目录)单击鼠标右键,你会看到菜单中多了 Git GUI Here 和 Git Bash Here 等选项。

6.2.1 Git Bash:类 Linux 的命令行体验
点击 Git Bash Here,会弹出一个黑色的命令行窗口。这个工具非常强大,它模拟了 Linux 的终端环境。

- 目录定位 :它默认打开的目录就是你右键所在的目录。比如你在
D:\programming_file\code\test_code下右键打开,命令行提示符就会显示在这个路径下。 - 常用命令 :你可以像在 Linux 里一样使用
pwd查看当前路径,使用ls查看文件列表。 - 避坑指南 :在克隆仓库时,千万不要在一个已经包含
.git隐藏文件夹的目录下再次执行git clone。这会导致仓库嵌套,引发严重的干扰和错误。建议新建一个干净的文件夹(如桌面的新目录)来进行克隆操作。
6.2.2 TortoiseGit:可视化的版本控制
除了命令行,Windows 下还有一款神器叫 TortoiseGit (小乌龟)。这在我的另一篇Git文章中有详细的安装教程。安装后,右键菜单会出现更丰富的选项,如 Pull...(拉取)、Push...(推送)、Commit -> "master"...(提交)等。
- Push 操作细节 :当你选择
Push...时,会弹出一个配置窗口。- Local(本地分支) :通常选择
master。 - Remote(远程分支) :也对应选择
master。 - Destination(目标) :选择
origin,即你克隆时绑定的远程仓库地址。 - 点击
OK后,如果一切顺利,进度条走完显示Success,就说明推送成功了。
- Local(本地分支) :通常选择
- Pull 结果查看 :拉取成功后,TortoiseGit 会弹出一个 "Git Command Progress" 窗口,底部有一个下拉框可以选择
Pulled Diff。点击它可以直观地看到这次从远端拉取下来哪些文件发生了变化,比如新增了code.c文件,插入了 7 行代码。
6.3 协同开发的标准工作流
为了保证团队协作不乱套,我们必须遵守一个铁律:远端仓库永远是最新的权威版本。
6.3.1 正常提交流程(无冲突)
假设张三(Linux)先提交了一个 code.c 到 Gitee。此时李四(Windows)想提交自己的 wcode.c。
- 李四的操作 :李四在本地写好
wcode.c,执行git add .和git commit -m "Windows提交代码"。 - 尝试推送 :李四执行
git push。 - 关键判断 :
- 如果李四在写代码期间,没有人更新过远端仓库,那么
push会直接成功。 - 但是 ,如果张三刚刚提交了新代码,李四的本地仓库就"落后"了。此时直接
push会失败,报错提示rejected(被拒绝),因为远端有李四本地没有的新内容。
- 如果李四在写代码期间,没有人更新过远端仓库,那么
- 正确做法 :李四必须先执行
git pull。这步操作会把张三提交的code.c拉取到李四的电脑上,并自动进行合并(Merge)。合并成功后,李四的本地仓库就包含了张三的代码和自己的代码,此时再执行git push,就能成功上传了。
6.3.2 验证同步结果
当我们完成上述操作后,可以登录 Gitee 网页端查看。你会发现仓库里既有了张三提交的 code.c(提交信息显示 "code fix"),也有了李四提交的 wcode.c(提交信息显示 "Windows提交代码")。这就实现了跨平台的完美协同。
6.4 深入理解代码冲突(Conflict)
刚才说的是大家修改不同文件的情况,那如果张三和李四同时修改了同一个文件 (比如都改了 Linuxcode.c),会发生什么?这就是最头疼的代码冲突。
6.4.1 冲突的产生
- 场景重现 :
- 张三在 Linux 上修改了
Linuxcode.c,添加了一行打印 "hello Linux",并提交推送到远端。 - 李四在 Windows 上也修改了
Linuxcode.c,添加了 "我是windows程序员",并尝试推送。
- 张三在 Linux 上修改了
- 报错分析 :
- 李四推送时,终端会显示
! [rejected] master -> master (fetch first)。 - 错误信息明确指出:
Updates were rejected because the remote contains work that you do not have locally.(更新被拒绝,因为远端包含你本地没有的工作)。 - 系统提示你:
You may want to first merge the remote changes (e.g., 'git pull') before pushing again.(你可能需要先合并远端更改)。
- 李四推送时,终端会显示
6.4.2 解决冲突的步骤
当李四执行 git pull 试图同步时,Git 会发现两个人都改了同一个文件的同一部分,它不知道该听谁的,于是报错:
CONFLICT (content): Merge conflict in Linuxcode.c
Automatic merge failed; fix conflicts and then commit the result.
这时候,你需要手动介入:
-
打开冲突文件 :用编辑器(如 Vim 或 VS Code)打开
Linuxcode.c。你会看到 Git 留下的特殊标记:c<<<<<<< HEAD 这是Linux程序员修改的代码 ======= 我是windows程序员,下面是我写的代码 >>>>>>> a4ef795a02c4736ee2f9de62140b99289d0fee2c<<<<<<< HEAD到=======之间:是你当前本地分支(李四)的代码。=======到>>>>>>>之间:是远端拉取下来(张三)的代码。
-
手动决策 :你需要决定保留哪部分代码,或者把两部分都保留。编辑文件,删除那些
<<<<、====、>>>>标记符号,只保留合法的 C 语言代码。 -
重新提交 :
- 保存文件。
- 执行
git add .(告诉 Git 冲突已解决)。 - 执行
git commit -m "merge"(生成一个合并提交记录)。 - 最后执行
git push,将解决冲突后的最终版本推送到远端。
6.5 常用命令速查与辅助工具
为了能让大家更流畅地使用 Git,这里整理了其余几个高频命令和技巧。当然,在Linux部分的 Git 内容中只是一些基础的使用方法,只是在实际公司业务中够用的程度,Git 是一个非常强大的工具,如果想要更深入的了解 Git ,请关注博主后续更新的 Git 专栏内容~
6.5.1 核心命令清单
除了基础的 add, commit, push, pull, clone,以下命令也非常重要:
git log:查看提交历史记录,了解谁在什么时候改了什么。git status:查看当前工作区状态,有哪些文件被修改了但还没 add。git diff:查看具体修改了什么内容。git --help <command>:如果你忘了某个命令怎么用,比如git --help add,它会直接列出该命令的详细帮助文档。
结语
