Skip to content

git merge vs git rebase

git mergegit rebase 是两种合并分支的主要策略,它们各有优劣。

  • git merge:

    • 优点: 忠实记录了分支的合并历史,不会破坏原分支的提交记录。
    • 缺点: 每次合并都会产生一个额外的合并提交(merge commit),可能导致提交历史变得复杂、分支线交错。

    git merge 示意图

  • git rebase (变基):

    • 优点: 会将当前分支的提交“变基”到目标分支的最新提交之后,形成一条线性的提交历史,使历史记录更简洁、直观。
    • 缺点: 会改写提交历史。绝对不要在共享的公共分支(如 master, develop)上执行 rebase,这会给协作者带来巨大的麻烦。

    git rebase 示意图

使用场景

  • git merge: 用于将功能分支合并到公共分支(如 developmaster)时。保留合并记录有助于代码审查和追溯。
  • git rebase: 用于在个人开发分支上同步主分支的最新代码时。可以保持个人分支的提交历史干净。

rebase 后的 push 问题

当你对一个已经推送到远程的分支执行 rebase 后,本地分支的历史记录被改写,与远程分支产生分歧。此时直接 git push 会失败。

rebase 后 push 失败

如果确认该分支只有你一个人在开发,可以使用强制推送来更新远程分支:

shell
git push --force-with-lease origin <branch-name>
# 或者更危险的


<NolebasePageProperties />




git push --force origin <branch-name>

注意: --force 会强制覆盖远程分支,可能导致他人提交丢失。--force-with-lease 相对安全,它只在远程分支没有新的提交时才强制推送。

git switchgit restore

git checkout 命令功能过于庞杂(切换分支、恢复文件等),因此社区引入了 switchrestore 使职责更清晰。

  • git switch: 专注于分支切换。

    shell
    # 切换到已有分支
    git switch main
    
    # 创建并切换到新分支
    git switch -c new_branch
    
    # 切换到某个 commit(分离头指针状态)
    git switch -d f8c5408
  • git restore: 专注于文件恢复。

    shell
    # 恢复工作区的文件,撤销未暂存的修改
    git restore test.txt
    
    # 从暂存区恢复文件到工作区(unstage)
    git restore --staged test.txt

git revertgit reset

  • git revert <commit-id>: 创建一个新的提交,用于撤销某次指定提交的更改。原有的提交历史得以保留,是一种安全的回退方式。
  • git reset <commit-id>: 将 HEAD 指针回退到指定的提交,其后的提交将从历史记录中移除(取决于模式)。这是一种破坏性的操作。
特性git resetgit revert
效果指针后移,丢弃或重置后续提交新增提交,抵消某个历史提交
历史记录修改历史记录(破坏性)不修改历史记录(非破坏性)
协作只能在私有分支使用可以在公共分支使用
安全性较低,可能丢失工作较高,所有操作都有记录

reset 的三种模式

  • git reset --soft <commit-id>: 仅移动 HEAD 指针。回退后的变更会保留在暂存区(Staged)。
  • git reset --mixed <commit-id> (默认): 移动 HEAD 指针,并重置暂存区。回退后的变更会保留在工作区(Unstaged)。
  • git reset --hard <commit-id>: 移动 HEAD 指针,并重置暂存区和工作区。回退后的变更会彻底丢失,请谨慎使用!

git tag

git tag 用于为项目历史中的某个特定节点(通常是版本发布点)创建一个语义化的别名。

shell
# 创建一个带附注的标签
git tag -a v1.0.0 -m "发布 1.0.0 版本"

# 为某个特定的 commit 打标签
git tag -a v0.9.0 <commit-id> -m "v0.9.0 版本"

# 推送所有本地标签到远程
git push --tags

版本规范

推荐遵循 语义化版本 2.0.0 (SemVer) 规范。版本号格式为 主版本号.次版本号.修订号 (Major.Minor.Patch)。

git worktree

git worktree 允许你同时在同一个仓库中检出多个工作树(工作目录),对应不同的分支。这对于需要在不同分支间频繁切换、或同时进行多任务开发非常有用。

shell
# 在 ../my-hotfix 目录下创建一个链接到主仓库的 hotfix 分支工作区
git worktree add ../my-hotfix hotfix

# 查看所有工作区
git worktree list

# 清理无效的工作区引用
git worktree prune

# 移除工作区
git worktree remove ../my-hotfix

Git Flow

Git Flow 是一种成熟的、基于分支的开发模型,它为不同类型的开发活动定义了专门的分支。

Git Flow 模型

  • 主分支:
    • master: 存放正式发布版本,代码永远是可部署的。
    • develop: 日常开发的主分支,整合所有已完成的功能。
  • 辅助分支:
    • feature/*: 开发新功能的分支,基于 develop 创建,完成后合并回 develop
    • release/*: 准备发布新版本的分支,用于修复 Bug 和准备元数据。基于 develop 创建,完成后合并到 masterdevelop
    • hotfix/*: 修复线上紧急 Bug 的分支,基于 master 创建,完成后合并到 masterdevelop

常见问题

.gitignore 不生效

.gitignore 文件只能忽略那些从未被 Git 跟踪(track)过的文件。如果文件已经被纳入版本管理,修改 .gitignore 是无效的。

解决方法:从 Git 的暂存区中移除该文件,然后重新提交。

shell
# 递归地移除所有文件的缓存
git rm -r --cached .

# 重新添加所有文件(此时 .gitignore 会生效)
git add .

# 提交更改
git commit -m "Fix .gitignore tracking"

修改已提交的 commit message

shell
# 查看最近的提交历史,找到要修改的 commit 的父 commit ID
git log --oneline

# 进入交互式 rebase 界面
git rebase -i <parent-commit-id>

# 在编辑器中,将要修改的 commit 前的 `pick` 改为 `reword` (或 `r`)
# 保存并退出,Git 会弹出新的编辑器让你修改 commit message

git status 显示中文乱码

shell
git config --global core.quotepath false

参考

贡献者

The avatar of contributor named as jiechen jiechen

页面历史

撰写