Git 鼓励大量使用分支,本篇覆盖本地分支操作、合并策略、远程协作三大块。
本地分支
$ git branch # 列出所有本地分支(当前分支前有 *)
$ git branch <name> # 创建新分支(不切换)
$ git checkout <name> # 切换分支
$ git checkout -b <name> # 创建并切换
$ git branch -d <name> # 删除分支(已合并才能删)
$ git branch -D <name> # 强制删除(未合并也能删)
恢复未提交的文件到线上最新版本
$ git checkout -- <filename>
或带匹配符:
$ git checkout -- src/*.java
恢复的是 HEAD 指向版本,工作区未提交的改动会丢失。
合并策略
git merge 基础
$ git merge <branch> # 把 branch 合并到当前分支
合并时 Git 默认启用 fast-forward——如果当前分支没有新提交,Git 只会把指针向前移动,看起来像没合并过一样。
--no-ff 模式合并
$ git merge --no-ff <branch>
强制生成一个 merge commit,保留分支历史:
# fast-forward 后
A - B - C - D (看不出 D 是合并进来的)
# --no-ff 后
A - B - C - M M 是合并提交,能看出曾经做过合并
\ /
D - E - F
何时用 —no-ff:合并 feature 分支到 main 时,建议用
--no-ff,让 git log 能看到”这里曾经合并过 feature 分支”,便于追溯。
冲突解决流程
合并两个分支时如果改了同一行,Git 会停下来让你手动解决:
$ git merge feature-x
# Auto-merging src/UserService.java
# CONFLICT (content): Merge conflict in src/UserService.java
# Automatic merge failed; fix conflicts and then commit the result.
冲突文件里会出现冲突标记:
public class UserService {
<<<<<<< HEAD
private String name = "main"; // 当前分支的版本
=======
private String name = "feature"; // 传入分支的版本
>>>>>>> feature-x
}
解决步骤:
- 手动编辑文件,决定保留哪段或合并两段
- 删除
<<<<<<<、=======、>>>>>>>标记 git add <file>(标记冲突已解决)git commit(Git 默认会带 merge message)
也可以
git merge --abort放弃这次合并,回到合并前的状态。
rebase vs merge 取舍
git rebase 把当前分支的提交”重放”到目标分支最新提交之后,让历史变直线:
$ git checkout feature
$ git rebase main # 把 feature 的提交重放到 main 最新位置
对比:
| 维度 | merge | rebase |
|---|---|---|
| 历史 | 真实保留分支结构 | 改成直线(改写历史) |
| 适用分支 | 公共分支 | 私有本地分支 |
| 协作风险 | 无 | 已推送的提交 rebase 会让别人 pull 困难 |
黄金法则:
“未推送用 rebase,已推送用 merge”
- 在私有 feature 分支上提交 → rebase 整理后再合并
- 已经推送到公共分支的提交 → 永远不要 rebase
远程分支
查看远程分支
$ git branch -a # 看所有分支(本地 + 远程)
$ git branch -vv # 看每个本地分支的远程追踪关系
$ git remote -v # 看远程仓库地址
推送新分支
$ git push origin <branch>
$ git push -u origin <branch> # -u 建立追踪关系
删除远程分支
$ git push origin -d <branch> # 简写
$ git push origin --delete <branch> # 完整写法
切换远程分支到本地
clone 项目后想切到已有的远程分支上开发:
$ git branch -a
# * master
# remotes/origin/dev
# remotes/origin/master
错误做法(推荐度低):
$ git checkout -b dev
# 这种方式创建的 dev 跟远程 dev **毫无关系**,只是名字相同
# 下次 push 会报"没有追踪信息"的错
正确做法:
# 方式一:直接 checkout 远程分支
$ git checkout dev # 自动追踪 origin/dev
# 方式二:显式指定来源
$ git checkout -b dev origin/dev
效果对比:

切到 dev 后再 push 时如果报”没有追踪信息”:

解决:
$ git push -u origin dev
-u相当于建立”本地 dev 永远对应远程 origin/dev”的关系,之后 push/pull 不用再带参数。
远程分支名带 origin/ 前缀的原因
git branch -a 看到的 remotes/origin/dev 是远程跟踪分支——它只是本地对远程分支状态的缓存,不是真正的”远程分支”。本地可以基于它创建工作分支,但不要直接提交到远程跟踪分支上。
fetch vs pull
$ git fetch <remote> # 下载远程更新,**不自动合并**
$ git pull <remote> # fetch + merge 的组合
| 命令 | 行为 | 适用场景 |
|---|---|---|
git fetch | 只下载,需要手动 merge/rebase | 想先看更新再决定 |
git pull | 自动 fetch + merge | 快速同步 |
推荐做法:本地用
git pull --rebase,等价于”fetch + rebase”,让本地历史保持直线。
多人协作的标准流程
1. git push origin branch-name
↓ 失败(远程比本地新)
2. git pull
↓ 无冲突 → 直接 push 成功
↓ 有冲突 → 解决冲突 → commit → push
如果 git pull 提示 “no tracking information”,说明本地分支和远程分支的链接关系没建立:
$ git branch --set-upstream branch-name origin/branch-name
或者推送时直接 -u:
$ git push -u origin branch-name
强制推送的安全姿势
如果需要覆盖远程分支(rebase 之后、删了某些 commit 后),需要 force push:
$ git push --force # 直接覆盖(危险,可能丢别人的提交)
$ git push --force-with-lease # 推荐:只在"远程没被别人推过新提交"时才覆盖
--force-with-lease比--force安全:它会先检查远程分支是否和你上次 fetch 的一致,不一致就拒绝 push——避免无意中覆盖别人的提交。
下一篇:Git 知识系列(五):标签、忽略与子模块,讲 tag 的轻量/附注区别、.gitignore 语法、submodule 子模块管理。