Git 是分布式版本控制系统。Git 记录一组文件在特定时刻的完整状态、提交之间的关系,以及每次修改的作者与说明。每位开发者的本机仓库都可以保存完整历史,因此即使暂时无法连接服务器,仍能查看差异、创建提交和切换分支。
初学 Git 最容易混淆的不是命令,而是修改目前位于工作区、暂存区、本机历史还是远程仓库。先建立数据流模型,再学习命令,便能根据操作目的选择工具。
工作区 ──git add──> 暂存区 ──git commit──> 本机提交历史 ──git push──> 远程仓库
1. 安装与初始配置
1.1 安装 Git
Ubuntu 或 Debian 可以运行 sudo apt update && sudo apt install git,Fedora、Rocky Linux 等系统可以运行 sudo dnf install git,macOS 搭配 Homebrew 时可以运行 brew install git。安装完成后,用 git --version 检查可执行文件与版本。
每个提交都会记录作者姓名与电子邮件。下列全局设置应用到当前用户的所有仓库:
git config --global user.name "Your Name"
git config --global user.email "[email protected]"
使用 git config --global user.name 和 git config --global user.email 可以读取配置。特定仓库需要另一组身份时,在仓库内省略 --global;仓库配置会覆盖同名的全局配置。
1.2 理解配置层级
Git 配置有三个常见层级:
| 层级 | 常见位置 | 配置选项 | 作用范围 |
|---|---|---|---|
| 系统 | /etc/gitconfig | --system | 整台电脑 |
| 用户 | ~/.gitconfig | --global | 当前用户 |
| 仓库 | .git/config | 不加层级选项 | 当前仓库 |
越接近仓库的配置优先级越高。git config --list --show-origin 会同时显示配置值与来源文件。常用初始配置如下:
git config --global init.defaultBranch main
git config --global core.editor "vim"
git config --global core.autocrlf input
Git 会在撰写合并信息、交互式 rebase 指令或标签说明时打开指定编辑器。需要熟悉编辑器基本操作时,可以参考站内的 Vim 使用指南;偏好图形界面的读者可以参考 Visual Studio Code 安装与扩展。
2. 创建仓库并完成第一次提交
2.1 初始化或克隆仓库
从本机创建项目时,可以运行以下命令:
mkdir git-practice
cd git-practice
git init
git init 会创建 .git 目录,用来保存对象数据库、分支引用与仓库配置。现有项目也能运行 git init,Git 不会删除原有文件。创建第一个文件后运行 git status,Git 会把尚未纳入跟踪的文件列为 untracked。
远程已经存在项目时,通常使用 git clone [email protected]:team/project.git。Clone 会下载提交历史、创建工作区、加入名为 origin 的远程,并检出默认分支。git init 适合从本机开始新项目,git clone 适合加入现有项目。
2.2 分清四个数据区域
Git 日常操作涉及四个区域:
- 工作区(working tree):编辑器与文件系统目前显示的内容;
- 暂存区(index 或 staging area):下一次提交准备收录的内容;
- 本机仓库(local repository):已创建的提交、分支与标签;
- 远程仓库(remote repository):团队共享的提交与分支。
git add 的主要作用是把文件目前的内容加入下一次提交。已跟踪文件每次修改后也需要重新 add,才能更新暂存区中的版本。
2.3 检查、暂存与提交
git status --short 适合快速查看文件状态。git diff 显示工作区相对暂存区的差异,git diff --staged 显示暂存区相对上一个提交的差异。提交前依次查看两种 diff,可以确认哪些修改仍未暂存,以及下一次提交实际包含哪些内容。
创建文件、选择修改并提交的完整流程如下:
printf '# Git practice\n' > README.md
git status --short
git diff
git add README.md
git diff --staged
git commit -m "Add project README"
git add src/ 暂存指定目录,git add . 暂存目前路径内的新增、修改与删除,git add --patch 则能逐段选择修改。逐段暂存适合拆分大型修改,让每个提交只表达一个连贯变更。提交信息应说明修改带来的结果,例如 Fix empty search results on mobile 比 update files 更容易审查。
.gitignore 用来排除构建产物、临时文件与本机配置:
node_modules/
dist/
.env
*.log
.gitignore 不会停止跟踪已经提交的文件。若 .env 已被跟踪,使用 git rm --cached .env 从暂存区移除,再提交变更。密码、API token 与私钥不应进入提交历史;稍后删除文件不会清除旧提交中的秘密。
3. 理解提交历史与分支
3.1 Commit、分支与 HEAD
每个 commit 记录项目快照、作者、时间、提交信息,以及父提交。多个提交通过父子关系形成有向图。HEAD 通常指向当前分支,分支名称则指向该分支的最新提交。创建提交 D 后,main 与 HEAD 都会向前移动:
A──B──C──D main, HEAD
分支不是项目的完整副本,而是一个可移动的提交引用。创建分支的成本很低,因此短期功能分支是 Git 协作的常见单位。
Commit ID 是 Git 根据对象内容计算出的哈希值。修改提交内容、父提交或提交信息会产生新的 ID,因此 rebase 和 amend 会重写提交。命令通常只需使用不会混淆的 ID 前缀。HEAD~1 表示第一父链的上一个提交,HEAD~3 表示沿第一父链向前三个提交。
3.2 创建、切换与合并分支
新功能应在独立分支开发。git switch -c feature/search 会创建并切换分支,git switch main 会回到主分支,git branch -vv 可以查看本机分支及其上游状态。旧版 Git 常使用 git checkout 同时切换分支与还原文件;现代 Git 提供 git switch 与 git restore,让两类操作更容易区分。
功能完成后,先切换到接收变更的分支,再合并功能分支:
git switch main
git merge feature/search
如果 main 没有新提交,Git 只需向前移动分支引用,称为 fast-forward merge。如果两个分支都有新提交,Git 通常会创建具有两个父提交的 merge commit,保留两条开发线汇合的关系。
3.3 使用 Rebase 整理个人分支
Rebase 会把一组提交重新套用到新的基底。运行 git switch feature/search 后再运行 git rebase main,原本建立在旧基底上的功能提交会重建到最新 main 之后。
Rebase 可以产生线性历史,但会改变 commit ID。尚未共享的个人分支适合整理;多人共同使用的分支若被任意重写,其他开发者的历史会产生分歧。功能合并后,git branch -d feature/search 会安全删除已合并的本机分支;大写 -D 会跳过合并检查,使用前应确认提交仍可从其他引用找到。
4. 远程协作与冲突处理
4.1 Fetch、Pull 与 Push
git remote -v 显示远程名称与 URL。origin 是 clone 时创建的惯用名称,不是 Git 保留字。origin/main 是远程跟踪分支,代表最近一次 fetch 后,本机所知道的远程 main 状态。
git fetch origin 下载远程的新提交与分支引用,但不修改工作区或本机分支。Fetch 后可以用 git log --oneline main..origin/main 查看远程新增提交,并用 git diff main...origin/main 比较两条开发线。
git pull 通常等于 fetch 后再 merge。git pull --ff-only 只允许快进,遇到分歧时停止,让开发者明确选择 merge 或 rebase。偏好 rebase 的团队可以使用 git pull --rebase。团队应统一 pull 策略,避免每位成员产生不同的历史形状。
第一次发布功能分支时运行 git push --set-upstream origin feature/search,之后可以直接运行 git push。如果远程分支包含本机没有的提交,普通 push 会被拒绝;应先 fetch 并整合远程提交。重写个人远程分支后若确实需要强制更新,git push --force-with-lease 会检查远程是否仍处于预期状态,比不检查状态的 --force 更能避免覆盖他人的新提交。
4.2 解决 Merge 与 Rebase 冲突
两个分支修改同一段文字且结果不同时,Git 无法判断需要保留哪一版,便会加入冲突标记:
标记 <<<<<<< HEAD 表示当前分支内容的起点,======= 分隔两个版本,>>>>>>> feature/search 表示传入分支内容的终点。
Merge 发生冲突后,先用 git status 查看冲突清单。编辑每个冲突文件,产生符合两边意图的最终内容,删除冲突标记并完成测试,再运行 git add path/to/resolved-file 和 git commit。git merge --abort 可以取消整次合并。
Rebase 发生冲突时,修正并暂存文件后运行 git rebase --continue;git rebase --abort 可以取消整次 rebase。冲突解决的完成条件是程序符合预期并通过测试,不只是冲突标记已经消失。
4.3 从功能分支发起审查
一套常见的协作流程如下:
git switch main
git pull --ff-only
git switch -c feature/search-filter
# 编辑并测试
git add --patch
git diff --staged
git commit -m "Add topic filter to article search"
git push --set-upstream origin feature/search-filter
推送分支后,在 GitLab 创建 merge request。审查产生的新修改可以继续提交到同一分支,merge request 会自动更新。需要把测试接到提交与 merge request 时,可以继续阅读 GitLab CI/CD 指南;希望自行管理代码平台时,可以参考 使用 Docker 部署 GitLab。
5. 安全恢复与暂存工作
5.1 根据修改所在区域选择工具
不同恢复命令处理不同区域:
| 目的 | 命令 | 历史是否改写 |
|---|---|---|
| 丢弃工作区修改 | git restore path/to/file | 否 |
| 取消暂存并保留工作区修改 | git restore --staged path/to/file | 否 |
| 修正最新的本机提交 | git commit --amend | 是 |
| 撤销已共享提交 | git revert <commit-id> | 否,创建新提交 |
| 移动当前分支 | git reset | 是 |
Restore 可能丢弃尚未保存的工作区内容,运行前应先查看 git diff。Amend 会创建新的 commit ID,适合修正尚未共享的最新提交。Revert 创建反向提交并保留原提交,适合公开分支与多人协作。
Reset 会移动当前分支,并依模式决定是否改动暂存区与工作区:
| 命令 | 暂存区 | 工作区 |
|---|---|---|
git reset --soft HEAD~1 | 保留 | 保留 |
git reset --mixed HEAD~1 | 重置 | 保留 |
git reset --hard HEAD~1 | 重置 | 重置 |
--hard 会丢弃工作区与暂存区的内容。Reset 适合整理尚未共享的本机历史,撤销公开分支上的提交通常应使用 revert。
5.2 使用 Reflog 找回提交
git reflog 记录本机 HEAD 和分支引用的移动。Reset 或 rebase 后若需要找回旧提交,可以从 reflog 找到 commit ID,再运行 git branch recovery <commit-id> 创建分支保留。Reflog 是具有保留期限的本机记录,不会同步到远程,因此不能取代远程备份。
5.3 暂存尚未完成的修改
需要临时切换分支,但修改尚未适合提交时,可以运行以下流程:
git stash push -m "WIP search form"
git switch main
# 处理临时工作
git switch feature/search
git stash pop
git stash apply 会恢复修改并保留 stash 记录,git stash pop 会在成功恢复后删除记录。默认 stash 不包含 untracked 文件;需要一并收起时使用 git stash push --include-untracked -m "WIP full tree"。Stash 适合短期切换,需要长期保存或分享的工作应创建清楚命名的分支与提交。
6. 标签、密钥与版本发布
标签通常用来标记正式版本。Annotated tag 会保存创建者、日期与说明:
git tag -a v1.0.0 -m "Release 1.0.0"
git show v1.0.0
git push origin v1.0.0
分支会随新提交移动,版本标签通常固定指向特定提交。正式版本若需要修正,创建新版本标签比移动既有标签更清楚。发布前应确认目标 commit 已通过测试,版本号与变更记录一致,构建产物可由相同源代码重建。
使用 SSH 连接远程仓库时,可以运行 ssh-keygen -t ed25519 -C "[email protected]" 创建密钥。把 .pub 公钥内容加入 Git 托管平台,私钥则留在本机并设置适当权限。私钥不能贴入网站、聊天消息或 Git 仓库。密钥管理与服务器端 SSH 加固可以参考 Linux 云服务器初始化与安全加固。
7. 诊断顺序与后续练习
遇到 Git 问题时,先收集状态,再运行会修改历史或工作区的命令:
git status
git branch -vv
git remote -v
git log --oneline --decorate --graph --all -20
git diff
git diff --staged
状态输出可以回答目前所在分支、分支跟踪的远程、尚未提交的修改,以及提交图如何分叉。判断修改位于工作区、暂存区、本机历史或远程后,再选择 add、commit、fetch、merge、rebase、push、restore、revert 或 reset,可以避免大部分误操作。
初学者可以在练习仓库依次完成三个任务:先创建两个小提交,再从第一个提交创建功能分支并制造一次可控冲突,最后分别使用 revert 与 reset 观察提交图。每一步都搭配 git status、git diff 和图形化 git log,Git 的区域模型与分支模型会比单纯背诵命令更容易掌握。