在上一篇中介绍了私人git服务器的安装架设,那么这一篇我们通过一些傻瓜式的界面操作熟悉一下Git。

在日常开发中,作为Java开发仔,IDEA是最常用的开发工具,IDEA自带的VCS也是十分强大,所以我一般git操作都是通过IDEA完成,现在我们来熟悉一下IDEA的整个Git操作吧!
版本:IntelliJ IDEA 2019.3

配置Git

在使用IDEA的Git之前,你需要先安装Git ,Windows下git的最低版本要求为2.4,MacOS,Linux为1.8.0.0 。

打开Settings【快捷键Ctrl+Alt+S】-Version Control-Git ,指定Git可执行文件的路径,如果你没有更改默认路径,那么默认安装路径可能是C:\Program Files\Git\bin\git.exe,如果自定义了,那么久根据自定义的选择路径即可。

如果你细心的话,会发现cmdbin目录下都有git.exe,那么有什么区别呢?

answer】:简单来说,cmd下面的git.exe是一个包装器,用于能够在Windows下能够直接在cmd.exe中使用git,一般来说我们只需要指定bin下的git.exe即可,如果你喜欢,指定cmd下的也可以。

Git使用

在公司里,作为小开发仔,我们自然不必关心项目是怎么来的,怎么初始化的项目,我们暂时只需要知道现在有一个项目已经进入VCS,我们要进行开发了。接下来就进入Git的实战环节!

git clone

有项目进入VCS,我们想要进入开发,必然先要拉取代码:

VCS- Get from Version Control

如果没有项目可以打开,那么在打开IDEA的欢迎页可以点击 Get from Version Control 来clone项目。

指定一个你要拉取的远程项目的URL

git clone项目
git clone项目

同步远程仓库:fetch,pull,update

如果你在本地开发了几天,那么其他成员可能已经上传了不少代码,你需要同步他们的代码,你有三种方式:fetchpullupdate Project。我们接下去一个一个说

Fetch

fetch的时候会从远程仓库拉取所有分支的最新代码,但是这些修改的代码不会立马跟你的代码进行合并,而是被存储在本地的远程仓库中,直到你打算提交的时候才会进行合并。

路径:VCS | Git | Fetch

到这里可能就有人不理解了,哎呀怎么本地也有个远程仓库呢?

这就涉及到git的三大分区了:

三大分区
三大分区

图片源于https://juejin.im/post/5b6c4eeff265da0f4d0da3fa

  1. 当你在本地写代码的时候,这时候代码是在工作区,也就是在IDEA上你看到的红色的那些文件。
  2. 当你开启了自动add,那么这些文件就会变绿色
  3. 当你执行commit之后,你的代码就会变回原来的白色【深色模式下】

那上面说的本地的远程仓库指的是什么呢?自然是指我们本地的版本库,因为它同步了远程仓库,所以它就类似于远程仓库的一个镜像,所以我也把他叫本地的remote repository。

Pull

pull就不太一样了,它直接拉取当前分支的远程代码和你的当前代码进行合并,这时候会弹出来说你是要以什么方式进行合并,是rebase还是merge,我们一般选择rebase,这两个的区别下面再谈。

路径:VCS | Git | Pull

Update Project

如果你的项目有多个分支,你想一次性拉取所有的分支,然后合并这些分支的内容,就使用这个操作,类似于fetch+pull的结合体

这个功能是IDEA自己的功能并非git自己有的命令,see: https://stackoverflow.com/questions/51343304/git-confusion-update-and-pull
路径: VCS | Update Project

提交修改:commit,push

操作快捷键
CommitCtrl+K
Commit and PushCtrl+Alt+K
PushCtrl+Shift+K

commit

从上面的图我们也可以知道,我们使用commit是将代码推到我们的本地的版本库中,并没有推动到远程分支,别人拉取的时候还是拉不到你的修改代码。

路径:下方的 Version Control的子tabLocal Changes,快捷键Ctrl+K,或者右上角Commit

push

推送我们的修改到远程仓库,注意只会推送我们当前所在的分支!

路径:VCS | Git | Push或者快捷键Ctrl+Shift+K,或者在上一步点击右上角Commit的时候选择commit and push

