5.分支合并「git merge」 VS 「git rebase」
Git中,可以使用「git merge」和「git rebase」两个命令来进行分支的合并。
git merge合并分支
合并的方式分为两种:快速合并 和 普通合并,两者的区别在于: 「前者合并后看不出曾经做过合并,而后合并后的历史会有分支记录」 如图所示:
快速合并,默认,快速合并有一个前提:「当前分支的每个提交都在另一个分支中」, Git不创建任何新的commit,只是将当前分支指向合并进来的分支。下面演示下快速合并, 执行git reset 切换到第四次commit,然后执行git merge develop合并master分支。
普通合并,添加**–no-ff**参数表示禁用快速合并。
另外有时会有这样的场景:合并的分支中有很多commit记录是无需在分支中体现的,一个commit 就够了。可以借助**--squash**参数来压缩提交,示例如下:
附:git merge的常用参数:
git merge -ff # 快速合并,默认参数 git merge -ff-only # 只有快速合并的情况才合并 git merge --no-ff # 不使用快速合并 git merge -n 分支名 # 合并分支,不会在合并后显示合并前后的不同状态 git merge -stat 分支名 # 合并分支,合并结束后显示合并前后的不同状态 git merge -e 分支名 # 合并分支,合并前调用编辑器,可自行编写commit
Tips: git-merge除了用来合并分支外,拉取远程仓库更新时也可用到(git fetch + git merge)
git reabse合并分支
rebase(衍合,变基),网上很多教程写得很高深莫测,其实并没有那么复杂, 只是这种合并会让树整洁,易于跟踪。以上面4中的结果为例,先把master分支 和develop分支重置到最新的commit。
先走一波前面的merge合并方式:
接着再试试rebase合并方式:
Git会把每个提交都取消掉,并把他们临时保存为补丁,比如经过一些冲突解决,生成新的commit, 旧的commit会被丢弃,还会被git的gc回收,这样的结果就是一条直线的树。
6.解决合并冲突
在分支的合并的时候,并不是每次都能直接合并的,有时会遇到合并冲突,特别是在多人协作的时候。 出现合并冲突后,需要解决完冲突,才能继续合并。
举个简单的例子,A和B在master分支上开辟出两个分支来完成相关的功能, A做完了,把自己的分支合并到master分支,此时master分支向前移动了几次commit, 接着B也完成了他的功能,想把自己分支合并到master分支,如果改动的文件和和A改动 的文件相同的话,此时就会合并失败,然后需要处理完冲突,才能够继续合并!
接下来我们来简单的模拟合并冲突,先来试试merge:
merge分支后处理冲突
如图,合并完A分支后合并B分支出现了冲突,接着键入:git status查看冲突的文件:
可以看到未合并的两个文件,1.txt和2.txt,打开其中一个文件:
<<< 和 >>>包裹着的就是冲突内容,保留自己想要的内容,处理完后删掉<<<和>>>,修改完后:
2.txt文件也如法炮制,接着add,然后commit即可,合并结束。
rebase分支后处理冲突
如图,A合并成功,在合并B的时候,出现了合并冲突,有三个可选的操作:
git rebase --continue # 处理完冲突后,继续处理下一个补丁 git rebase --abort # 放弃所有的冲突处理,恢复rebase前的情况 git rebase --skip # 跳过当前的补丁,处理下一个补丁,不建议使用,补丁部分的commit会丢失!
键入git status查看冲突文件:
接着处理1.txt文件中的冲突,解决完成后,先键入git add,接着键入git rebase --continue处理下一个冲突:
处理接下来的冲突,直到没有冲突为止:
可以看到使用rebase合并,最后的分支线是一条直线。另外,使用rebase合并中途出差错, 可以使用git rebase --abort恢复rebase前的状态。
7.删除分支
合并完的分支,基本没什么用了,可以使用下述命令删除:
git branch -d 分支名 # 删除分支,分支上有未提交更改是不能删除的 git branch -D 分支名 # 强行删除分支,尽管这个分支上有未提交的更改
8.恢复误删分支
两步:找出被删分支最新的commit的Hash值,然后恢复分支:
git log --branches="被删除的分支名" # 找到被删分支最新的commit版本号 git branch 分支名 版本号(前七位即可) # 恢复被删分支
9.切换分支时暂存未commit的更改「git stash」
有时我们可能在某个分支上正编写着代码,然后有一些突发的情况,需要 我们暂时切换到 其他分支上,比如要紧急修复bug,或者切换分支给同事 review代码,此时如果直接切换 分支是会提示切换失败的,因为这个分支 上做的更改还没有提交,你可以直接add后commit, 然后再切换,不过我们习惯写完某个功能再提交,我们想:
先暂存这个分支上的改动,切去其他分支上搞完事,然后回来继续 继续在之前的改动上写代码。
那么可以使用:
git stash # 保存当前的改动
然后放心的切换分支,然后再切换回来,接着使用:
git stash apply # 恢复保存改动
另外有一点一定要注意!!!可以stash多个改动!!如果你切换到另一个分支 又stash了,然后切换回来stash apply是恢复成另一个分支的stash!!! 如果你这样stash了多次的话,我建议你先键入:
git stash list # 查看stash列表
找到自己想恢复的那个
比如这里恢复的应该是master上的stash,可以使用下述命令进行恢复:
git stash apply stash@{1}
10.分支重命名
git branch -m 老分支名 新分支名 # 分支重命名
11.把提交的commit从一个分支放到另一个分支「git cherry-pick」
有时我们可能需要把某个分支上的一次commit放到另一个分支上,此时可以使用git cherry-pick, 比如下面这样两个分支:
master分支:A -> B -> C feature分支:a -> b
现在想把feature分支上的b,放到master的后,可以这样操作:
- Step 1:切换到feature分支上,git log拿到b commit的版本号(SHA1)。
- Step 2:切换到master分支,键入:git cherry-pick 版本号。
0x5、Git远程仓库
1.远程仓库概述
在实际开发过程中,基本都是团队协作的形式进行,即多人一起负责同一个项目,那如何共享同一份代码并进行管理呢?可以用到「Git远程仓库」。可以自己搭建,或选择专业的代码托管平台,比如:Github,Git@OSC,GitCafe,GitLab,coding.net,gitc,BitBucket,Geakit,Douban CODE 等。当然,如果有条件的话,肯定是自己搭建的爽一些,可控,还可以做一些订制(集成编译,机器人提醒等),简单点的可以试试「Gogs」,可玩性更高的可以试试「GitLab」。
2.本地仓库与远程仓库建立关联「git remote」
在Github上新建了一个项目仓库,会生成对应的仓库链接,如:
键入下述命令进行关联:
git remote add origin 远程仓库地址
接着可键入下述命令查看关联情况:
git remote # 列出已经存在的远程分支 git remote -v # 查看远程仓库的地址
3.推送本地仓库到远程仓库「git push」
建立完关联后,我们可以使用git push命令把本地更改推送到远程仓库
git push -u origin master
-u参数:作为第一次提交使用,作用是把本地master分支和远程master分支关联起来(设置默认远程主机),后续提交不需要这个参数!
另外,如果想修改远程仓库地址,可通过下述命令:
# 直接修改远程仓库地址 git remote set-url origin 远程仓库地址 # 也可以先删除origin后再添加 git remote rm origin # 删除仓库关联 git remote add origin 远程仓库地址 # 添加仓库关联
你还可以直接修改「.git文件夹中的config文件」,直接替换圈住位置内容即可:
还有一点:「origin」并不是固定的东西,只是后面「仓库地址的一个别名」!!可以写成其他的东西,然后你也可以设置多个仓库关联,用不同的别名标志,比如:
git remote add github https://github.com/coder-pig/SimpleTea.git git remote add osc git@git.oschina.net:coder-pig/SimpleTea.git
4.克隆远程仓库「git clone」
把项目推送到远程仓库后,其他开发者就可以通过git clone命令把项目克隆到本地
git clone 仓库地址 # 克隆项目到当前文件夹下 git clone 仓库地址 目录名 # 克隆项目到特定目录下 # 注:git clone命令只会建立master分支,如果想克隆特定远程分支,可在克隆后: git checkout -t origin/dev # 该命令等同于 git checkout -b dev origin/dev # 除此之外,还可以: git fetch origin 远程分支:本地分支 # 会在本地新建分支,但不会自动切换,还需checkout git branch --set-upstream 本地分支 远程分支 # 建立本地分支与远程分支的链接
5.同步远程仓库更新「git fetch」VS 「git pull」
获取远程仓库更新的方法有两种:fetch 和 pull,简要讲解下两者的区别:
git fetch
仅仅只是从远处服务器获取到最新版本到本地,假如你不去合并(merge),本地工作空间是不会发生变化的!比如:在Github上创建一个README.md文件,然后调 git fetch 去获取远程仓库的更新。
git pull
一步到位,pull = fetch + merge,比如:同样修改Github上的README.md 文件,然后git pull 同步远程仓库的更新:
区别显而易见,使用git fetch会更安全一些,毕竟merge的时候,查看更新的情况,再决定是否进行合并。
6.git push 时的unrelated history问题
在Github创建新项目后,在repo处创建了README.md或其他文件,然后关联本地仓库,push时会报错: Push rejected: Push to origin/master was rejected,然后提示你pull一下,当你pull时又会报错: 「refusing to merge unrelated histories」,原因是两个仓库不同导致的,可使用下述命令解决:
git pull origin master --allow-unrelated-histories
除此之外,你还可以粗暴一点,直接用本地仓库「强制覆盖远程仓库」,但是 慎用!!!如果出问题了,只能看下其他人的电脑中是否有原始的本地仓库进行还原!!!
git push -f origin # 慎用!!!
7.SSH Key避免每次push重复输入账号密码
私有项目,使用Https协议pull或push,都需要验证账号和密码,有点繁琐,如果想避免这种重复输入的情况,可以考虑使用SSH协议。SSH,Secureshell(安全外壳协议),专为远程登陆会话与其他网络服务提供安全性的协议,而SSH传输的数据是可以经过压缩的,可以加快传输的速度,出于安全性与速度,优先考虑使用SSH协议,而SSH的安全验证规则又分为基于密码和基于密钥两种!这里使用的是第二种,即在本地创建一对密钥「公钥(id_rsa.pub)和私钥(id_rsa)」然后把公钥内容贴到远程仓库设置中的ssh keys中,从而建立本地与远程的认证关系。配置SSH Key的流程如下:
- ① 来到电脑的根目录下(假设还没创建过SSH key):
执行完ssh-keygen那个指令后,后面依次要你输入文件名直接回车 → 会生成两个默认的秘钥文件,接着提示输入密码,直接回车 → 如果这里你输入密码了的话,那么push的时候你还是需要输入密码,接着又输多一次密码直接回车 → 出现最下面的这串东西就说明ssh key已经创建成功了! 接着可以用编辑器打开id_rsa.pub文件或者键入下述命令复制内容:
clip <id_rsa.pub
打开Github,点击头像,选择:Settings,然后点击左侧SSH Keys,然后New SSH Key
然后Github会给你发来一个提示创建了一个新ssh key的邮件,无视就好,接下来我们可以键入:
**ssh -T git@github.com**
然后如果上面设置过密码则需要输入密码,否则直接输入yes然后一直按回车就好!,最后出现Hi xxx那句话就说明ssh key配置成功了!
其他远程仓库配置方法类似,另外如果想一个电脑管理多个SSH-Key,可移步至:
0x6、Git工作流
关于Git工作流,Github上有一篇图文并茂写得很好的文章,就不细说了,只是简单介绍下,更多详情可见:《Git Workflows and Tutorials》
1.集中式工作流
类似于SVN,不过只有一条master分支,然后一群人就在这条分支上嗨,比如有小A和小B:
- 1.项目管理者初始化仓库,然后推到远程仓库
- 2.其他人克隆远程仓库项目到本地
- 3.小A和小B完成各自的工作
- 4.小A先完成了,git push origin master 把代码推送到远程仓库
- 5.小B后完成了,此时推送代码到远程仓库,出现文件修改冲突
- 6.小B需要先解决冲突,git pull –rebase origin master,然后rebase慢慢玩
- 7.小B把冲突解决后,git push origin master 把代码推送到远程仓库
2.功能分支工作流
和集中式分部流相比只是分支再不是只有master,而是根据功能开辟新的分支而已,示例如下:
- 1.小A要开发新功能,git branch -b new-feature 开辟新分支
- 2.小A在new-feature上新功能相关的编写,他可以这个分支推到远程仓库
- 3.功能完成后,发起请求pull request(合并请求),把new-feature合并到master分支
- 4.仓库管理员可以看到小A的更改,可以进行一些评注,让小A做某些更改, 然后再发起pull request,或者把pull request拉到本地自行修改。
- 5.仓库管理员觉得可以了,合并分支到master上,然后把new-feature分支删掉
注:这里的仓库管理者是拥有仓库管理权限的人
3.Gitflow工作流
其实就是功能分支工作流做了一些规范而已,大概流程参见上面「一个简单的分支管理策略」
4.Forking工作流
分布式工作流,每个开发者都拥有自己独立的仓库,为开源项目贡献代码常用,把项目fork到自己的远程仓库,完成相应更改,然后pull request到源仓库,源仓库管理者可以决定是否合并。
5.Pull Request工作流
和Forking工作流类似,Pull Requests是Bitbucket上方便开发者之间协作的功能
0x7、其他杂项
1.为开源项目贡献代码
你可以Clone别人的开源项目,在看别人代码的时候,觉得作者某些地方写得不好,写错,或者你有更好的想法,在本地修改后,想把修改push推送到开源项目上,是无法直接Push推送更改的。参与开源项目的方式有两种:
- 方法一: 是让作者把你加为写作者,添加协作者流程: 点击仓库的Settings → Collaborators 然后输入想添加的人的用户名或者邮箱,点击 添加即可。
- 方法二: 点击Fork按钮,把这个项目fork到自己的账号下,然后Clone到本地,然后做你想做的修改,commit提交,然后push到自己账号里的仓库,然后打开开源项目,点击,然后新建一个「pull request」,接着设置自己的仓库为源仓库,设置源分支,目标仓库与目标分支,然后还有pull request的标题和描述信息,填写完毕后,确定。 这个时候开源项目的作者就会收到一个pullrequest的请求,由他来进行审核,作者审查完代码觉得没问题的话,他可以点击一下merge按钮即可将这个pull request合并到自己的项目中,假如作者发现了你代码中还有些bug,他可以通过Pull Request跟你说明,要修复了xxBUG才允许合并,那么你再修改下BUG,提交,更改后的提交会进入Pull Request,然后作者再审核这样!
Tips:o(╯□╰)o假如作者不关闭或者merge你的这个Pull Request,你可以一直commit骚扰主项目…
2.SourceTree使用详解
命令行虽酷炫可装逼,但是有时用图形化工具还是能提高不少效率的,安利个巨好用的Git图形化工具SourceTree,官网下载地址:www.sourcetreeapp.com/,网上教程满天飞,笔者也不粘贴复制了,找到个写得还行的,有兴趣可移步至:《用SourceTree轻松Git项目图解》。
参考文献与更多Git学习资料: