万字总结!全方面git 操作!

·  阅读 265
序:只总结实用的git法i,涉及到原理的部分请自行查阅加深印象。
很多同学可能会觉得,网上的git教程铺天盖地,再多添加一篇的意义何在?最初的想法我也是这样的,遇到不懂的百度一下就有了。可是过了一段自己就又忘了,git难嘛?我觉得算不上难,只是不常用忘记了或者对于一些操作记得熟练而混淆了操作。这正是写下这篇千字长文的目的,搜集汇总关于git的操作讲解,同时结合自身在工作开发中遇到的问题场景进行总结。同时,一直都认为,自己会,自己能讲出来,自己讲出来能让别人懂,这是不断提升自己的三个阶段,所以写下这篇总结也是自己对git学习一次知识巩固。文中不足之处,还请帮忙指正。

一、基础篇

先写的高级篇,后来觉得做事有头有尾,还是的补充下一下基础篇,突然觉得基础篇有点写不下去了,不知从何开始。

如上图,我们可以简单的把 git 的存储仓库分为 本地+三个区:
git add . 后的index暂存区进行git commit 后的 带HEAD指针记录的缓存区git push 后的远端仓库
复制代码
repository和index的区域,都是存放在本地 .git文件下 ,push后存放在远端。
了解了上面的概念后,我们从一个项目的0到1,来讲解整个的git 操作

1. 今天我接手了一个项目

接手一个项目后,我们可以进行git clone操作拉取远端代码并将远端分支关联起来
git clone 我是远端仓库地址
这里有一个问题,如果是全新的一个项目,以vue为例,通过vue/cli创建了项目,怎么关联远程分支:
附上配置git密钥的链接,没啥讲解的,随便参考一个

juejin.cn/post/684490…

git remote add origin 新建的仓库的地址
git push -u origin master
复制代码
这样本地的新建项目就推送并关联远端仓库了

2. 开发一个新功能模块

在拉取项目后,我们并不能再主分支上开发,因为主分支是所有共享的,也不符合现在主流的 Feature Branching 工作流。
什么是 Feature Branching 工作流?它的概念很简单:
每个新功能都新建一个 branch 来写;
写完以后,把代码分享给同事看;
写的过程中,也可以分享给同事讨论分支确定可以合并后,把分支合并到 master ,并删除分支。
复制代码
了解了feature branching概念后,开始我们的新功能开发,创建一个新的分支,基于主分支进行创建(条件有限,手动模拟(猛狗哭泣.git))
执行 git status 
// 当前分支为master分支,主分支名根据团队自己定义,
// git status查看状态,确保是基于主分支创建
复制代码
执行 git checkout -b feature/user   
 // 在本地创建了一个名叫feature/user的开发分支,并自动切换到了feature/user
复制代码
执行 git status
// 当前分支为feature/user
复制代码
开发中...开发了一天,愉快的下班,将代码推送到远端分支,防止代码丢失
git add .
git commit -m '描述' //基础阶段就不强调优雅了
git push -u origin feature/user
复制代码
继续开发...完成后,
需要将自己分组的代码
合并到mater分支,并推送主分支到远端
git status//当前分支为feature/user
git add .
git commit -m '描述'
git push
// 将本地feature/user推送到了远端feature/user,
// 因为前面 git push -u origin feature/user已经关联了,
// 可以直接git push​//此处基础篇,就不推荐使用rebase变基操作,
// 不太熟练还是建议merge操作,高级篇后再来变基!!
git checkout matser //切换分支
git pull // 更新主分支,同事可能提了代码
git merge feature/user// 有冲突解决冲突,推荐使用可视化工具,vscode也行
git push
复制代码

若测试后功能有bug, 有更改,再切换回feature/user分支,重复上面的操作就行

3. 我的分支功能上线了