commit and push
commit and push

如果你在commit或者push的时候想知道到底修改了什么,双击文件即可打开。

如果你在推送远程的时候,远程的代码或者文件出现更新或者修改,IDEA将会停止push操作要求你先合并代码,比如rebase还是merge,看下一章节将讲述这两个的区别

更新策略:rebase,merge

分叉的提交历史。
分叉的提交历史。

如上图,我们会遇到一种情况,小组内有两名开发,今天A,B两人同时拉取了C2节点的代码开始进行今天的开发,A开发完成后立马把代码提交到仓库了,B慢悠悠开发好之后正准备提交,发现被拒绝了,要求他先更新,这时蹦出个弹窗,要他选择是rebase还是merge。

Push Rejected
Push Rejected

merge

第一种策略就是合并,会把两个分支的最新快照(C3C4)以及二者最近的共同祖先(C2)进行三方合并,合并的结果是生成一个新的快照(并提交)。

通过合并操作来整合分叉了的历史。
通过合并操作来整合分叉了的历史。

这种做法会产生一个merge节点,如下图,对于我们追溯代码的修改其实是很不友好的,而且也不够美观,提交历史是多条线不断合并,所以有的公司会要求万不得已少用merge。

Log
Log

rebase

我们一般管这个叫变基,寓意改变他的基准点。如下图,首先找到这两个分支(即当前分支 experiment、变基操作的目标基底分支 master)的最近共同祖先 C2,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件,然后将当前分支指向目标基底 C3, 最后以此将之前另存为临时文件的修改依序应用。(译注:写明了 commit id,以便理解,下同)

将 <code>C4</code> 中的修改变基到 <code>C3</code> 上。
C4 中的修改变基到 C3 上。
C4 中的修改变基到 C3 上。">

C4 中的修改变基到 C3 上,现在回到 master 分支,进行一次快进合并。

master 分支的快进合并。
master 分支的快进合并。

简单来说,虽然我们拉取的代码基准点都是一样的,但是我在你之后提交,我先把你的代码提交点默认为我新的基准点,我在你的基础上做一系列的修改。

你在查看一个经过变基的分支的历史记录时会发现,尽管实际的开发工作是并行的,但它们看上去就像是串行的一样,提交历史是一条直线没有分叉。这样可以有效提高提交历史的整洁性。

当然了rebase,merge能做的还不止这些,他们还可以进行分支的合并,变基,这个我们下面再讲。

分支的开启

如果代码只有一个分支那么真的是太不方便了,我们线上出了bug,可能要发布一个紧急修复版本,那么我们只有一个分支吗,我们又进入了迭代开发,势必会导致新的代码被糅合到其中,所以至少我们应该要有一个分支能够和线上一致,让我们能够在遇到bug直接从那个分支开出Hotfix分支。所以我们一般会用master作为与线上版本一致的分支,如果权限管控严格的话,master分支原则上是不允许直接提交代码,只能使用merge或者rebase的方式进行代码合并,然后再开出一个分支develop作为开发分支,这是最简单的分支管理策略,如果更严格,那么可能就有更多的分支。

为远程开分支

所以我们如何开一个新的分支呢?IDEA右下角 New Branch,输入分支名称,例如develp点击create

开分支
开分支

将新的分支push到远程仓库【使用快捷键Ctrl+Shift+K】,我们从图中可以看到这次的提交将会在远程仓库创建一个develop的分支。

提交分支
提交分支

这时候也许有人要问啦:哎呀,这个怎么没经过commit啊?你稍微思考一下,我们本地有版本管理的是什么,只有一个本地版本库啊,暂存区是没分支这个概念呀。

提交之后我们发现右下角的GIt快捷操作里的Remote Branches会多一个origin/develop

为本地开分支

我们远程可能存在好几个分支,假如这时候你接到一个紧急需求,你的同事A已经开了一个分支用于开发紧急需求,这时候你通过fetch拉取远程仓库的所有分支,你就发现Remote Branches多了个分支,我们这里假定他叫local,我们可以通过check out检出这个分支的代码到我们的本地版本库中。

checkout
checkout

分支合并:rebase,merge,cherry pick

