撤销最近的commit,但保留修改的文件

git reset --soft HEAD~1

撤销最近的commit,不保留修改的文件

git reset --hard HEAD~1

撤销后找回commit(git reset --hard HEAD~1后)

git reflog找回提交的hash,再git reset --hard

放弃未暂存的更改

# 单个文件
git restore <文件>
 
# 全部
git restore .

恢复放弃的未暂存更改

local history 也可以试试ctrl+z

把新修改与上一次commit合并

不要用在已经push的commit上,不然下次push就要-f

git commit --amend

让已clone的文件夹成为submodule

git rm --cachedgit submodule add <repo> <local path>

创建和使用patch

  • git diff / git apply
    • 用途:主要用于分享**尚未提交(uncommitted)**的更改。
    • 特点:生成的 patch 文件只包含代码的差异(diff),不包含 commit 信息(如作者、提交信息等)。git apply 在应用补丁时,只是将更改放入你的工作目录或暂存区,不会自动创建 commit
git diff > my_changes.patch # unstaged
git diff --cached > staged_changes.patch # staged
git diff HEAD > all_uncommitted_changes.patch # unstaged & staged
git diff <commit_id_1> <commit_id_2> > changes_between_commits.patch
 
git apply --check my_changes.patch
git apply my_changes.patch
 
git add ...
git commit ...
  • git format-patch / git am
    • 用途:主要用于分享**已经提交(committed)**的更改。
    • 特点:生成的 patch 文件是 .mbox 格式,包含了完整的 commit 信息(作者、日期、提交信息、代码差异等)。git am 在应用补丁时,会尝试自动创建新的 commit,保留原始的提交信息。这是 Linux 内核等项目常用的邮件协作流程。
git format-patch -1 # 最新一个
git format-patch -3 # 最新3个
git format-patch -1 <commit_id>
git format-patch main # main比当前分支多的所有commit
 
git am --show-stats 0001-some-change.patch
git am 0001-some-change.patch
git am patches/*.patch

branch

删除

git checkout main # 切走
git branch -d my-feature # 删除,如未合并,会被阻止
git branch -D my-feature # 强制删除
git push origin --delete my-feature # 删除远程分支

push到指定远程分支

git push origin my-feature # 同名
git push origin local_name:remote_name # 不同名

开发分支从主分支同步最新提交

# 1. 切换到 main 分支
git checkout main
 
# 2. 从远程仓库拉取最新的提交
git pull origin main
 
# 3. 切换回你的开发分支
git checkout develop
 
# ---
 
# 4. 将 main 分支的更改合并(merge)到 develop 分支 
git merge main
 
# ---
 
# 4. 将 develop 分支变基(rebase)到 main 分支上 
git rebase main

把分支a的某个commit应用到分支b上

# 记得先确保已经切到了要修改的分支
git cherry-pick <commit-hash>
 
# 如有冲突,解决后:
git cherry-pick --continue
# 解决不了:
git cherry-pick --abort

如何选择merge还是rebase

  • git merge保留最真实、最完整的历史。它忠实地记录了每一次分支的集成事件。
  • git rebase创造一个更整洁、更线性的历史。它让历史记录看起来像是所有开发工作都是串行完成的,非常易于阅读。

何时选择 git merge (默认行为)

当你希望保留分支的完整历史,并且重视每一次集成的上下文时,应该使用 merge。

黄金法则:永远不要 rebase 已经推送到远程并且被多人使用的公共分支 (如 main, develop, release 分支)。

适用场景:

  1. 合并到公共分支: 当一个功能分支 (feature) 开发完成,需要合并回 main 或 develop 分支时,通常会使用 merge。这会在主干上创建一个合并提交,清晰地记录了“功能 X 在这个时间点被集成进来了”,保留了整个功能分支的开发上下文。
  2. 团队协作: 当多个人在同一个长期存在的分支上协作时,使用 merge 来同步彼此的更改可以避免很多混乱。

优点:

  • 可追溯性强: 历史是真实的,不会被修改。你可以清楚地看到每个分支的来龙去脉,以及它们在何时何地交汇。
  • 安全简单: 不会改变已有的提交历史,对于 Git 新手来说更安全,不易出错。
  • 保留上下文: 合并提交本身就是一个有意义的记录,它封装了一个完整的功能或修复。

缺点:

  • 历史记录混杂: 如果功能分支非常多且频繁,主分支的历史图谱会变得非常复杂和混乱,充满了大量的合并提交,难以阅读。
  • “无意义”的合并提交: 类似于 “Merge branch ‘a’ into ‘b’” 这样的提交信息可能会充斥在 git log 中。

何时选择 git rebase

当你希望在合并前“清理”你的本地提交,并为主分支提供一个干净、线性的提交历史时,应该使用 rebase。

适用场景:

  1. 同步个人分支: 你在一个私有的功能分支上开发,此时公共的 main 分支有了新的提交。为了在你的分支上获得这些最新更新,使用 rebase 是最佳选择。这会把你的分支上的所有提交在最新的 main 分支上“重放”一遍。
    • 命令:git pull —rebase origin main
  2. 整理本地提交: 在将你的功能分支推送到远程准备 Code Review 之前,可以使用交互式 rebase (git rebase -i) 来整理你的提交。你可以:
    • squash: 将多个零碎的提交(如 “fix typo”, “wip”)合并成一个有意义的提交。
    • reword: 修改提交信息,使其更清晰。
    • reorder: 调整提交的顺序。

优点:

  • 历史记录清晰: 最终合并到主分支后,提交历史是一条直线,没有分叉和合并,非常容易阅读和理解。
  • 没有冗余的合并提交: 避免了 merge 带来的额外合并节点。
  • 易于定位问题: 由于历史是线性的,使用 git bisect 等工具追溯引入问题的提交会变得非常容易。

缺点:

  • 破坏了历史的真实性: Rebase 的本质是重写历史,它会创建新的提交(即使内容一样,hash 值也变了)。
  • 潜在风险: 如果在共享分支上使用,会给其他协作者带来巨大的麻烦。他们需要复杂的修复操作才能同步你的“新历史”。
  • 冲突处理更复杂: 如果 rebase 过程中有多个提交都与基底分支冲突,你需要一个一个地解决这些提交的冲突,而 merge 只需要解决一次总的冲突。

推荐的团队协作工作流 (混合使用)

大多数现代开发团队会采用一种结合了两者优点的混合策略:

  1. 分支内用 rebase 保持整洁:
    • 在自己的本地或私有功能分支上开发时,定期使用 git pull —rebase origin main 来同步主分支的最新代码。
    • 在准备提交合并请求 (Pull Request) 之前,使用 git rebase -i 来清理自己的提交记录,确保每一个 commit 都是有意义的、独立的单元。
  2. 分支间用 merge 记录历史:
    • 当功能分支准备就绪,提交 Pull Request。
    • 经过代码审查和自动化测试后,通过代码托管平台(如 GitHub, GitLab)的 “Merge” 按钮将该功能分支合并到 main 或 develop 分支。
    • 通常会选择创建一个合并提交 (—no-ff),即使可以 Fast-Forward。这样做的好处是,它会在主干上明确地记录“功能 X 的开发工作在这里被完整地并入”,同时保留了功能分支内部的清晰线性历史。