项目上线后,我们的开发分支已经合并主分支了就不要保留了
清除远端分支,用一个空去覆盖
git status
// 当前分支分 feature/user
git push origin :feature/user 
// :前为空格
复制代码
再清除本地分支,先切换到其他的分支,才可以删除本地分支
git status
// 当前分支分 feature/user
git branch -a 
//查看所有分支
git checkout dev //随便切换到一个分支
git branch -d feature/user
// 这个时候会提示删除不了,原因是因为合并过mater分支,将 -d 改成 -D
git branch -D feature/user
复制代码
至此一个功能的开发到上线,全部结束。
1. git checkout -b feature/user
// 创建并切换到feature/user开发
2. git add .
3. git commit -m '描述'
4. git push -u origin feature/user //第一次远程提交
5. git add .
6. git commit -m '描述'
7. git push
// 合并到主分支的代码
8. git checkout master
9. git pull
10. git merge feature/user
11. git push
复制代码

4. 我的分支删错了

这部分本来应该放到下面去讲的,但这部分比较重要和前面连贯起来,且比较简单,就提前讲了
远端分支删除错了,我们只要再次推送到远端就行
git push origin -u feature/user
复制代码
本地分支删错了,且此时远端的也被删除了,例如执行下面:
git branch -d feature/user
或者 git branch -D feature /user
复制代码
恢复本地分支
git reflog
复制代码

找到原来的分支的最后的一次commit提交记录

git checkout -b 分支名 分支commit_id
复制代码
例如: git checkout -b test-reset 10009ec
复制代码
举一反三:

也可以通过此操作对分支进行重命名

5. 补充和遗留问题

在了解上面的基本操作后,我们在工作中,多人开发的情况并不会按照我们的预期进行。
我们的理想状态是:每次创建一个分支开发一个新功能,开发完再放到主分支中去。
现实的情况是:
小明同学,我修改了一下项目配置,你拉下代码?	
小明同学,我们的共用代码部分我改了下,你拉下代码?	
小明同学...更新下代码?
...
复制代码
因为各种的原因,我们在feature/user分支上开发的时候,需要拉取别的同学的代码, 即拉取新的代码到自己本地分支
第一种操作:
git status // 当前分支为 feature/user
git checkout master
git pullgit checkout feature/user
git merge master
复制代码
第二种操作:
git status // 当前分支为 feature/user 
git pull origin matser 
// 在 本地feature/user分支上拉取远端master的代码 
// 在后面高级篇会讲到 git pull的本质
复制代码
两种操作都可以实现我们的目的,没有那个一定好,个人喜欢第一种操作
至此基础篇的内容全部完成,不出现任何意外的情况下,我们能顺利的完成项目的开发。
然后实际并不会一直向着我们期待的方向发展

例如:

merge后,我的代码丢失了?
分支树,在master和feature/user上不停的来回交叉所带来的问题
代码提交错了?等等...
复制代码
带着这些问题,我们进入高级篇

二、高级篇

1. 未曾了解的git merge