假如我们通过feature的分支完成了一个紧急需求之后,我们的测试也完成了发版工作,这时候我们常见的操作就是要将这部分的代码合并回master,再把develop rebase到master的最新代码,我们画个图来解释一下这个流程:

时序图
时序图

想要完成分支合并不只有rebase,还可以mergecherry pick

我在虚拟机里新建了一个用户stone,并用stone这个账号在local分支提交了4个记录【见棕色的线】,本地我用的chenly这个用户在develop分支提交了两个记录【见绿色的线】。我们可以看到他们都是从那个Merge节点开出来的分支,也就是他们的基准都是Merge点。

这里我还想做一个小小的实验,我在develop分支将pom文件版本改为1.0.0,而local分支改为1.0.1,我们来实验一下到时候master和develop的rebase会不会有冲突。

merge

概念跟上面的一样就略过。首先,按照上面的时序图,我们应该将local【比较懒没有建feature分支,姑且将local当做feature看】代码merge到master,我们来看看例子:

log
log

我们在local上加了两条记录,现在我们把它合并到master,本地先切到master分支,IDEA提供了两个方法:

方法一:右下角的快捷操作

合并
合并

我们merge之后,合并的代码暂时还是在本地并没有被push到远程仓库,所以,我们需要再一步push,快捷键:Ctrl+Shift+K。这时候我们再去观察Version Controllog你会发现是没有merge节点的,因为你没有需要解决冲突的地方,所以很平滑地合并过去了。

方法二:VCS-Git-Merge Changes

Merge Changes
Merge Changes

这里的选项会更多,我们只要知道如果我们是打算把local分支的代码合并到master,那么我们切到master分支,在Branches to Merge中选择remotes开头的local分支然后点merge即可。

更详细的参数见官方文档

为什么master不用rebase?

master分支按照划分这是一个最基础的分支,所有的分支都是他派生的,所以master的基按道理来说不能被换基,所以只能采取merge的方式进行代码合并。

rebase

概念其实和上面的更新策略差不多。明确一下我们要做什么,从上面时序图得知我们现在应该要做的是将master rebase到develop,也就是将develop的基变成master当前的基,也就是我们develop应该要基于合并之后的master的"修改这个文件-2233"这条commit message的基础上进行开发。

log
log

分支切到develop,IDEA提供了两种简便的操作:

方法1:右下角的快捷操作

rebase current onto selected
rebase current onto selected

由于我们上面做了个小小的实验,pom文件的版本号再两个分支进行了修改,现在在rebase的时候要出现merge,解决冲突之后apply一下即可完成rebase,别忘了还要push到远程仓库!

这时候我们大概就能看出git rebase也是有弊端的,上面我们讲到git rebase可以有一个漂亮的提交记录线,但是这也意味着如果出现冲突merge的信息会被吃掉,这是不利于我们排查到底是那一条记录开始出现问题的!

meger
meger

快捷键:Ctrl+Shift+K,将修改push到远程仓库

merge into current
merge into current

方法2:VCS-Git-Rebase

Rebase Branch
Rebase Branch

我当时第一次使用的时候有点慌,onto,from到底谁才是要被换基的分支啊?

查阅官方文档,结果官方的说明书也差点把我绕进去。所以呢就是onto是要被换基的分支,from就是要换过去的基。

rebasing
rebasing

当我们执行rebase的时候发现报错了,IDEA不让我们rebase

rebase error
rebase error

原因很简单,要解决pom文件的冲突。但是这个功能我发现有点残缺,根据官方文档来看还有些字段没有了比如Show Remote Branches,Merge Strategy,Do not use merge strategies没有选项可以选择进行merge,所以建议还是使用右下角的快捷操作。

cherry pick

假如你和你的A,B两个同事都在feature分支开发,那么必然这个分支会有你们三个人不同时间提交的记录,而你的主管要求你们没有完成自测的代码不能提到develop让测试进入测试,为了不让部分无关你做的功能代码流入develop,我们就可以使用cherry pick的功能,如果你的功能开发完成,准备提测,那么你就将你的commit的那部分代码从feature pick到develop进行合并,这样A,B的代码也不会合过去影响develop分支。

这里我们拿local分支做个测试:

新增一条记录,如下图,点小樱桃或者右键选择cherry pick

