git rebase 合并多个提交
在自己的开发分支上,经常会产生多个无意义或者不需要后续查看的提交(比如添加日志或者添加测试代码的提交)。在后续将开发分支合并到主分支上,这些提交也会被合并到主分支的提交日志上,这会造成 git 提交信息的冗余,不利于后续提交日志的查看。
因此,可以使用 git rebase 将多个提交合并到一个提交。
下面结合一个具体的实例进行说明。
git log --pretty=oneline查看提交日志(此处对commitID仅给出前6位)
lbsMacBook-Pro:testgit lbs$ git log --pretty=oneline
2e12b3 (HEAD -> master) commit 5
fe134d commit 4
18faed commit 3
44e9df commit 2
efdcfc init 1
- 下面将
commit 5,commit 4和commit 3这三个提交合并为一个提交,使用git rebase -i进行操作
//推荐使用该方式
//语法:git rebase -i startPoint
//合并 (startPoint, head] 区间的提交,区间左开右闭
//合并的提交不包括 startPoint 对应的提交,即区间左侧为开区间
git rebase -i 44e9df
//语法:git rebase -i startPoint endPoint
//合并 (startPoint, endPoint] 区间的提交,区间左开右闭
//合并的提交不包括 startPoint 对应的提交,即区间左侧为开区间
//endPoint可以省略,若省略,则默认endPoint为head,即当前的提交
git rebase -i 44e9df 2e12b3
// git rebase -i HEAD~count
// 合并从head当前提交开始的count个提交
git rebase -i HEAD~3
- 执行完上述命令,会看到下面的信息
pick 2e12b3 commit 3
pick fe134d commit 4
pick 18faed commit 5 #head 在最后 时间从前到后
# Rebase xxxxxx.......
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
- 提交记录是按照时间顺序排列的,我们需要后面的提交合并到的区间的第一个提交,即将非首个提交的
pick改为squash或者s,如下修改示例
pick 2e12b3 commit 3
s fe134d commit 4
s 18faed commit 5 #head 在最后 时间从前到后
- 退出上述编辑区间,会看到如下信息,在最上面输入合并提交之后,需要展示的提交信息
# This is a combination of 3 commits.
# This is the 1st commit message:
commit 3
# This is the commit message #2:
commit 4
# This is the commit message #3:
commit 5
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
"~/Desktop/testgit/.git/COMMIT_EDITMSG" 36L, 664C
我们在最上面添加需要展示的提交信息
squash-test-1
# This is a combination of 3 commits.
# This is the 1st commit message:
commit 3
...
- 保存上述编辑,会看到合并成功的提示
Date: Sat Jun 13 22:52:38 2020 +0800
1 file changed, 3 insertions(+), 2 deletion(-)
Successfully rebased and updated refs/heads/master.
- 最后查看下提交记录
lbsMacBook-Pro:testgit lbs$ git log --pretty=oneline
5fed13 (HEAD -> master) squash-test-1
44e9df commit 2
efdcfc init 1
可以看到,commit 5, commit 4 和 commit 3 这 3 个提交,已经被合并为一个新的提交了,提交信息为 squash-test-1。 使用 git log 可以看到 squash-test-1 更详细的信息
lbsMacBook-Pro:testgit lbs$ git log
commit 5fed13 (HEAD -> master)
Author: lbs0912 <lbs1203940926@163.com>
Date: Sat Jun 13 22:52:38 2020 +0800
squash-test-1
commit 3
commit 4
commit 5
...
- 需要注意的是,如果这些多个提交已经推送到了远端,执行完上述操作,会出现如下情况。
ZBMAC-b286c5fb6:MySeckillApp liubaoshuai1$ git status
On branch master
Your branch and 'origin/master' have diverged,
and have 1 and 3 different commits each, respectively.
(use "git pull" to merge the remote branch into yours)
nothing to commit, working tree clean
参考 git合并多个远程commit并提交 | CSDN 解决该问题。
执行 git push -f 将本地提交强制提交到远端,但前提是保证不会覆盖掉远程其他的提交。
git push -f
git本地仓库推送到远端
git remote add origin git@github.com:lbs0912/my-spring-boot-app.git
git push -u origin master
//-u 表示在推送的同时,将 origin 仓库的 master 分支设置为本地仓库当前分支的 upstream
本地更新远程分支列表
当远程有新的分支时,本地不会自动获取到新分支。可以执行下述代码获取远程新的分支列表。
git remote update origin --prune
//or
git remote update origin -p
创建本地分支跟踪远程分支
git branch -a # 查看远程分支
git checkout -b localBranch remoteBranch
git branch # 检查分支切换效果
- 需要说明的是,在检出远程分支时,使用远程分支名
remotes/origin/xxx或origin/xxx均可
# 以下举例说明
> git branch -a
remotes/origin/master
remotes/origin/dev
remotes/origin/feature-1
> git checkout -b dev origin/dev
> git checkout -b master remotes/origin/master
此处,以创建一个本地分支 features/dev_02 ,并创建一个远程同名分支,最后进行关联为例进行说明。
//基于远程master分支创建一个本地开发分支
git checkout -b features/dev_02 origin/master
//本地分支关联远程同名分支(若远程分支不存在,则会创建)
git push --set-upstream origin features/dev_02
//后续推送代码
git push
git push origin features/dev_02
删除分支
- 删除本地
dev分支
git branch -d <name>
- 强制删除本地
dev分支。比如,若分支未合并,直接删除会提示该分支还未合并,删除会造成代码丢失,此时若执意删除分支,可以使用-D进行强制删除
git branch -D <name>
- 删除远程
dev分支
git push origin --delete dev
git代理
- 查看当前代理状态
git config --global http.proxy
git config --global https.proxy
- 命令行git代理
git config --global http.proxy 'socks5://127.0.0.1:1086'
git config --global https.proxy 'socks5://127.0.0.1:1086'
- 取消代理
git config --global --unset http.proxy
git config --global --unset https.proxy
git: 'credential-wincred' is not a git command
The problem is that you're trying to use the wincred credential helper, which is only available on Windows, on macOS. You mentioned that you've run git config --global credential.helper wincred, which sets the credential helper to wincred. When Git tries to invoke it, it fails because it's unavailable.
Instead, you should run git config --unset-all credential.helper and run git config --global credential.helper osxkeychain. That will result in you using the macOS Keychain for storing credentials, which will work on macOS.
git config --global --unset-all credential.helper
git config --global credential.helper osxkeychain
git reflog 解决提交代码丢失 detached-head
reflog 是 Git 操作的一道安全保障,它能够记录几乎所有本地仓库的改变。包括所有分支 commit 提交,已经删除(其实并未被实际删除)commit 都会被记录。总结而言,只要 HEAD 发生变化,就可以通过 reflog 查看到。
detached-head 代码丢失找回
- git提交到HEAD detached导致代码丢失
- StackOverflow - gitx How do I get my 'Detached HEAD' commits back into master
背景
日常开发中,切换分支误操作,造成本地代码修改丢失。
此时,可以借助 git reflog 找回丢失的代码修改。
丢失产生原因和步骤
首先在 master 分支上开发,此时线上出现 bug 且回到旧版本的 tag。这时 master 分支上有一部分代码修改但未提交。
在 master 分支上执行 git status,有未提交的代码,如下图所示
在 master 分支上执行 git tag查看标签信息,如下图所示
此时有未提交的代码,然后执行 git checkout v1.0
这个时候,提示当前分支为 detached HEAD
然后再执行 git add ./git commit 和 git checkout master,切换回 master 分支。这个时候发现 detached HEAD 分支不见了,master 分支上未提交的代码也不见了。
代码找回
执行 git reflog 查看提交记录
查找对应提交的 commitId 为 247e11b,然后执行下述命令行,找回丢失的代码
git checkout 247e11b //检出对应的提交
git checkout -b diff //新建一个新的diff分支
git checkout master //切换到master分支
git merge diff //将新建的diff分支合并到master分支
删除所有历史提交记录
此处介绍如何删除所有历史提交记录,形成一个全新的仓库。
- 1 - Checkout
git checkout --orphan new_branch
- 2 - Add all the files
git add -A
//等效于 git add --all 或 git add .
git add中使用参数-A或--all表示追踪所有操作,包含新增、修改和删除Git 2.0版开始,
-A参数为默认参数,即git add .等效于git add -A或git add --all
- 3 - Commit the changes
git commit -am "commit message"
- 4 - Delete the branch
git branch -D master //同时删除本地和远程分支
- 5 - Rename the current branch to master
git branch -m master
- 6 - force update your repository
git push -f origin master
下面对上述步骤进行说明
git checkout --orphan
如果你的某个分支上积累了无数次的无意义的提交,git log 信息满天飞,那么可以使用 git checkout --orphan <new_branch_name>
- 基于当前分支创建一个新的“孤儿(
orphan)”的分支,没有任何提交历史,但包含当前分支所有内容 - 执行上述命令后,工作区(
Workspace)中所有文件均被认为在该操作中新增(git statue查看状态,所有文件状态均为new file,如下图所示),此时执行git add .会把所有文件添加到缓存区(Index)
- 严格意义上说,执行
git checkout --orphan <new_branch_name>后,创建的并不是一个分支,因为此时HEAD指向的引用中没有commit值。只有在进行一次提交后,它才算得上真正的分支。
orphan译为“孤儿”,该参数表示创建一个孤立的分支,没有任何提交历史,且与当前分支不存在任何关系(查看提交信息,可发现其为一个孤立的点,如下图所示)孤儿(
orphan)无父辈信息,同理,创建的分支也不包含任何历史提交信息
git commit -am
git branch -m
重命名
git push -f origin master
git项目协作——保证git信息简洁
同一分支 git pull 使用 rebase
默认情况下,git pull 使用的是 merge 行为。多人协作开发时,会产生不必要的 merge 提交记录,造成提交链混乱不堪。
推荐在同一个分支更新代码时,使用 git pull --rebase。
# 为某个分支单独设置,这里是设置 dev 分支
git config branch.dev.rebase true
# 全局设置,所有的分支 git pull 均使用 --rebase
git config --global pull.rebase true
git config --global branch.autoSetupRebase always
分支合并使用 merge --no-ff
Usage
Fast-Forward:当前分支合并到另一分支时,如果没有冲突要解决,就会直接移动文件指针,并且不会产生合并提交记录。该过程中,存在git文件指针快速移动, 因此该过程称为Fast-Forward。--no-ff(no fast foward):每一次的合并,都会创建一个新的commit记录。使用--no-ff,可以保持原有分支提交链的完整性,并且当该分支被删除时,提交信息依旧存在。
结合上图分析,在 dev(绿色) 分支上检出 feature-1 分支(蓝色),且 dev 分支不进行任何提交
- 直接
merge,默认采用Fast-Forward,两个分支的提交链会合并为一条直线,不利于后期代码审查和维护 - 使用
git merge --no-ff feature-1合并代码,会产生一个新的提交,且两个分支的提交链不会重叠,利于后期代码审查和维护
merge 默认设置
git merge 默认使用 fast-forward,可以通过如下方式,修改为默认使用 --no-ff。
git config --global merge.commit no
git config --global merge.ff no
此外,SourceTree 在设置中也可以设置 --no-ff。
git clone 设置缓存区
当工程较大时,使用 git clone 拉取代码,可能会出现 early EOF 的报错或者拉取代码失败。
这是因为 git clone 本质上是建立一个 HTTP 连接,工程较大时会超过默认设置的缓存大小。
使用 git config --list 查看 http.postbuffer 的大小,确认是否小于下载的工程大小。
使用 git config --global http.postbuffer 524288000 //500x1024x1024 设置为500M 可以对缓存区大小进行设置。
git clean
git clean 用于清理 workspace 中未被 git 版本控制的文件,比如临时文件,构建出来的二进制文件。其附加参数如下
-f: 删除未跟踪的文件-d: 删除未跟踪的目录-x: 删除.gitignore已经设置不跟踪的文件-n: 显示要删除的文件和目录列表(-n并不实际执行删除操作,只显示出将被清理的文件列表)
# 删除 untracked files
git clean -f
# 连 untracked 的目录也一起删掉
git clean -fd
# 连 gitignore 的untrack 文件/目录也一起删掉 (慎用,一般这个是用来删掉编译出来的 .o之类的文件用的)
git clean -fdx
# 在用上述 git clean 前,墙裂建议加上 -n 参数来先看看会删掉哪些文件,防止重要文件被误删
git clean -fdx -n
git clean -f -n
git clean -fd -n
为了避免文件误删,建议在使用 git clean 前先使用 -n 查看将要删除的文件列表,确认无误后再删除文件,如下示例。
# git clean -fd -n
ZBMAC-b286c5fb6:JavaTest liubaoshuai1$ git clean -fd -n
Would remove .idea/codeStyles/
Would remove .idea/sbt.xml
Would remove out/production/JavaTest/com/lbs0912/java/demo/FutureTaskExample.class
Would remove out/production/JavaTest/com/lbs0912/java/demo/FutureTaskForMultiCompute$ComputeTask.class
# git clean -fd
ZBMAC-b286c5fb6:JavaTest liubaoshuai1$ git clean -fd
Removing .idea/codeStyles/
Removing .idea/sbt.xml
Removing out/production/JavaTest/com/lbs0912/java/demo/FutureTaskExample.class
Removing out/production/JavaTest/com/lbs0912/java/demo/FutureTaskForMultiCompute$ComputeTask.class
git tag
git tag: 显示所有标签git tag -l 'v1.0.*': 用通配符查看符合筛选条件的标签git show xxx: 查看标签信息(提交者,邮箱等)git tag xxx: 创建轻量标签git tag -a xxx: 创建含有附注的标签git tag -a xxx -m 'xxxx': 创建含有附注的标签,并附加提交信息(默认标签打到当前Head提交状态)git tag -a xxx -m 'xxxx' \<commitID>: 创建补丁标签,即对之前的提交添加标签git tag -d xxx: 删除本地标签git push origin --delete tag <tagname>: 删除远程标签
需要注意的是,
Git 使用的标签有 2 种类型:轻量级的(lightweight)和含附注的(annotated)。 —— git tag | Doc
- 轻量级标签就像是个不会变化的分支,实际上它就是个指向特定提交对象的引用。
- 含附注标签,实际上是存储在仓库中的一个独立对象,它有自身的校验和信息,包含着标签的名字,电子邮件地址和日期,以及标签说明,标签本身也允许使用
GNU Privacy Guard(GPG) 来签署或验证。
一般我们都建议使用含附注型的标签,以便保留相关信息;当然,如果只是临时性加注标签,或者不需要旁注额外信息,用轻量级标签也没问题。
cherry-pick
使用场景
cherry 译为樱桃,pick 译挑选。git cherry-pick 即选择某一个分支中的一个或几个提交,合并到其他分支中(选择的提交即所需的樱桃),主要使用场景为
- 情况1: 把弄错分支的提交移动到正确的分支上
- 情况2: 将其他分支的提交添加到当前分支
Demo
假设工程有个稳定版分支 v2.0,还有个开发版分支 v3.0。开发分支还未彻底完成,不能直接把两个分支合并,这样会导致稳定版本混乱,但是又想增加一个 v3.0 中的功能到 v2.0 中,这里就可以使用 cherry-pick 了。
// 先在v3.0中查看要合并的commit的commit id
git log
// 假设是 commit f79b0b1ffe445cab6e531260743fa4e08fb4048b
// 切到v2.0中
git checkout v2.0
// 合并commit
git cherry-pick f79b0b1ffe445cab6e531260743fa4e08fb4048b
git cherry-pick -x f79b0b1ffe445cab6e531260743fa4e08fb4048b //表示保留原提交的作者信息进行提交
语法
git cherry-pick commitID:将其他分支的commitID提交合并到当前分支git cherry-pick -x commitID:将其他分支的commitID提交合并到当前分支,-x表示保留原提交的作者信息进行提交git cherry-pick <start-commit-id>…<end-commit-id>: 该功能在Git 1.7.2 版本后才支持,将一个连续区间范围的提交,合并到到当前分支。提交范围区间左开右闭,即(start, end]git cherry-pick <start-commit-id>^ … <end-commit-id>: 同上,使用^表示包含start-commit-id,即[start, end]
JetBranins 系列IDE,内置了git cherry-pick 快捷键(樱桃图标)
在 SourceTree 中,选中要 cherry-pick 的提交,鼠标右键的菜单栏中有对应的 cherry-pick 操作(中文译名为“遴选”)。