Git 操作笔记
> 按学习曲线编排:从初始化配置 → 基础使用 → 进阶操作 → 回滚处理
目录
-
初始化与配置\](#1-初始化与配置)
-
基础操作 - 分支\](#3-基础操作---分支)
-
同步与推送\](#5-同步与推送)
-
回滚操作\](#7-回滚操作)
-
查询与调试\](#9-查询与调试)
1. 初始化与配置
初始化 Git 项目
```bash
cd /path/to/your/project # 进入项目目录
git init # 初始化 Git 仓库
```
创建初始提交
```bash
git add . # 将所有文件加入暂存区
git commit -m "Initial commit" # 添加初始提交
```
配置 pull 行为
```bash
git config --global pull.rebase true # 设置 pull 后默认执行 rebase
git config --unset --global pull.rebase # 取消设置
```
> 建议:代码在提交前最好执行一次 `git pull --rebase`
2. 基础操作 - 仓库与远程
添加远程仓库
```bash
git remote add origin https://github.com/yourname/your-repo.git
git remote -v # 查看远程仓库地址
```
添加多个远程仓库
```bash
方式一:添加为独立远程
git remote add gitlab https://gitlab.com/yourname/repo.git
git remote add gitee https://gitee.com/yourname/repo.git
推送时指定远程
git push origin main
git push gitlab main
git push gitee main
```
让一个远程名称对应多个推送地址
```bash
git remote add origin https://github.com/yourname/repo.git
为同一个 origin 添加额外的 push 地址
git remote set-url --add --push origin https://gitlab.com/yourname/repo.git
git remote set-url --add --push origin https://gitee.com/yourname/repo.git
移除默认 push URL(避免重复推送)
git remote set-url --delete origin --push https://github.com/yourname/repo.git
git remote -v # 查看所有远程仓库地址
```
3. 基础操作 - 分支
创建分支
```bash
git checkout -b feature/new-feature # 创建并切换到新分支
```
推送并创建远程分支
```bash
git push -u origin feature/new-feature # -u 关联本地与远程分支
```
绑定已存在的远程分支
```bash
git branch -u origin/<远程分支名> <本地分支名>
或
git branch --set-upstream-to=origin/<远程分支名> <本地分支名>
```
创建本地分支并关联远程
```bash
git checkout -b <本地分支名> origin/<远程分支名>
```
检查分支关联信息
```bash
git branch -vv # 显示每个本地分支对应的远程分支
```
删除远程分支
```bash
git push origin --delete <远程分支名>
```
> 删除后,其他协作者执行 `git fetch --prune` 可以同步删除本地的远程追踪引用
4. 基础操作 - 提交
基本提交
```bash
git add . # 添加所有更改到暂存区
git commit -m "提交信息" # 提交
```
修改最后一次提交
```bash
git commit -a --amend # 将更改追加到最近一次提交
会打开编辑器,可修改提交信息
```
合并多个提交
```bash
git rebase -i HEAD~3 # 针对最近 3 次提交做交互式变基
```
编辑器中按从旧到新排列提交:
```
pick abc1234 第一次提交
squash def5678 第二次提交
squash ghi9012 第三次提交
```
-
**pick**:保留该提交
-
**squash**:合并到前一个提交,保留提交信息
-
**fixup**:合并到前一个提交,丢弃提交信息
Cherry-pick 挑拣提交
> 挑选某个提交(或提交范围)到当前分支重新提交,**副作用:会增加一次提交**
>
> 注意:使用前需先切换到目标分支
使用步骤
```bash
1. 切换到目标分支(要接收挑拣提交的分支)
git checkout target-branch
2. 挑拣单个提交
git cherry-pick <commitId>
3. 推送到远程
git push origin target-branch
```
常用命令
| 命令 | 说明 |
|------|------|
| `git cherry-pick <commitId>` | 挑拣单个提交到当前分支 |
| `git cherry-pick <commit0>..<commitN>` | 挑拣 commit1 到 N(**不包含** commit0) |
| `git cherry-pick <commit0>^..<commitN>` | 挑拣 commit0 到 N(**包含** commit0) |
| `git cherry-pick <commitId> -n` | 挑拣但**不自动提交**(修改留在暂存区) |
| `git cherry-pick <commitId> -e` | 挑拣并**编辑提交信息** |
| `git cherry-pick <commitId> -x` | 挑拣时在提交信息末尾**添加原始 commit 的引用** |
| `git cherry-pick --continue` | 解决冲突后继续挑拣流程 |
| `git cherry-pick --abort` | 取消整个挑拣操作,恢复到挑拣前的状态 |
参数说明
-
**`<commitId>`**:要挑拣的提交哈希值(可用 `git log` 查看)
-
**`-n, --no-commit`**:挑拣后不自动提交,修改留在暂存区,方便手动调整
-
**`-e, --edit`**:挑拣后打开编辑器,可修改提交信息
-
**`-x`**:在提交信息末尾添加一行 `cherry picked from commit <hash>`,方便追溯
-
**`--continue`**:冲突解决后,继续执行挑拣
-
**`--abort`**:放弃挑拣,恢复原状
示例
**挑拣单个提交:**
```bash
git checkout main
git cherry-pick abc1234
```
**挑拣多个连续提交(不包含起点):**
```bash
git checkout main
git cherry-pick abc1234..def5678 # 挑拣 def5678 及其之后的所有提交
```
**挑拣多个连续提交(包含起点):**
```bash
git checkout main
git cherry-pick abc1234^..def5678 # 挑拣 abc1234 到 def5678 的所有提交
```
**挑拣但不立即提交(用于修改后再提交):**
```bash
git cherry-pick abc1234 -n
此时修改已在暂存区,可根据需要修改后再提交
git commit -m "自定义提交信息"
```
**挑拣并保留原始提交信息:**
```bash
git cherry-pick abc1234 -x
```
冲突处理
```bash
1. 挑拣时发生冲突,Git 会暂停
git cherry-pick abc1234
报错:CONFLICT (content): Merge conflict in <file>
2. 手动解决冲突后,标记为已解决
git add <file>
3. 继续挑拣流程
git cherry-pick --continue
或放弃本次挑拣
git cherry-pick --abort
```
注意事项
-
Cherry-pick **会增加新的提交**(新的 hash),不是移动提交
-
如果挑拣的提交涉及文件已被目标分支修改,可能产生冲突
-
建议在干净的工作区上执行挑拣操作
5. 同步与推送
同步远程仓库
```bash
git fetch origin # 同步 origin 的所有分支数据(不合并)
git pull # 同步并合并当前分支
相当于 git fetch + git merge
或 git fetch + git rebase
```
拉取特定分支
```bash
git fetch origin main # 只更新 origin/main
git pull origin dev:my-feature # 拉取并合并到本地指定分支
```
推送
```bash
git push <远程名称> <本地分支名>:<远程分支名>
git push origin main # 推送本地 main 到远程 main
git push origin main:feature-new # 推送本地 main 到远程 feature-new
```
git push 机制
**第一步比对 old commit id:**
| 变量 | 含义 |
|------|------|
| old-commit-hash | 客户端认为远程分支当前指向的提交(本地 origin/main 记录) |
| new-commit-hash | 客户端希望远程分支更新到的提交(本地 main 的哈希) |
**服务器比对逻辑:**
-
**old 匹配** → 进入对象协商阶段,客户端上传缺失对象
-
**old 不匹配** → 拒绝推送(有人比你先推送了,需先 fetch)
**能否推送成功:**
-
**fast-forward**(快进):本地提交包含远程提交作为祖先 → 推送成功
-
**non-fast-forward**(非快进):分支分叉 → 默认拒绝,需 `--force`
强制推送
```bash
git push --force origin <分支名> # 强制覆盖远程
git push --force-with-lease origin <分支名> # 安全强制推送(检查远程是否被更新)
```
6. 合并操作
> `git merge` 不加 `--no-ff` 默认使用 fast-forward 方式,可能不产生提交;加 `--no-ff` 总是产生新提交
检查两个分支是否存在冲突
**方法一:`git merge-tree`(推荐,Git 2.36+,不修改工作区)**
```bash
git merge-tree --write-tree branch-A branch-B
```
-
退出码 0:无冲突,输出合并后的文件树
-
退出码 1:有冲突,输出冲突信息
**方法二:`git merge --no-commit --no-ff`(会修改工作区)**
```bash
git checkout master
if git merge --no-commit --no-ff feature/login >/dev/null 2>&1; then
echo "无冲突"
git merge --abort
else
echo "有冲突"
git merge --abort
fi
```
7. 回滚操作
git add 后回退
```bash
git reset HEAD # 回退所有文件的暂存状态
git reset HEAD filename # 回退指定文件
```
> 文件修改不会丢失,`git status` 仍能看到修改
git commit 后回退
> 回退后如需重新提交,需重新 `git add`
| 模式 | 命令 | HEAD | 暂存区 | 工作区 |
|------|------|------|--------|--------|
| 硬回退 | `git reset --hard <hash>` | 回退 | 清空 | 清空(丢失修改) |
| 软回退 | `git reset --soft <hash>` | 回退 | 保留 | 保留 |
| 混合回退 | `git reset --mixed <hash>` | 回退 | 清空 | 保留(变未暂存) |
```bash
git reset --hard <commit-hash> # 硬回退(慎用,丢失所有修改)
git reset --soft <commit-hash> # 软回退(撤销提交,修改保留在暂存区)
git reset --mixed <commit-hash> # 混合回退(默认,修改变未暂存)
```
安全的撤销方式(推荐)
```bash
git revert <commit-hash> # 创建逆向提交,撤销指定提交
历史完整,不会丢失提交记录
```
修补最后一次提交
```bash
git add . && git commit --amend # 漏提交时补救,不产生新提交
```
git push 后回退
```bash
git revert <commit-hash> # 创建逆向提交
git push origin <branch> # 推送(git revert 新增提交,无需强制推送)
```
如果需要回退多个提交:
```bash
git revert HEAD~3..HEAD # 回退最近 3 个提交
```
其他情况需要强制推送:
```bash
git push --force-with-lease origin <branch>
```
8. 进阶操作
交互式变基
```bash
git rebase -i HEAD~3 # 打开编辑器,编辑最近 3 个提交
```
可用操作:
-
`pick`:保留提交
-
`squash`:合并提交,保留提交信息
-
`fixup`:合并提交,丢弃提交信息
-
`reword`:修改提交信息
-
`drop`:删除提交
> 如果已 push,需要 `git push --force-with-lease` 强制推送
获取分支最新 commit id
```bash
git rev-parse origin/feature-newapi-26900
```
9. 查询与调试
Git Log
```bash
git log --graph # 显示合并路径
git log -1 # 最新 1 条日志
git log -1 --oneline # 一行显示
git log -1 --format="%ai" # 显示最新提交时间
git log --since="2026-01-01" # 按日期筛选
```
查看历史记录(指针移动历史)
```bash
git reflog # 记录本地所有 HEAD 指针移动
git reflog -1 --format='%ai' # 最新一次操作的时间
```
遍历与对比提交
```bash
两点表示法:列出 B 中有而 A 中没有的提交
git rev-list A..B
三点表示法:列出 A 和 B 各自独有的提交(对称差集)
git rev-list A...B
统计差异提交数
git rev-list --count main..feature
快速判断 A 是否包含 B 的所有提交
if [ $(git rev-list --count B..A) -eq 0 ]; then echo "A 包含 B"; fi
```
对比两个分支差异
```bash
git diff --quiet "origin/SOURCE_BRANCH" "origin/TARGET_BRANCH"
```
退出码:
-
**0**:无差异
-
**1**:有差异
-
**128**:命令执行出错
查询分支是否存在
```bash
本地分支
git show-ref --verify --quiet "refs/heads/$SOURCE_BRANCH"
远程分支
git ls-remote --exit-code --heads origin "$TARGET_BRANCH"
```
> `--heads` 只查询分支,不查询标签
查找分支交叉点
```bash
git merge-base branch-A branch-B # 找出最近共同祖先
判断 A 是否是 B 的祖先(合入检查)
git merge-base --is-ancestor A B
如果 A 的内容全部已合入 B,返回 0
```
其他查询
```bash
git rev-list --since="1 week ago" --author="John" --all # 某人本周的提交
git rev-list --grep="hotfix" --all # 包含关键字的提交
git rev-list --merges main..HEAD # 指定范围的合并提交
git rev-list @{upstream}..HEAD # 领先上游分支的提交
```
stash 暂存修改
```bash
git stash # 暂存当前修改
git stash save "备注信息" # 暂存并添加备注
git stash list # 查看暂存列表
git stash pop # 恢复暂存并删除
git stash apply # 恢复暂存但保留记录
git stash drop # 删除某个暂存
git stash clear # 清空所有暂存
```
忽略文件
```bash
git update-index --assume-unchanged <file> # 临时忽略(本地有效)
git update-index --no-assume-unchanged <file> # 取消忽略
git ls-files -v | grep "^h " # 查看被忽略的文件
```
清理与重置
```bash
git clean -fd # 删除未跟踪的文件和目录
git clean -n # 预览将要删除的文件(不实际删除)
git gc --aggressive # 垃圾回收,优化仓库
```
标签操作
```bash
git tag v1.0.0 # 创建轻量标签
git tag -a v1.0.0 -m "版本说明" # 创建附注标签
git tag # 查看标签列表
git push origin v1.0.0 # 推送标签到远程
git push origin --tags # 推送所有标签
```
查看差异
```bash
git diff # 查看工作区与暂存区的差异
git diff --staged # 查看暂存区与上次提交的差异
git diff HEAD # 查看工作区与上次提交的差异
git diff commit1 commit2 # 对比两个提交
git diff branch1 branch2 # 对比两个分支
```
查看文件历史
```bash
git log -p <file> # 查看文件的提交历史
git blame <file> # 查看文件的每一行是谁修改的
git log --follow <file> # 跟踪文件的重命名
```
撤销操作
```bash
git checkout -- <file> # 丢弃工作区的修改(未暂存)
git restore <file> # 同上(新命令)
git restore --staged <file> # 取消暂存(保留工作区修改)
git reset HEAD <file> # 同上(旧命令)
```
其他常用命令
```bash
git status # 查看当前状态
git status -s # 简洁模式
git remote -v # 查看远程仓库地址
git remote show origin # 查看远程仓库详情
git remote prune origin # 清理已删除的远程分支引用
git fetch --all # 获取所有远程仓库的更新
git branch -a # 查看所有分支(包括远程)
git branch -d <分支名> # 删除本地分支
git tag -d <标签名> # 删除本地标签
git push origin --delete <分支名> # 删除远程分支
git push origin :<分支名> # 同上,另一种写法
```
10. Shell 脚本示例
批量合并脚本
```bash
#!/bin/bash
BASE_DIR="."
SOURCE_BRANCH="feature/your-feature-branch"
TARGET_BRANCH="master"
find "$BASE_DIR" -maxdepth 2 -type d -name ".git" | sed 's/\/.git//' | while read -r repo; do
echo "检查仓库: $repo"
cd "$repo" || continue
git fetch origin
if git diff --quiet "origin/SOURCE_BRANCH" "origin/TARGET_BRANCH"; then
echo "✅ 无差异,跳过"
else
echo "⚠️ 发现差异,开始合并"
git checkout "TARGET_BRANCH" 2\>/dev/null \|\| git checkout -b "TARGET_BRANCH" "origin/$TARGET_BRANCH"
git reset --hard "origin/$TARGET_BRANCH"
if git merge "origin/$SOURCE_BRANCH" --no-edit; then
echo "✅ 合并成功"
else
echo "❌ 合并冲突"
git merge --abort
fi
fi
done
```
检测合入后是否有新提交
```bash
#!/bin/bash
check_repo() {
local repo_path=$1
local repo_name=(basename "repo_path")
cd "repo_path" \|\| { echo "ERROR\|repo_name||仓库无法访问"; return; }
拉取最新代码
if [[ "$FETCH_BEFORE_CHECK" == "yes" ]]; then
export GIT_TERMINAL_PROMPT=0
git fetch origin --quiet 2>/dev/null
fetch_result=$?
unset GIT_TERMINAL_PROMPT
if [[ $fetch_result -ne 0 ]]; then
echo "SKIP|$repo_name||拉取失败(需要认证)"
return
fi
fi
检查远程分支是否存在
if ! git ls-remote --exit-code --heads origin "$SOURCE_BRANCH" >/dev/null 2>&1; then
echo "SKIP|repo_name\|\|源分支 SOURCE_BRANCH 不存在"
return
fi
if ! git ls-remote --exit-code --heads origin "$TARGET_BRANCH" >/dev/null 2>&1; then
echo "SKIP|repo_name\|\|目标分支 TARGET_BRANCH 不存在"
return
fi
找共同祖先
local merge_base
merge_base=(git merge-base "origin/SOURCE_BRANCH" "origin/$TARGET_BRANCH" 2>/dev/null)
if [[ -z "$merge_base" ]]; then
echo "NEVER_MERGED|repo_name\|SOURCE_BRANCH|无共同祖先从未合入"
return
fi
检查源分支是否是目标分支的祖先
if git merge-base --is-ancestor "origin/SOURCE_BRANCH" "origin/TARGET_BRANCH" 2>/dev/null; then
local merge_date
merge_date=(git log -1 --format="%ai" "merge_base" 2>/dev/null)
echo "OK|repo_name\|SOURCE_BRANCH|0|0|merge_date\|merge_base|无遗漏"
else
local merge_date
merge_date=(git log -1 --format="%ai" "merge_base" 2>/dev/null)
local missing_count
local missing_merges
missing_count=(git rev-list "merge_base..origin/$SOURCE_BRANCH" --count 2>/dev/null)
missing_merges=(git rev-list "merge_base..origin/$SOURCE_BRANCH" --merges --count 2>/dev/null)
echo "WARNING|repo_name\|SOURCE_BRANCH|missing_count\|missing_merges|merge_date\|merge_base|合入后存在新提交!"
fi
}
```
附录:origin 是什么
> `origin` 是 git 默认的远程仓库别名,你也可以换成其他名称(如 `gitlab`、`gitee` 等)