cherry pick
cherry pick

有时候提交记录太多了我们需要过滤掉已经pick过的或者只选择我的commit,IDEA非常贴心地给了一些过滤条件可以试着过滤看看。

过滤条件
过滤条件

同样的,在pick之后别忘了push到远程仓库!

如何回滚

在实际开发中难免会有几次误提交,那么如何解决这个问题呢?问题分为以下三种,未commit的回滚,commit到本地仓库的回滚,push到远程仓库的回滚。

未commit的回滚

右键你想回滚的文件,选择Rollback 即可

回滚本地仓库

回滚上一个commit:打开底部的Version Control工具窗口,选择Log tab。点击这个commit,右键选择Undo Commit。如果我们直接选择默认,那么这个提交记录是不在的,但是你可以在Version Control-Local Changes里看到这个原始的commit信息,你可以继续使用这个commit提交。

回滚到某个commit点:我们直接Reset Current Branch toHere,有四个选项:Soft,Mixed,Hard,Keep

Soft在选定的提交之后所做的所有提交更改都将进入暂存区(这意味着它们将被移至Version Control 工具窗口Alt + 9的Local Changes选项卡中,以便你可以查看它们并在以后进行必要的提交)。
Mixed选定提交后所做的更改将保留,但不会到暂存区。
Hard在所选提交之后进行的所有更改都将被丢弃(暂存区和本地版本库)。
Keepcommit提交的内容将会被丢弃,但本地更改将保持不变。

回退commit
回退commit

回滚本地以及远程仓库

如果你细心的话你会发现上图中还有个Revert Commit,这个是用于回退远程仓库的记录,这种方式的做法其实就是相当于把你修改的revert掉再提交修改,这就会产生两个不应该出现的提交记录【一条错误提交记录,一条去掉错误的提交记录】,有时候这并不是我们想要的处理方案。你也许会问,IDEA有没有回退强推的方案啊,答案是没有

When you run push, Git will refuse to complete the operation if the remote repository has changes that you are missing and that you are going to overwrite with your local copy of the repository. Normally, you need to perform pull to synchronize with the remote before you update it with your changes.

The --force push command disables this check and lets you overwrite the remote repository, thus erasing its history and causing data loss.

A possible situation when you may still need to perform --force push is when you rebase a pushed branch and then want to push it to the remote server. In this case, when you try to push, Git will reject your changes because the remote ref is not an ancestor of the local ref. If you perform pull in this situation, you will end up with two copies of the branch which you then need to merge.

If you decide to force push the rebased branch and you are working in a team, make sure that:

  • Nobody has pulled your branch and done some local changes to it
  • All pending changes have been committed and pushed
  • You have the latest changes for that branch

作为一个可视化的平台,安全无疑是最重要的,所以这种强推的操作是非常危险的,很容易导致远程仓库的内容被覆盖,如果A提交了记录,但是你强推上去把A的记录覆盖了,那么会导致A下一次拉取会有异常,同时还丢失了代码!

所以除非必要,你可以借助IDEA+git.exe来实现强推回退。

  1. Reset Current Branch toHere,回退本地版本库的代码
  2. 在当前项目文件夹里右键git bash here打开git.exe执行 git push -f,或者直接在

IDEA的Terminal执行git push -f

为项目打Tag

一般来说,每个版本发布后我们都会在代码合并的时候打上TAG标记发布版本,例如DEMO-1.0.0-RELEASE

IDEA可以很方便打上TAG,

new tag
new tag

当然了,打上TAG只是在本地,你还需要push到远程

push tag
push tag

总结

IDEA的Git客户端已经非常强大了,我觉得日常开发使用已经足够了。但是傻瓜化的操作并不是终点,我们三部曲最后一步将使用常用的命令行进行操作git。虽然IDEA亦或者Git.exe有着许多功能或者命令,但是我们根据二八法则,只需要掌握那百分二十的技巧就能解决百分八十问题,剩下那些刁钻的问题再去翻阅一下文档即可解决!

Maybe命令行的不会马上就写,还有一些优先级高的事会先学习记录。

参考:

https://www.jetbrains.com/help/idea/using-git-integration.html

https://git-scm.com/book/zh/v2