Chapter 02

Git 基础操作

从创建仓库到查看历史——掌握每天都会用到的核心命令

创建仓库

git init — 在当前目录初始化

在一个已有的项目目录中运行 git init,Git 会在其中创建一个 .git 子目录,这就是仓库的"大脑"。

# 在当前目录初始化 Git 仓库
git init

# 指定目录名初始化(会创建该目录)
git init my-project

# 初始化后的状态
git status

.git/ 目录包含 Git 仓库的所有数据,永远不要手动删除或修改它,除非你知道自己在做什么。主要结构:

.git/
├── HEAD          # 指向当前分支
├── config        # 仓库级配置
├── objects/      # 所有对象(blob/tree/commit/tag)
├── refs/
│   ├── heads/    # 本地分支引用
│   └── tags/     # 标签引用
└── index         # 暂存区(二进制文件)

git clone — 克隆远程仓库

克隆会下载远程仓库的全部历史和文件,自动设置 origin 远程并建立追踪关系。

# 克隆到同名目录
git clone https://github.com/user/repo.git

# 克隆到指定目录名
git clone https://github.com/user/repo.git my-local-name

# 使用 SSH(需提前配置 SSH 密钥)
git clone git@github.com:user/repo.git

# 只克隆最近 1 次提交(浅克隆,加快速度)
git clone --depth 1 https://github.com/user/repo.git

查看仓库状态

git status 是你最常运行的命令之一,它显示工作区和暂存区的当前状态。

git status

典型输出及含义:

On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:     # 暂存区中,等待提交的变更
  (use "git restore --staged <file>..." to unstage)
        new file:   README.md
        modified:   src/app.js

Changes not staged for commit:  # 工作区中,已修改但未 add 的文件
  (use "git add <file>..." to update what will be committed)
        modified:   src/utils.js

Untracked files:             # 从未被 Git 追踪过的新文件
  (use "git add <file>..." to include in what will be committed)
        notes.txt
# 简洁格式(适合日常使用)
git status -s
# M  = 已修改(M在左=暂存区,M在右=工作区)
# ?? = Untracked(未追踪)
# A  = 新文件已暂存

添加到暂存区

基本用法

# 添加指定文件
git add index.html

# 添加整个目录
git add src/

# 添加当前目录下所有变更(包括新文件、修改、删除)
git add .

# 添加所有 .js 文件
git add *.js

git add -p — 交互式选择部分变更

这是一个强大但少被初学者知道的功能。它允许你逐块(hunk by hunk)选择要暂存的变更,而不必把整个文件的所有变更一起提交。

git add -p src/app.js

交互时可用的选项:

按键含义
y暂存这个 hunk(代码块)
n跳过这个 hunk
s将当前 hunk 再拆分为更小的块
e手动编辑这个 hunk
q退出,不再处理剩余 hunk

为什么要用 -p?假设你在修复一个 bug 的同时顺手重构了几行代码,-p 让你把 bug 修复和重构分成两个独立的提交,保持提交历史的清晰。

提交变更

基本提交

# 提交暂存区的内容,打开编辑器输入提交信息
git commit

# 直接在命令行指定提交信息(最常用)
git commit -m "feat: add user login page"

# 跳过 git add,直接提交所有已追踪文件的变更(不包含新文件)
git commit -am "fix: correct calculation logic"

# 修改最后一次提交(仅在未 push 前使用!)
git commit --amend -m "fix: correct calculation logic (amended)"

好的提交信息规范

一个好的提交信息应遵循50/72 规则:主题行不超过 50 字符,正文每行不超过 72 字符,主题行与正文之间用空行分隔。

feat: add OAuth2 login with Google

- Implement Google OAuth2 callback endpoint
- Store access token in encrypted session cookie
- Add redirect to original URL after successful login

Closes #142

更进一步,推荐遵循 Conventional Commits 规范,提交信息格式为 type(scope): description

类型含义示例
feat新功能feat: add dark mode toggle
fixBug 修复fix: resolve null pointer in parser
docs文档更新docs: update API reference
refactor重构(不改变功能)refactor: extract auth middleware
test添加或修改测试test: add unit tests for validator
chore杂务(构建、依赖)chore: upgrade webpack to v5

查看提交历史

# 基础日志,显示完整哈希、作者、日期、提交信息
git log

# 每行一条提交(最常用!)
git log --oneline

# 图形化显示所有分支(强烈推荐!)
git log --oneline --graph --all

# 显示每次提交的文件差异
git log -p

# 只看最近 3 次提交
git log -3

# 按作者过滤
git log --author="张三"

# 按时间过滤
git log --since="2024-01-01" --until="2024-06-30"

# 搜索提交信息中包含关键词的提交
git log --grep="login"

# 自定义格式输出
git log --pretty=format:"%h %an %ar %s"

--oneline --graph --all 的典型输出:

* a3f2c1d (HEAD -> main, origin/main) feat: add profile page
* 8e4b9a2 fix: correct email validation
| * c7d1e3f (feature/dark-mode) feat: implement dark mode
| * 5a2b8c1 style: add CSS variables for theme
|/
* 2f9d4e5 refactor: extract auth module
* 1b3c7a9 init: initial commit

查看文件差异

# 工作区 vs 暂存区(未 add 的变更)
git diff

# 暂存区 vs 最新提交(已 add、未 commit 的变更)
git diff --staged

# 工作区 vs 最新提交(所有未提交的变更)
git diff HEAD

# 两次提交之间的差异
git diff HEAD~1 HEAD

# 两个分支之间的差异
git diff main..feature-login

# 只显示变更了哪些文件,不显示具体内容
git diff --name-only

配置 .gitignore

.gitignore 文件告诉 Git 哪些文件不需要追踪。应放在仓库根目录并提交到版本控制。

语法规则

模式含义示例
*匹配任意字符(不含路径分隔符)*.log 忽略所有 log 文件
**匹配任意层级的目录**/temp 忽略任意位置的 temp 目录
?匹配单个字符file?.txt
/ 结尾只匹配目录build/ 只忽略目录
! 开头取消忽略(例外)!important.log 不忽略此文件
# 开头注释# 这是注释

典型 .gitignore 示例

# 依赖目录
node_modules/
vendor/

# 构建输出
dist/
build/
*.min.js

# 环境变量(绝对不要提交!)
.env
.env.local
.env.*.local

# 系统文件
.DS_Store
Thumbs.db

# 日志文件
*.log
npm-debug.log*

# IDE 配置
.idea/
.vscode/
*.swp
!

已追踪的文件不会被 .gitignore 影响!如果一个文件已经被提交过,之后再把它加入 .gitignore 并不会让 Git 停止追踪它。需要先运行 git rm --cached filename 将其从追踪列表中移除(不删除本地文件),再提交。

查看特定提交

# 查看某次提交的详细内容(变更了什么)
git show a3f2c1d

# 查看最新提交
git show HEAD

# 只显示某次提交修改的文件列表
git show a3f2c1d --name-only

# 查看某次提交中某个文件的内容
git show a3f2c1d:src/app.js