git rebase
我们还是看这个图,本来 topic 分支是从 E 打出来的,如果想改成 从 G 打出来,可以这样写
git rebase master topic
一般情况下,我们是在 topic 分支执行这个命令,可以省略后面的 topic
git rebase master 复制代码
除了变基, rebase 最好用的功能是修改未发布分支的历史。
修改最近三条提交
rebase -i head~3 复制代码
弹出一个对话框 ,最上面显示的是对三条信息的处理意见。注意是正序显示了。默认为采用,保持原样。
pick a582932 第一次提交 pick 4665a16 第二次提交 pick 31b2f1f 第三次提交 复制代码
现在删除第一次提交,修改第二次提交,保留第三次提交
drop a582932 第一次提交 edit 4665a16 第二次提交 pick 31b2f1f 第三次提交 复制代码
修改完成后,保存退出。rebase 停在第二次提交,你可以做修改,然后执行 git commit --amend
如果一切满意,执行 git rebase --continue
,最后显示成功。
有这几种常用的命令
- pick : 保持不变
- reword = 只修改 message,也就是只修改提交信息。
- edit = 停在这个提交,修改, 执行 git commit --amend,提交修改。
- squash = 合并到前一个提交。会弹出一个对话框,默认显示所有合并 commit 的提交信息,你可以直接保存退出,也可以编辑提交信息,直接满意。所有合并的提交会被删除,用一个新的提交代替。
- fixup [-C | -c] = 如果不带参数,和squash 一样,只是不弹出对话框,直接采用前一个提交的 message 生成新的提交。如果加 -c 参数 ,和 squash 的形为差不多一样。
- exec = 不处理 commit,只是单纯的执行 shell 命令。比如
exec list 复制代码
- 会执行 list 命令
- break = 停止执行 (恢复执行 'git rebase --continue')
- drop = remove commit
注意 rebase 只应该修改未发布的 commit
git 分支
分支是并行开发的基础。分支名称的本质是对分支最后一个提交的引用。分支有多个,但 HEAD 只有一个,可以认为 HEAD 是"current branch"(当下的分支)。当你用git switch
切换分支的时候,HEAD 重新指向新的分支。
分支是 git 的杀手级应用。Git 处理分支的方式可谓是难以置信的轻量,git 鼓励在工作流程中频繁地使用分支与合并,哪怕一天之内进行许多次。分支会涉及很多常用 git 命令,我们在这里一起讲。
分支的名称是一个引用,指向支持的最后一个提交节点。分支就是从父分支开始的第一个节点,到最后一个节点的所有 节点的集合。
新建 git 分支
当你新建仓库的时候,会默认建立 master 分支。
git init test git init 复制代码
两种写法都可以。不同的是,git init test
会在当前目录下新建 test 文件夹,git init
会在当前目录初始化 git。
至于说建议用 main 而不是 master,其实就是名字,名字本无含意,在 git 中就是分支的名字而已,是看名字的人读出了所谓含意。如果在意名字,也是可以修改的。
git init -b main 用 -b 指定初始化的分支为 main 复制代码
也可以在全局指定,后面就不用 -b
指定了。
git config --global init.defaultBranch main 复制代码
初始化完成后,新建其它的分支有三种方法。
git checkout -b dev git switch -b dev git branch dev 复制代码
这三种方法都可以新建 dev 分支。不同的是前两种新建并转到新分支,最后一种只新建分支不跳转。
既然 git checkout -b dev
和git switch -b dev
效果完全一样,为什么弄出两个来?开始都是用 git checkout -b
,后来觉得用git checkout -b
不够清晰,checkout
被赋予了太多职责,所以增加了 switch 命令。
git cherry-pick
为什么要用 cherry-pick?
不适合 merge 的场景就可以考虑 cherry-pick。
试想下面这些场景
- 只想同步分支的部分提交。两个分支是两上完全独立的 feature,不适合 merge。
- 不想过早的同步分支。
下面举几个例子。 dev 为 分支 ,A、B 为 commit。
git cherry-pick dev 将 dev 分支的最近一次提交,转移到当前分支。 git cherry-pick A 可以转移有权访问的任意分支的任意提交。 git cherry-pick A B 一次可以同步多个提交 转移从 A 到 B 的所有提交,不包含 提交 A。提交 A 必须早于提交 B,否则命令将失败,但不会报错。 git cherry-pick A..B 包含提交 A git cherry-pick A^..B 复制代码
如果没有冲突会在当前分支形成一个新的提交,提交的内容和 message 完全一样,只是 hash( commit id) 值不一样。
如果有冲突,解决冲突的方法前面在 git checkout
那一节已经说过,解决的方法是一样的,最后用 git cherry-pick --contine
,如果想撤销用 git cherry-pic --abort
git patch
为什么要用 patch?
不适合 merge,也不方便 cherry-pick 的场景,可以考虑 patch。
试想下面这些场景
- 两个不同的 git 库,其中的某段代码需要同步。
- 有些修改会影响所有开发者,但你想做这个修改,来验证一些东西。你需要另一个开发配合,需要把这个修改同步给他。直接 copy 是个办法,但如果修改较多,容易出错,用 patch 比较合适。
虽然 check-pick 也可以同步不同的库,但实操的时候,因为权限或安全问题,不大方便联网同步。
patch 方案
pach 有两种方案,diff 和 format-patch。
diff 仅保留了文件重 A 状态变成 B 状态的差别,而不会保留 commit 记录消息等信息,diff可以多个commit生成单个patch。用 git apply
应用补丁。
format-patch 完整保留了每次提交的完成信息,每个commit都生成一个patch文件。用 git am
应用 补丁。
检查都是用 git apply --check。查看 都是 git applay -stat
diff 生成 patch,apply 应用patch
制作 patch
git diff >fix.patch git diff 38d8e02 >fix.patch 相当于 git diff 38d8e02 HEAD >fix.patch 复制代码
总之,diff 的结果都可以制作 patch。
应用 patch
git apply --check fix.patch git apply fix.patch 复制代码
format-patch 制作 patch ,am 应用 patch
git format-patch -2 用最近的两次提交制作 patch git format-patch commitId 某次提交以后的所有patch,不包括本次提交 git format-patch --root commitId 从第一次提交到指定提交的所有 patch git format-patch -o patch -2 输出 patch 文件到 patch 文件夹 复制代码
format-patch
制作的 patch 是一个提交一个文件,正序排列。
0001-第一次提交.patch 0002-第二次提交.patch 复制代码
应用提交
git apply --check *.patch git am *.patch 复制代码
git stash
stash 的英文原意是 贮藏。git stash
的功能就是把当前工作区的内容存起来。和提交到暂存区不同,git stash
贮藏的内容不受分支切换的影响。
应用场景
- 开发了一阵,发现分支错了。这时最好的文案就是
git stash save
,切到新分支后git stash pop
。 - 开发到一半,有一个紧急的 bug 要 fix,这时提交会造成无效的提交记录。可以先
git stash save
,切换分支修复 bug,再切回来git stash pop
。
注意:没有被 add 过的文件不会被 stash 起来,如果想把这些文件也一起 stash,可以加上
-u
参数,它是--include-untracked
的简写, git stash -u。
非常感谢你能读到这里。如果你都掌握了,你现在已经掌握了足够的知识了,为了能运用自由,还需要融汇贯通,学而不思则罔。下面举几个实战的例子
git 命令实战
你在分支 A,一个同事在分支 B fix 了一个bug。你不方便 merge 分支B,只想更新这个 fix bug 的提交。
最先想到的是 cherry-pick,但还有两个办法,git restore,和 patch。相比较来说,如果 fix 已经提交到远程,cherry-pick 是最佳的,git restore 也可以,但是还得提交一次。如果网络不通,那只能用 patch了。
修改上次提交的 message 可以用 git commit --amend
,那如果是修改上上次提交的 message 呢?
方案一 reset + chery-pick
git log --oneline 081b00b (HEAD -> master) 第二次提交 451ddbc 第一次提交 复制代码
如果要修改第一次提交的提交信息,需要先退回到第一次提交,再修改提交信息
git reset --hard HEAD^ git commit --amend -m '第一次提交补充' 复制代码
修改完第一次提交的信息,我们用第二条提交的 commid id 恢复第二条信息
git cherry-pick 081b00b 复制代码
也许你会问,直接 把 HEAD reset 到 081b00b 可以吗?答案是不可以。因为执行 cherry-pick 后,虽然内容和 message 都一样,但这本质上却是一个新的提交。无法从这个提交回到 081b00b。
方案二 rebase
用 rebase 有一个前提,提交次数 >2
git rebase -i HEAD~2 复制代码
在 dev 分支上开发完了,发现提交记录太多太乱了,提交主干的时候想合成一个提交,将来查的时候也好查。
git merge-base master dev 输出:caa12ecabf18b0b7247f07481b01946f8b548d94 git reset --soft caa12ecabf18b0b7247f07481b01946f8b548d94 git commit 'feat:登录' 复制代码
示例把所有修改合成一个提交,也可以分成几次提交。如果那样的话,需要用 --mixed 参数 ,把暂存区也还原。
用分支开发的时候,合并到主干可能会产生分叉。如果不想分叉呢?也是可以做到的。
场景一 从master创建分支 dev 后,master 没有修改,合并的时候,不会有分叉。
场景二 从master创建分支 dev 后,master 有修改,合并的时候,有分叉,为了避免出现分叉,不直接在 master 分支执行 merge,而是在 dev 分支 执行 rebase ,然后在 master 再执行 merge。
git switch dev git rebase master git switch master git merge dev 复制代码
这样在 master 分支上就不会有分叉了。
rebase 的过程相当于把 dev的提交一个一个的重新提交到 master 分支,可能有冲突。解决冲突的办法有二,可以手动解决,也可以自动解决(前面讲 checkout 时有讲)。解决完了 git rebase --continue
我有好几个分支,git 怎么知道我在哪个分支呢?
cat .git/HEAD 输出:ref: refs/heads/dev2 复制代码
原来是 HEAD 的功劳。HEAD 可以理解成一个引用,它一般情况下是指向分支,有时也指出 commit id。
当你执行 git commit
的时候生成节点 A,A 把 HEAD 认作父节点 ,HEAD 再指向 A。
当你执行 git reset B
的时候 HEAD 指向 B。
当你执行 git checkout C
的时候,HEAD 指向 commit C,这时因为没有分支指向 C,HEAD 这时的状态叫 detatch(分离) 状态。
当你执行 git checkout dev
的时候,HEAD 指向 分支 dev。
美化
毫无章法的提交会让人感觉混乱。如果所有的提交都整齐划一就会让人有正规军的感觉。适当的美化可以让我们用很小的付出得到较大的回报。
提交信息格式化
具体的内容可能参看阮一锋老师的文章 Commit message 和 Change log 编写指南
实操作的时候,注意要结合实际。
提交历史要清晰
在代码 push 之前,最好是先做个检查,对提交做一些调整。可以用之前介绍过的 rebase。一个好的提交历史不仅可以让后面查找的时候方便,也会给别人留下严谨的印象。
参数归类
很多命令都用一样的参数表达同样的意思,看到这样的参数,不用看文档,也能知道它的含意。
- -q --quiet 安静。会尽量减少信息输出。
- -e --edit 打开编辑器。
- -f --force 强制执行。
到这里并不多就结束了,为了方便大家,最后以附录的形式收集了 git 的一些配置相关的内容。
附录
git 配置
有两个配置每个人都应该设置
git config --global user.name yourname git config --global user.email youremail 复制代码
为了让我们的工作更高效,可以对常用命令设置别名。
git config --global alias.co checkout git config --global alias.ci commit git config --global alias.br branch git config --global alias.st status 复制代码
详细完整说明看这里 git配置
这里是一些配置的说明 自定义git
gitignore
gitignore 文件用于指定 Git 应该忽略的未跟踪文件,已跟踪的文件不受影响
。 gitignore 文件格式的说明在这里 gitignore 格式
记住几个常用的例子,照猫画虎,可以快速解决问题。
*.a 忽略以 .a 结尾的文件 !help.a help.a 文件除外,可以被跟踪 /doc 只忽略根目录下的 doc 文件和文件夹 /doc/ 只忽略根目录下的 doc 文件夹 doc/ 只忽略 doc 文件夹 doc 忽略 doc 文件和文件夹 doc/* 忽略 doc 文件夹的内容,不忽略 doc 文件夹本身 a/*/b * 不能代表 /,不能忽略 a/c/b a/**b ** 可以代表任何内容 复制代码
注意,空目录 git 会自动屏蔽。
引用规范
引用规范用来设置 fetch ,和 push的行为的。
具体请看 引用规范