git merge feature 是我们最常用的合并命令,在开发中从a merge到b, 再从b merge到a , merge来merge去,代码就突然不见了。因为对git merge 的不了解,所以对于出现的问题也不能很好的解决。
git merge 的原理是什么?在执行git merge的时候,具体的进行了什么样的操作?
git merge 的时候,在内部进行的是“三向合并”,详细可参考
[http://blog.plasticscm.com/2016/02/three-way-merging-look-under-hood.html](http://blog.plasticscm.com/2016/02/three-way-merging-look-under-hood.html)
简单的来说,合并两个文件的时候,先找到这两个文件的一个共同祖先作为合并的base

git合并策略:

在执行合并的时候,默认git会自动挑选合适的合并策略。了解git合并策略的原理可以对git合并结果有一个准确的预期。
git 常见的策略:Fast-forward、Recursive 、Ours、Theirs、Octopus
复制代码
关于git策略的详情可以参考:
[https://zhuanlan.zhihu.com/p/192972614](https://zhuanlan.zhihu.com/p/192972614)
扩展
参考:[https://www.jianshu.com/p/684a8ae9dcf1](https://www.jianshu.com/p/684a8ae9dcf1)
merge的集中形式: 
- 普通的merge : git merge 
- rebase merge : 即 git rebase,后面详细讲解变基 
- squash merge : git merge --squash
复制代码
此处,主要来对比git merge 和git merge squash 的区别和适用场景 :
默认的git merge会保留全部的提交的记录,适用于 自己的分支独立部分,保留自己的每一次的提交记录;
而git merge --squash 适用于:当我们新建出一个feature/dev的开发分支,开发完成后,将分支合并到主分支。在主分支上,只需要记录这个分支功能的添加,而不需要记录每一次的commit记录。

操作详情:

git checkout master
git merge --squash feature/dev
git add .
git commit - m '新的描述内容'
// 新的描述内容,会替代feature/dev中的所有commit记录
复制代码
在实际的开发中,我们通常是,结合git rebase 和git merge --squash dev以及git commit --amend 来修改和合并commit, 使得提交的记录清晰。
git rebase 能减少分支树的分叉。
下面来一起去解开git rebase 的面纱

2. 更好用的代码合并git rebase

变基:

提取feature(要合并的分支)中引入的补丁和修改,然后在dev(主分支)的基础上应用一次。这样的操作就叫做变基。

重要的事情说三遍:注意变基的不可用场景!!!

禁止在公共分支上使用rebase,原因如下:

- 对于其他的开发同事,在主分支上的rebase后的commit都是新的提交 
- 在主分支上,rebase其他分支的修改,那么主分支上的历史提交将会被篡改,不是完整的历史。
复制代码
变基的好处:
多人开发的时候,是否经常遇到,
merge来merge去,然后突然之间代码丢失了?

我的代码不见了,git有bug?

前面提到在执行变基的死后,会提取引入的补丁和修改,在主分支(需要合并到的分支)的基础上应用一次。故,
1.相较于每次的merge操作,每次代码的合并commit都会减少一次merge的记录。
2.变基的作用只是简单的减少commit的记录嘛?如果仅仅如此,那绝不能称为更好用的代码合并。
  变基的作用在于,每一次的合并都是在原来的分支的基础上进行的引入补丁和修改,
  所以代码的分支树将不会出现分叉,这个意义很重要。减少分支树的分叉,
  在代码merge"三向合并"寻找祖先的时候,减少偏离预期结果的情况出现,
  同时有利于代码分支树的可读性
复制代码
常用指令:
git checkout dev
git rebase master, 指定分支名,分支名一般主分支(即,我们需要合并到的分支名)
git rebase -i 
// -i, 即打开交互式界面,可以干预rebase这个事务的过程,
// 包括设置commit message,暂停commit等等。
git rebase -i master, 变基到master时打开交互模式git rebase不指定分支名,
//即在自己的当前分支上进行变基,git rebase -i HEAD~2 
//可以使用head(大小写都支持)来指定次数,从后向前的次数,
//可以用来修改或者合并commit的提交记录,后面详解。
复制代码
操作流程:
1\. git checkout dev 
2\. git rebase - i master 
3\. git add . 
4\. git rebase --continue 
5\. 可能需要多次执行第3步和第4步,直到变基完成 
6\. git checkout master 
7\. git merge dev --squash // --squash 合并dev此次合并的所有commit为一次新的commit 
8\. git add . 
9\. git commit -m '新的commit记录' 
10\. git push
复制代码

3. 更优雅的commit记录

3.1 上一次提交打错字怎么办?

git commit --amend
i // 插入
esc //退出编辑模式
shift + zz // 退出交互模式
复制代码
如果已经提交到远程,则只能强迫提交覆盖:git push -f

3.2 多次提交描述一个问题太罗嗦?

git rebase -i HEAD~3 // 指定需要倒退的次数
复制代码
进入交互模式,将对应的commit记录前的pick改成对应的操作,交互模式中有说明,
pick => s, 需要合并的成一条, pick => edit , 编辑当前的commit记录 //后面详细讲解
直接删掉,丢弃此次提交
完成后退出交互模式
git commit --amend , 修改提交的文字
git push -f

3.3 commit的提交格式不规范?

可以通过配置 husky 插件来强制校验每次提交的commit的格式,此处不展开讲解,详细可参考husky的官网:[https://www.npmjs.com/package/husky](https://www.npmjs.com/package/husky)
安装了husky校验提交太麻烦想绕过检测:
git commit --no-verify -m “XXX”  //忽略husky检查
复制代码

4. 让代码的回滚更流畅

代码回滚我们常用的有git reset 和 git revert 命令,主要在于区别两个命令的应用场景:
区别:(注意两个命令使用上的区别)
1\. git reset会将当前的HEAD指向目标所在,并把目标之后的commit记录全部清除; 
   // 注意:清除的是目标之后的提交且不包含目标 
2\. git revert 则是使用当前的HEAD指针,替换git revert 目标所指向的HEAD的内容, 
   并生成一个新的commit提交记录,且完全保留原有记录 
   // 注意:替换的是目标
复制代码
应用场景:
先来说git reset,
git reset 常用的有三个参数,即 --hard, --soft, --mixed(默认)
复制代码
参数的定义即含义如下:
--soft 回退后a分支修改的代码被保留并标记为add的状态(git status 是绿色的状态)
--mixed 重置索引,但不重置工作树,更改后的文件标记为未提交(add)的状态。默认操作。 
--hard 重置索引和工作树,并且a分支修改的所有文件和中间的提交,没提交的代码都被丢弃了。
复制代码
场景1:我进行了三次提交,依次为功能1,2,3,上线前产品经理突然说,2和3的功能不要了;
场景2:我进行了三次提交,但后面两次的commit提交记录我不想要了,但我想保留2和3的修改文件
commit 3 333
commit 2 222
commit 1 111
复制代码
分析:

对于上面的场景,我们都是不希望保留commit提交记录而进行的代码的回滚,那么我们就可以使用git reset 进行操作。

对于场景1,直接清除commit的记录且保留什么的文件, 可以添加hard参数:
git reset --hard HEAD~2 或者 git reset --hard 111
复制代码
对于场景2,即添加 --soft 参数:
git reset --soft HEAD~2 或者 git reset --soft 111
复制代码

然后执行 git status, 可以看到功能2和3的提交的文件变成了 modifued状态

4.1 git reset回滚错了怎么办?

  1. 对于不熟练的同学,建议git reset的操作都采用git reset --hard(--soft) 我是id名 ,指定id名字来执行代码的回退;
  2. 对于远端的回退,需谨慎操作确保回退后的代码正常,此时执行git push是必然报错的,因为回退后的HEAD指针是落后与远端分支的,需要强推上去: git push -f
  3. git reset 回退版本错了,git log 也查看不到记录怎么办?代码都没有了,马上又要上线了,慌得一匹.....不要慌,办法总比困难多:
       3.1 执行: git reflog

       3.2 查看上一次的commit的id,执行 :
git reset --hard  我是reflog中得commit的id
git revert 指定commit 的id进行代码的回滚,前面已经讲过。
复制代码
现在新的需求来了,小明同学需要revert多个提交怎么办?一次次的revert显然不是一个优雅的操作。
git revert -n old_commit_id^..new_commit_id 
git revert --continue // 当有冲突的时候,继续执行下去
git revert --abort // 放弃本次revert git revert --quit // 退出本次revert
复制代码

4.2 git revert与git reset结合回滚更优雅

有人可能会说,我这次revert错了,再次revert,也是回到了原来的状态。但是,相较于历史的状态,revert之后再revert,在历史的记录中,多了两次提交记录。
当使用git revert回滚错了,又想撤回这次回滚
git reset --hard HEAD~1
复制代码
如果是git revert 回滚多个提交撤回,则有多少个,相应的HEAD~n 中得n就为多少

5. 更安全的代码拉取

通俗的来说,git pull = git fetch + git merge, 如果仅仅是如此简单,那么也没有展开讲解的必要。
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/333e3be2321342deaad5f27ae0ed6182~tplv-k3u1fbpfcp-zoom-1.image)

5.1 git fetch 让每一次代码拉取更放心

git fetch 是将远程的主机的最新的内容拉到本地,用户在检查了之后决定是否合并到工作的本地分支中。
而git pull 则是将远程主机的最新的内容拉下来后直接合并,可能会产生冲突,需要我们手动解决。
git fetch 是如何工作的呢?
git fetch会将数据拉取到本地仓库,但是他不会自动合并或者修改当前的工作区;
同时,如果我们在执行git fetch后,再使用git status,那么git会提示当前落后基本版本,这是因为,git fetch只会将本地库所关联的远程库的commit id 更新至最新,但是本地的HEAD没有变化,因为本地库没有变化,因为此时我们并没有进行合并。

git fetch适用于什么样的场景呢?

对于自己的独立开发分支,拉取关联的远程分支,我们git pull可以随便拉取。
git fetch适用于:(对于被拉取的代码安全可靠性不高的情况)
我们在本地独立分支开始的时候,拉取其他同事的分支代码(一般不建议直接这么干),
通常是将远端的公共分支代码拉到自己开发分支的时候,因为master分支包含了其他同事的代码,
我们需要决定是否要合并。本地主分支master拉取远端主分支master,
同样的道理,主分支包含了其他的同事的代码,
这对于我们来说,就是未知的代码,需要谨慎操作。
复制代码

5.2 git pull --rebase让拉取代码也不分叉

git pull --rebase = git fetch + git rebase
复制代码
如果我们以及对git rebase熟悉了,那么我们可以在拉取代码的时候,进行rebase变基操作,使分支树可读性更高。切记!!!不要在公共分支上进行rebase操作!!!

补充:

如果远程主机删除了某个分支,默认情况下,git pull 不会在拉取远程分支的时候,删除对应的本地分支。这是为了防止,由于其他人操作了远程主机,导致git pull不知不觉删除了本地分支。
但是,你可以改变这个行为,加上参数 -p 就会在本地删除远程已经删除的分支。
git pull -p
复制代码

6. 压箱底的代码暂存技巧

当你正在开发一个功能的时候,突然同事要你帮忙改个小问题打个包,
你说不行,你正在开发某某功能?那么友谊的小船说翻就翻...
你一脸不情愿还是接收了,git add . git commit -m '我不想提交这次commit',然后切换到分支,去修改小问题,然后发包。发完包再切回自己的分支,再git reset --soft HEAD~1回退自己的commit的提交记录。同事看着你的操作闷不吭声,一脸嫌弃...有没有更优雅的操作的方式呢?
有!必须有!!不优雅的,一律不要!!!

stash:临时存放工作目录的改动

stash 指令可以帮你把工作目录的内容全部放在你本地的一个独立的地方,它不会被提交,也不会被删除,你把东西放起来之后就可以去做你的临时工作了,做完以后再来取走,就可以继续之前手头的事了。

操作流程:
1.接收到同事的请求 
2.执行 git stash 
3.工作目录的改动就被清空了,所有改动都被存了起来 
4.git checkout 我是同事的分支名 
5.一顿操作发包 
6.git checkout 自己分支 
7.git stash pop
复制代码

stash是在本地所有分支上都是共享的,这个信息就很有意思。

为什么呢?我们是否遇到过这样的场景,修改完代码后发现是在主分支master更改的,而期望的是在自己分支上修改,不对主分支产生直接操作记录。

那么我们就可以,执行git stash后,再切换到自己的分支再执行 git stash pop

7. 更高端的git rebase操作

前面我们已经提到git rebase -i 能获取commit 的记录
例如: git rebase -i HEAD~3

我们可以将某一次的commit 提交改成edit, 那么当值的HEAD指针就会指向edit的commit 的这一次对应的HEAD位置,看图说明:

然后退出交互模式,细心的同学会发现,此时已经进入到了commit 测试2对应的节点了,(图中为vscode集成的git环境),左下角和刚刚改成的edit对应的HEAD是一致的。

然后在此次的HEAD进行对应的更改操作
再进行:
1\. git add .  
2\. git rebase --continue
复制代码
将1和2的操作循环进行,有冲突解决冲突,直到我们执行完git rebase --continue提示已经rebase成功,同时左下角的已经变成了正常的分支名(test-reset是之前测试reset就没改了)

此时,已经完成了git rebase -i 更改其中的某一个commit 提交内容的操作,
注意:
  1. 如果需要更新远端代码,则需要强推上,此时已经时落后远端分支的
  2. 如果此时执行了git pull,那么代码又回到了git rebase -i 操作之前的了
md图片没地方存放,故采用富文本的方式。可能是我的打开方式不对,很多格式都识别不了,例如:加粗,行内代码等等...所以排版有点乱

未经允许,谢绝转载,谢谢!

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改