平时习惯了使用 Git,但是目前工作场景中 Git 使用频率较低以至于逐渐忘记各种细节了,于是系统地梳理了 Git 的一些核心逻辑、高频操作等便于随时回顾,也为同样需要使用 Git 的开发者提供参考。
想快速使用 Git 的直接划到 第二章
什么是 Git?
Git 是一个开源的分布式版本控制系统,由 Linus Torvalds 于 2005 年开发。它的核心功能是跟踪文件的变化,帮助多人协作开发项目,支持代码版本的回退、分支管理、合并等操作,适用于任何类型的文件(尤其是文本文件)。
它与 SVN 的区别?
1. 架构模式
-
Git 是分布式的:每个开发者本地都有一个完整的仓库副本(包含完整的历史记录和元数据),不依赖中央服务器即可提交、查看历史、创建分支等。适合离线工作,即使没有网络也能提交代码,仅在同步远程仓库(push/pull)时需要网络
-
SVN 是集中式的:所有代码和历史记录存储在中央服务器,开发者通过客户端与服务器交互(提交、更新等)。必须联网才能执行大多数操作,本地仅保留当前版本的文件
2. 数据存储方式
-
Git 存储完整的文件快照(差异化的二进制对象),而非仅记录文件差异。每次提交生成一个唯一的哈希值标识版本。历史记录完整性高,难以篡改
-
SVN 存储基于文件的差异(增量式),历史记录依赖中央服务器存储的版本号
3. 分支与合并
-
Git 分支非常轻量级,创建和切换分支速度快(仅修改指针),鼓励频繁使用分支(如 Git Flow 工作流)
-
SVN 分支是目录的副本,创建分支较慢且占用空间
4. 冲突解决
-
Git 冲突通常在本地合并时解决,开发者可以灵活调整代码后再提交
-
SVN 冲突需在提交前通过服务器最新版本解决,流程相对繁琐
5. 历史记录修改
-
Git 允许修改提交历史(如 rebase、amend),适合清理本地提交
-
SVN 历史记录一旦提交不可修改,只能通过新提交修复错误
6. 权限管理
-
Git 权限控制较弱,通常依赖第三方工具(如 GitLab、GitHub 的权限系统)
-
SVN 支持目录级精细权限控制,适合企业级严格管理需求
工作流程对比:
-
集中式:每个开发者从同一个中央服务器更新最新版本(包括其他开发者所做的修改),在本地工作区(包含所有项目文件的工作目录)修改后,定期将修改提交给该服务器,这台服务器上存储着这些文件(即版本库)的当前版本和历史版本。所有版本、分支被集中管理
-
分布式:每个开发者同时拥有用于文件操作的工作区和一个用于存储该项目所有版本、分支以及标签的本地版本库(一份克隆),每个开发者将一次次的修改版本先提交(commit)到本地版本库,通过推送(push)和拉回(pull)可以将这些修改从一个版本库传送到另一个版本库。不过实际开发中并不是简单的两个开发者之间互推版本,通常需要一个远程共享版本库(如 GitHub、GitLab)作为中介,大家把版本推送到这里用来同步修改以及最终发布上线
一、基础概念
先来讲讲上面提到的几个关键词的概念:
1. Repository(版本库)
当你在项目目录下运行 git init
或 git clone
时会生成一个隐藏的 .git
目录,这就是本地版本库的实体。
其中关键的文件夹/文件:
-
objects/
:存储所有 Git 对象(Blob、Tree、Commit),每个对象通过 SHA-1 哈希值 唯一标识-
Blob 对象:存储文件内容的二进制快照(不保存文件名)
-
Tree 对象:存储目录结构,记录目录下的文件名、权限及对应的 Blob 或子 Tree
-
Commit 对象:存储提交信息,包含作者、提交时间、提交说明、父提交的哈希值,以及根目录的 Tree 对象哈希值
-
-
refs/
:存储分支和标签的指针,指向具体提交 -
HEAD
:指向当前所在的分支或提交 -
config
:当前仓库的配置信息 -
index
:暂存区的二进制文件,记录已git add
但未提交的内容(后面会讲暂存区的概念)
为了节省内存空间,Git 对于相同数据将只储存一次,比如相同内容的两个文件将返回相同的哈希值。许多 Git 操作之所以快就是因为它们的算法只比较相关的散列值,而不需要查看其实际数据。
2. Commit(提交)
用于记录项目在某一时刻完整状态的操作,相当于给代码拍了一张“快照”。每次提交都会生成一个唯一的提交记录(用 Commit 对象的哈希值来标识),利用该唯一哈希值可以帮助开发者随时回退到任意提交的状态、明确谁在何时修改了哪些内容、快速定位引入 Bug 的代码等。
3. Branch(分支)
实际开发中,项目会有一个主分支(master)用来打包发布,需要开发新功能或修 Bug 时,如果每个人都直接在主分支上做改动会变得非常混乱,且主分支不可以轻易直接改动(像我们之前是不允许直接 push master 的)。通常需要基于主分支创建多个功能分支(feature-a、feature-b...)来并行开发,测试没问题后推送到远端再申请合并到主分支上。
-
分支本质是一个指向某个提交(commit)的轻量级指针
-
分支随着新提交自动向前移动,形成链式结构
注:由于一些历史原因,Git 的默认主分支名称在 2020 年 10 月之后从 master 更改为 main,我习惯了用 master 所以自行修改的。
二、使用 Git
1. 安装与配置
1.1 安装 Git
-
Windows:下载安装包 git-scm.com 没梯子的👉阿里镜像:https://registry.npmmirror.com/binary.html?path=git-for-windows/ 全文搜索最新版本,例:
2.49.0
-
macOS:使用Homebrew:
brew install git
-
Linux:
sudo apt-get install git
(Debian/Ubuntu)
1.2 配置用户信息
前面提到过提交时会带上作者信息。
git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"
2. 创建版本库
在项目目录下使用 init
指令:
如果本地已有项目,远端没有,需要将本地项目关联到远端:
2.1 创建远程仓库
-
登录 GitHub → 点击右上角
+
→New repository
-
输入仓库名称(如 my-project),选择公开(Public)或私有(Private),不要勾选 "Initialize this repository with a README"(否则需先拉取远程仓库)
-
复制 GitHub 仓库的 SSH
# (SSH)
git remote add origin git@github.com:user/repo.git
如果本地没有项目,需要去 GitHub 上拉项目:
2.2 克隆远程仓库
注意:如果只需一次性拉取代码,不需要频繁推送代码/公司网络受限就选 HTTPS,如果是参与开发、需要频繁推送的项目就选 SSH。
# (HTTPS)后续推送代码时需要输入用户名和 GitHub 的个人访问令牌 PAT (代替密码)
git clone https://github.com/user/repo.git
# (SSH)配置 SSH 密钥后,无需每次输入用户名/密码
git clone git@github.com:user/repo.git
2.3 SSH 免密登录配置(用 HTTPS 的不用看)
👉 SSH 免密登录
3. 首次提交代码
我想把新建的 test.txt 传到远端。
3.1 status 命令可以显示该项目自上次提交以来所发生的所有修改
git status
git status --short # 精简输出
3.2 add 命令确定提交哪些文件,并发到暂存区
git add test.txt # 添加单个文件
git add . # 添加所有修改
3.3 commit 命令将修改传到本地版本库
git commit -m "描述你的提交"
3.4 push 命令将提交推送到远程仓库
-
语法:
git push <远程仓库> <本地分支名>:<远程分支名>
如果就想推送当前分支,则只写 <远程分支名>
就行,加 <本地分支名>
的作用是即使当前不在该分支,也能推送该分支。
# 首次推送需指定远程(origin)分支(main),并设置上游(-u)
git push -u origin main
# 后续推送可直接用 git push(因为上游已关联)
git push
注意:我将远端的 main 改成 master 了,所以我用的 git push -u origin master
指令。
可以在远程仓库看到 test.txt 已提交。
4. 其他常见命令(在这里讲一下暂存区的概念)
4.1 log 命令显示提交历史
git log
git log -n 3 # 仅查看最后 3 次提交
git log --oneline # 每个提交只展示一行
git log --shortstat # 显示有多少文件被修改,以及新增或删除了多少文件
git log --graph # 显示各提交之间的关系
4.2 reset 命令撤销操作
前面提到过 add 命令将想要提交的文件先放到暂存区(.git/index
二进制文件)。
讲讲暂存区的概念:
暂存区(也叫索引)是 Git 实现“选择性提交”的核心机制,其二进制文件内部包含了被跟踪且已暂存的文件列表(通过特定规则排列的条目列表),每个条目对应一个文件,包含:
-
文件路径(例如 src/main.c)
-
文件模式(如 100644 普通文件,100755 可执行文件)
-
Blob 的 SHA-1 哈希(指向 .git/objects 中的文件内容)
-
stat 信息(文件大小、最后修改时间等)
运行 git add
时,索引通过 stat 信息快速判断文件是否可能被修改,对于可能修改的文件重新计算 SHA-1 哈希值,且:
-
如果是新增文件:索引中会新增一个条目,记录路径、SHA-1 哈希等信息
-
如果是修改文件:则覆盖原有条目,更新 SHA-1 哈希和 stat 信息
-
如果是删除文件:索引中对应的条目会被移除
-
如果是文件重命名:移除旧路径(old.txt)的条目、新增新路径(new.txt)的条目,但 Blob 的 SHA-1 哈希保持不变(若内容未修改)
运行 git commit
时会将索引中所有条目打包成一个树对象(Tree Object),形成新的提交。
如果我想撤销某次注册(add 行为)或者某次提交呢?如果改了半天代码发现改错了但又不想一点点回退呢?
-
想修改提交信息或合并多个提交:仅移动 HEAD 指针,保留暂存区和工作区的所有内容
git reset --soft <commit>
# 例如:
# 1. 回退 HEAD(保留改动在暂存区)
git reset --soft HEAD~1
# 2. 修改文件后重新提交
git commit -m "新的提交信息"
-
想撤销
git add
操作或调整暂存文件:移动 HEAD 指针,且暂存区回退到旧 HEAD 状态,但工作区保留所有内容
# (默认模式)之前的提交内容会变为“未暂存状态”,需重新 git add
git reset --mixed <commit>
# 例如:
# 误将文件添加到暂存区,撤销到工作区
git reset HEAD file.txt # --mixed 模式
-
想彻底放弃某次提交后的所有改动(确保已备份需要保留的内容):移动 HEAD 指针,暂存区和工作区完全回退到旧提交状态,丢弃所有未提交的修改
# 谨慎操作,未提交的改动(包括暂存区)会被永久删除
git reset --hard <commit>
4.3 diff 命令比较文件差异
-
工作区 vs 暂存区:显示工作目录中已修改但未暂存(未
git add
)的变更
git diff
-
暂存区 vs 最新提交:显示已暂存但未提交的变更
git diff --staged # 或 --cached
-
工作区 vs 最新提交:显示所有未提交的变更,包括已暂存和未暂存的变更
git diff HEAD
-
比较两个提交之间的差异:分析历史版本差异
git diff <commit1> <commit2> # 例如 git diff HEAD~2 HEAD
4.4 用 .gitignore
指定需要忽略的文件或目录
通过它,可以避免将临时文件、编译产物、敏感信息等提交到仓库。在项目根目录下创建 .gitignore
文件,写法如下:
secret.key # 忽略所有名为 secret.key 的文件
logs/ # 忽略所有 logs 目录及其内容
/config.env # 仅忽略根目录下的 config.env(不忽略子目录中的同名文件)
*.log # 忽略所有 .log 文件
src/*.tmp # 忽略 src 目录下所有 .tmp 文件
temp-* # 忽略所有以 temp- 开头的文件
!important.log # 即使父级规则忽略了 *.log,仍跟踪 important.log
.gitignore
不会影响已经被 Git 跟踪的文件。若需忽略已跟踪文件,需先删除缓存:
git rm --cached file.txt # 停止跟踪 file.txt,但保留本地文件
git commit -m "Stop tracking file.txt"
也可以在项目子目录中创建 .gitignore
文件,只影响该目录下的文件和路径,但通常建议在根目录统一管理。
4.5 stash 命令临时保存工作区和暂存区未提交的改动
当我们手头的工作未完成,但需要切换到其他分支解决突发问题(如修复紧急 Bug)时使用。
# 保存工作区和暂存区的改动,添加描述性消息(推荐)
git stash push -m "正在开发的xx功能"
# 等价简写(默认保存未跟踪文件外的改动)
git stash
# 包含未跟踪文件(untracked files)
git stash -u # 或 --include-untracked
# 包含未跟踪文件和忽略的文件(谨慎使用)
git stash -a # 或 --all
git stash list # 查看储藏列表
# 恢复最新的改动并删除此记录(类似于弹出栈顶元素)
git stash pop
# 恢复某个特定 stash(如 stash@{1})
git stash pop stash@{1}
# 恢复改动但不删除记录(适用于重复应用)
git stash apply stash@{1}
# 恢复时保留暂存区状态(原暂存的改动仍处于已暂存状态)
git stash apply --index
三、Git 协作开发
1. 拉取他人的修改并解决冲突
如果是多人同时开发,其他人可能也会有新的提交,这时不能直接 push。
1.1 pull 命令从远程仓库获取更新并合并到当前分支
git pull
= git fetch
(获取远程更新) + git merge
(合并到当前分支)
-
若当前分支已关联上游分支(如 main → origin/main)
git pull
# 等效于:
git fetch origin # 拉取远程所有分支的更新
git merge origin/main # 合并到当前分支
-
若当前分支未关联上游,或想拉取其他分支
git pull origin dev # 拉取远程 origin 的 dev 分支并合并
-
git fetch
:可以查看远程改动后再决定是否合并 -
git pull
:若远程和本地修改冲突,会直接触发合并冲突,需当场解决
1.2 手动解决冲突
如果有人修改了文件 a ,同时我也修改了文件 a 的同行代码,拉取代码时会提示出现冲突。
-
命令行提示
CONFLICT (content): Merge conflict in <文件名>
Automatic merge failed; fix conflicts and then commit the result.
此时需要手动解决冲突,以 VSCode 为例:
-
打开左侧源代码管理视图,有黄色警告图标标记冲突文件
-
打开冲突文件会看到典型冲突标记
<<<<<<< HEAD
本地修改的代码
=======
远程/其他分支的代码
>>>>>>> commit-id
-
点击冲突文件中的蓝色提示条,选择处理方式:
-
Accept Current Change
:保留本地修改
-
Accept Incoming Change
:使用远程版本
-
Accept Both Changes
:合并两者
-
Compare Changes
:对比差异
-
-
保留需要的代码段后回到源代码管理视图中,将冲突文件标记为已解决
-
再执行 add、commit、push 操作
2. 创建新分支/合并分支
2.1 查看当前活跃分支
在一个版本库里总是唯一存在着一个活动分支,用 branch 命令列出所有分支,带星号( * )的就是当前活跃分支。
git branch # 查看本地分支
git branch -a # 查看所有分支(本地 + 远程)
git branch -v # 查看分支及其最新提交信息
2.2 创建新的分支
git branch feature-1 # 为当前提交创建名为 feature-1 的分支(不切换)
git checkout -b feature-1 # 创建并切换到 feature-1 分支
2.3 切换当前活跃分支
git checkout dev # 切换到 dev 分支
git switch dev # Git 2.23+ 推荐的新命令(更语义化)
如果工作区或暂存区存在一些未提交的修改,则不能直接切换,可以考虑以下办法:
-
提交修改再切换
-
放弃这部分修改并进行切换
git checkout --force dev # 会被覆盖,谨慎操作
-
储存修改再切换
git stash
git checkout dev
2.4 删除分支
git branch -d dev # 删除已被合并到其他分支的 dev 分支
git branch -D dev # 强制删除未被合并的 dev 分支
2.5 恢复已删除分支
恢复已删除分支的前提是该分支的提交尚未被 Git 的垃圾回收(GC)机制清理(默认保留约 30 天)。
-
通过
git reflog
找到提交哈希
-
找到被删除分支的最后一次提交哈希
3d7f307
,重新创建分支
git branch <分支名> <提交哈希> # 例如 git branch feature-1 3d7f307
git checkout feature-1 # 切换到恢复的分支
2.6 合并分支
# 将 dev 分支合并到当前分支(如 master)
git checkout master
git merge dev # 默认使用 Fast-Forward 或 Recursive 策略
分支合并策略:
-
Fast-Forward(快进合并):当目标分支(如 master)的最新提交是待合并分支(如 dev)的直接祖先时,只需将目标分支的指针向前移动到待合并分支的最新提交,无需创建新的合并提交
git merge --ff-only dev # 可以仅允许快进合并
-
No-Fast-Forward(非快进合并):当目标分支的最新提交不是待合并分支的直接祖先时,生成一个新的合并提交
git merge --no-ff dev # 可以强制生成合并提交(保留分支历史)
若合并时出现冲突,则参考前文,进到冲突文件里手动解决冲突后,再进行 add 和 commit 操作生成合并提交。
以上基本已经满足日常开发需求了,还有像打tag、变基等操作,下次有时间再整理吧。