当你基础入门了git指令后,比如 添加
add
、提交commit
、分支branch
、stash
等等,但等遇到具体一些场景时候,又不太知道从何操作起。本文主要是从使用需求出发,来描述操作过程。
1. 保存工作现场
场景: 线上运行着稳定版本stable1.0,你在QA上开发新版本dev2.0。突然stable1.0出现bug了,你需求切分支过去修改bug。但是,dev2.0的代码不过完整,还不想提交。肿么办?
解决: 这种情况相当于是要在本地保存修改,但有想切换分支(当前分支若有文件修改没有提交,会无法切分支)。这时候我们可以使用git stash
来保存当前工作现场。
具体操作如下:
#在dev2.0分支下,保存工作现场
git stash
#切换到stable1.0去修改bug
git checkout stable1.0
#创建修复分支
git checkout -b stable1.0-bug1
#coding...修改好了之后
git add ./
git commit -m "fix bug1"
#合并修复分支,并删除修复分支
git checkout stable1.0
git merge --no-ff -m "merge fix bug1" stable1.0-bug1
git branch -d stable1.0-bug1
#接下来回到dev2.0,继续愉快的玩耍吧
git checkout dev2.0
#查看保存的工作现场
git stash list
#恢复stash内容
git stash apply
#删除stash内容
git stash drop
#以上的恢复和删除 stash内容 可以合并为 pop指令
git stash pop
#嗯,一切如常,仿佛啥也没发生过一样...
2. 本地更改保存分支替换
场景: 你在分支A愉快的玩耍着,但突然发现这些本地修改出现了点问题,比如改乱了,或者是在研究阶段等等,总之就是不能保存在分支A上面了,可是又想要保留这些修改。于是你想创建一个新分支来放置这些修改,可是现在在分支A上,怎么优雅地挪动这些修改呢?
解决: 这种情况有两种解决方案。
- 直接从分支A创建一个新分支B,然后在新分支B上面进行add和commit即可。
- 利用stash把本地修改保存在当前工作现场,然后在任意分支创建一个新分支B,然后在新分支B下进行stash pop即可。
具体操作如下:
#方法1
git branch -b branch-B
git add ./
git commit -m "some edit..."
#方法2
git stash
git checkout -b branch-B
git stash pop
git add ./
git commit -m "some edit..."
3. 给分支添加描述
场景: 修复bug创建分支,临时创意创建分支,突然想备份个版本又创建分支……(一不小心还忘记删掉无用分支)久而久之,你突然不知道你为什么创建分支,而且这些分支该何去何从呢?这时候你突然想,要是创建分支能像commit那样写个备注啥的就好了。
解决: 给分支添加描述,不是不可能,只是操作方式并和commit不一样。
具体操作如下:
#给当前分支添加描述
git branch --edit-description
#执行上面指令,界面会进入一个编辑界面,可以按【insert】键,然后在里面噼里啪啦写下你的感言
#写完后,【Q】键退出编辑,然后输入":wq!"保存退出。
#查看分支描述
git config branch.<branch>.description
注意:
- 分支描述是保存在
.git/config
下的,是本地存储,所以不能被推送。当删除分支时,对应的分支描述也会一起删除。 - 设置
git config --global branchdesc true
, 就可以将此描述推送到合并提交。即git merge --log<branch>
,分支描述会添加到合并提交消息。(此条规则我还没有测试,你们可以先测测看。)
4. 从指定分支的指定版本中拉取新分支
场景: 你在开发分支B上愉快的玩耍着新版本需求R,但突然间这个新版本需求被拆分成两个小版本R1 R2了,且还在R1中补充了更新的一些需求。但你已经杂糅一起开发了啊?肿么办?
先 git log
查看下提交日志,发现分支B上最新两次commit是R2的,之前都是R1的。
那我们就想,从提交前2次的版本checkout
出新的分支作为R1需求开发分支B1。
解决:
一开始想要 git reset
进行版本回滚,但我们只是要拆分啊,不是要撤销。后来也想到git resvert
操作,但也一样不行。后来重温了下 git branch
语法,才发现其实很简单。
我们熟悉从当前分支创建一个分支的命令,如下:
# 从当前版本新建一个分支
git branch new-branch
这个命令实际上是下面命令的缩写:
git branch new-branch current-branch
也就是说,当我们不指定创建分支的起点,Git默认从当前活动分支开始创建新分支。既然如此,那我们可以通过提供这个起点(commit id),来指定要新分支指向的版本号。
# eg. 假设我们要指向的版本号是169d2dc,这是一个 SHA1 散列值(Hash 值),每次git commit时都会这个id。
git branch new-branch 169d2dc
当然,我们也可以使用 checkout -b
实现创建并检出新分支:
git checkout -b new-branch 169d2dc
拓展:
以下代码,个人没有去试验,大家可以自行试一下。
# 将某个历史版本 checkout 到工作区,但不建议这么做。
git checkout <sha1-of-a-commit>
# 将某个文件的历史版本 checkout 到工作区
git checkout <sha1-of-a-commit> </path/to/your/file>
# 将某个文件的历史版本 checkout 出来,并以一个新的名字保存
git checkout <sha1-of-a-commit>:</path/to/your/file> </new/name/of/the/file>
参考文章:
1.在Git中 Checkout 历史版本
5. 合并分支时,只合并指定文件夹
场景: 我们在开发分支A上面开发者消息模块和详情页模块(两个已经同时在进行了),但突然说消息模块要紧急上线,我们就需要把消息模块剥离到线上分支B。
也就是说,我们希望把A分支上的消息模块代码合并到分支B。
解决:
合并一般采用git merge
,但merge方式会将两个分支的内容完全合并。这时候我们可以巧用 git checkout
。
# 若消息模块是一个文件夹,把分支A的message文件夹检出到当前分支(也就是例子中的B分支)
git checkout A ./message
# 若消息模块是若干个文件夹,把分支A的message相关文件检出到当前分支
git checkout A message.html message.css message.js common.js
注意:在使用git checkout某文件到当前分支时,会将当前分支的对应文件强行覆盖。
文件类型 | git checkout的操作结果 |
---|---|
新增文件 | 直接拉取 |
已有文件的编辑 | 仅保留在A分支上的编辑 |
删除文件 | 不处理 |
比如common.js,B分支上也有修改,在checkout的时候就会将common.js强行覆盖,这样就导致B分支上的修改内容丢失。那如何避免不强制覆盖呢?
非强制覆盖
想要不强制覆盖,我们可以考虑创建一个中间分支B_TEMP,对分支B进行备份,然后把A分支merge
到B_TEMP,在从B_TEMP检出消息模块文件到B分支
# (在分支B)
# 拷贝分支B
git checkout -b B_TEMP
# (在分支B_TEMP)
# 把A分支合并到B_TEMP,这时候在B_TEMP分支里面有消息模块和详情页模块
git merge A
# 切换回分支B(在分支B)
# 再进行从B_TEMP检出
git checkout B_TEMP message.html message.css message.js common.js
总结:其实就是构建一个临时分支,用来保存A/B分支合并(真正的merge
)后结果。之后再去覆盖,就会是合并后代码的覆盖。
参考文章:# git小技巧--如何从其他分支merge个别文件或文件夹
6. 本地分支合并乱套,想恢复远程版本
场景: long long ago,你参与了项目A的开发,但中途被调到了项目B,时隔半年又回来项目A帮忙。这时候你在dev分支(对应测试环境)上面进行开发、提交。现在要发布线上了,你就需要合并dev到master分支。
你的操作如下:
#切到本地master
git checkout master
#拉取master最新代码
git pull
#这时候提示了错误:
#error: unable to create file config/dev.env.js: Permission denied Updating ac7cb80..be4852a
#你习惯性查看下分支状态
git status
#发现有一堆的文件需要add/commit。
#但这些文件都不是你修改的,明明是远程master分支上面的修改啊,为什么要你本地add/commit啊
#无奈之下,你乖乖的 add/commit了
git add ./
git commit -m "无奈之举啊"
#这下分支安静了,我们在来拉取下远程代码
git pull
#结果,一堆的CONFLICT啊!!
#那真是无从处理起了。。。。
解决:
这时候只有一个想法,要么把master分支删掉,要么赶紧回退到合并之前啊。。。。
# 强制恢复到远程master分支的最新版本
git reset --hard origin/master
参考文章:
请教大家一个三联用的 git 命令的意思, git fetch --all && git reset --hard origin/master && git pull:我也没太明白这个三联是啥。。