Git 操作指南
一、 在Idea中配置Git
安装好IntelliJ IDEA后,如果Git 安装在默认路径下,那么idea会自动找到git的位置,如果更改了Git 的安装位置则需要手动配置下Git 的路径。
选择File→Settings打开设置窗口,找到Version Control下的git选项
选择git的安装目录后可以点击“Test”按钮测试是否正确配置。
至此,Git 已经集成到IDEA当中,下一步我们需要配置Git 账号信息
在IDEA中设置Git Hub,File–>Setting->Version Control–>GibHub
可以使用账号密码,或者Token(Token需要在你的Git Hub网站生成)
二、 从远程Git Lab下拉取项目代码
1.cmd操作
在你存放代码的目录下,通过cmd命令克隆代码
git clone + 项目URL
2.使用IDEA的Git 操作
选择File→New→Project from Version Control
填写要拉取的远程项目的URL,点击Clone
注意,能够拉取该项目的前提是你具有对应的权限
三、导入拉取的工程
此时项目工程已经拉取到了你的电脑上,但此时观察IDEA里面的工程列表,并没有新增工程
这样的场景常会出现,比如你负责的开发任务,刚开始只涉及一个工程,而后涉及到其他工程,你满怀期待的将其他工程拉取下来,本地有了代码,却没有同时出现在IDEA的Project列表
对应的解决办法就是,将新拉取的工程加入到当前的项目模块组中
选择File→New→Project Structure→Modules→"+"→Import Module
选择你代码所在的文件夹,以Maven类型导入即可
如果你需要移除工程,将上述操作的“+”换成“-”即可
四、将修改保存到暂存区
Git 和其他版本控制系统如SVN的一个不同之处就是有暂存区的概念。
先来看名词解释。
工作区(Working Directory)
就是你在电脑里能看到的目录,比如我的learngit
文件夹就是一个工作区:
版本库(Repository)
工作区有一个隐藏目录.git
,这个不算工作区,而是Git 的版本库。
Git 的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,还有Git 为我们自动创建的第一个分支master
,以及指向master
的一个指针叫HEAD
。
分支和HEAD
的概念我们以后再讲。
前面讲了我们把文件往Git 版本库里添加的时候,是分两步执行的:
第一步是用git add
把文件添加进去,实际上就是把文件修改添加到暂存区;
第二步是用git commit
提交更改,实际上就是把暂存区的所有内容提交到当前分支。
在IDEA中将文件修改添加到暂存区
修改文件,这里理解为进行开发即可,比如我新增一个字段,一个接口
右键工程→Git →Add
当然,在工作中涉及到改动多个文件,我们可以同时全部Add,也可以先Add一部分,再Add一部分
你可以把它想象成往盒子里面放物品,你可以一下子全部放进去,也可以一部分一部分的放入,同时,你不用担心先放进去的部分会遗漏(盒子就是暂存区,Add即将改动放进暂存区,commit则是将暂存区的内容放进版本库)
如果改动的文件很多,我们就很难记住哪些文件被修改,同样的,即使改动的地方很少,我们也希望改动被记录下来,我们需要查看时,就会一目了然
1.使用Git Status
命令对比分支
点击IDEA下方的Terminal→输入Git status
(Git status用于查看当前所有的Git 状态,包括未Add,已Add未Commit等)
对于热衷于敲命令的同学来说,这是一件好事,但它无法显示文件具体的改动,我们来看看方式二
如果你需要使用Git 命令,那么就在此处畅所欲敲吧
2.使用IDAE的Git 操作对比分支
右键改动所在的工程→Git →Compare with Branch→选择你需要进行对比的分支(一般和master分支对比)
可以看到修改的文件,点击查看具体的修改内容,这实在是太酷了
在与master(或你选择的分支)对比的情况下,我们更容易发现自己改动是否有误,同时我们可以在此处直接修改,文件会实时生效,不用再Add
五、将暂存区中的改动提交到版本库的分支中
复习一下工作区,版本库,暂存区,分支的概念
工作区 git add →暂存区 git commit →本地分支
,需要注意的是本地分支和暂存区stage都存在于版本库中(可回看【四、将修改保存到暂存区】)
右键改动所在的工程→Git →Commit Changes
现在,你的改动已经被提交到本地分支
一旦提交后,如果你又没有对工作区做任何修改,那么工作区就是“干净”的:
Git status查看工作区状态
你可以以此判断你是否漏掉了需要Commit的内容
工作区 git add →暂存区 git commit →本地分支 此时我们处于这条链路的最后一点【本地分支】
六、查看分支级别的改动信息
当你需要查看分支改动的时候,你可以使用 show history 查看当前分支的改动
右键工程→Git →show history,此时你会发现左下角的Git 面板被弹出
分支级别的信息,在这里都可以找到 你可以根据分支名/用户/日期/路径等对Git log 进行筛选
这些信息对于你后期避免分支冲突,查看分支改动有莫大的帮助
七、查看工作区,暂存区和版本库里面最新版本的区别
右键工程→Git →Compare with Revision
同样的,你也可以选择与其他版本进行比较
诶,你可能会说我们在前文提到了一种比较方法 ,当时用的是 Compare with Branch,那现在为什么还要 Compare with Rivison 呢?
注意,这二者可是不一样的,Compare with Branch 是将你当前的工作区,暂存区,本地分支与远程分支进行对比,你可以理解为工程维度,尽管你可能会回滚;
而Compare with Rivision则是将你当前的工作区,暂存区与本地分支里的版本进行对比(每一次Commit都会创建一个版本)
八、回滚改动
1.你的改动还没Add到暂存区,只在工作区
这种情况直接改就可以了,工作区无所畏惧
2.你的改动已经Add到暂存区,但未Commit到本地分支
此时,你可以先使用 Git status
查看目前的Git 状态,它提示我们ApollConfigUtil文件的改动已经Add(绿色表示已经Add)
此时进行RollBack会回滚到未Add的状态
右键工程→Git →RollBack→选择你需要回滚的文件
使用 Git status
查看状态,红色表示已经Add,等待Commit,如果你有其他的未Add的改动,也会以绿色的形式显示在下面
右键工程→Git →RollBack→选择你需要回滚的文件
此时可以看到,你Add的改动,Commit的改动均消失不见了
3.你的改动已Commit到本地分支
此时你会发现RollBack无法点击,当你在工作区进行改动之后,它又可以点击了
总之,RollBack会让【未Add / 已Add但未Commit】的所有改动消失
九、从版本库删除文件
如果你需要删除一个文件,很简单,右键该文件→Delete
即可
在删除之后,如果你想查看它所带来的影响,同样可以使用 Git Status
命令
现在,我删除了 ApollConfigUtil.java 文件,使用 Git Status
来查看当前状态
此时,你具有两种选择
1.确实要从版本库中删除该文件,则直接提交改动即可
2.误删除该文件,那么因为版本库中还有,所以可以很轻松地把巫山的文件恢复到最新版本
使用IDEA 的 RollBack 操作 或者 git checkout -- XX.java
git checkout
其实是用版本库里的版本替换工作区的版本,无论工作区是修改还是删除,都可以“一键还原”。
注意:从来没有被添加到版本库就被删除的文件,是无法恢复的!
命令git rm
用于删除一个文件。如果一个文件已经被提交到版本库,那么你永远不用担心误删,但是要小心,你只能恢复文件到最新版本,你会丢失最近一次提交后你修改的内容。
十、分支管理
分支就是科幻电影里面的平行宇宙,当你正在电脑前努力学习Git 的时候,另一个你正在另一个平行宇宙里努力学习SVN。
如果两个平行宇宙互不干扰,那对现在的你也没啥影响。不过,在某个时间点,两个平行宇宙合并了,结果,你既学会了Git 又学会了SVN!
分支在实际中有什么用呢?假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。
现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。
其他版本控制系统如SVN等都有分支管理,但是用过之后你会发现,这些版本控制系统创建和切换分支比蜗牛还慢,简直让人无法忍受,结果分支功能成了摆设,大家都不去用。
但Git 的分支是与众不同的,无论创建、切换和删除分支,Git 在1秒钟之内就能完成!无论你的版本库是1个文件还是1万个文件。
1.Git 的分支原理
在里,你已经知道,每次提交,Git 都把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在Git 里,这个分支叫主分支,即master
分支。HEAD
严格来说不是指向提交,而是指向master
,master
才是指向提交的,所以,HEAD
指向的就是当前分支。
一开始的时候,master
分支是一条线,Git 用master
指向最新的提交,再用HEAD
指向master
,就能确定当前分支,以及当前分支的提交点:
每次提交,master
分支都会向前移动一步,这样,随着你不断提交,master
分支的线也越来越长。
当我们创建新的分支,例如dev
时,Git 新建了一个指针叫dev
,指向master
相同的提交,再把HEAD
指向dev
,就表示当前分支在dev
上:
你看,Git 创建一个分支很快,因为除了增加一个dev
指针,改改HEAD
的指向,工作区的文件都没有任何变化!
不过,从现在开始,对工作区的修改和提交就是针对dev
分支了,比如新提交一次后,dev
指针往前移动一步,而master
指针不变:
假如我们在dev
上的工作完成了,就可以把dev
合并到master
上。Git 怎么合并呢?最简单的方法,就是直接把master
指向dev
的当前提交,就完成了合并:
所以Git 合并分支也很快!就改改指针,工作区内容也不变!
合并完分支后,甚至可以删除dev
分支。删除dev
分支就是把dev
指针给删掉,删掉后,我们就剩下了一条master
分支:
真是太神奇了,你看得出来有些提交是通过分支完成的吗?
下面开始实战。
首先,我们创建dev
分支,然后切换到dev
分支:
$ git checkout -b dev Switched to a new branch 'dev'
git checkout
命令加上-b
参数表示创建并切换,相当于以下两条命令:
$ git branch dev $ git checkout dev Switched to branch 'dev'
然后,用git branch
命令查看当前分支:
$ git branch * dev master
git branch
命令会列出所有分支,当前分支前面会标一个*
号。
然后,我们就可以在dev
分支上正常提交,比如对readme.txt
做个修改,加上一行:
Creating a new branch is quick.
然后提交:
$ git add readme.txt $ git commit -m "branch test" [dev b17d20e] branch test 1 file changed, 1 insertion(+)
现在,dev
分支的工作完成,我们就可以切换回master
分支:
$ git checkout master Switched to branch 'master'
切换回master
分支后,再查看一个readme.txt
文件,刚才添加的内容不见了!因为那个提交是在dev
分支上,而master
分支此刻的提交点并没有变:
现在,我们把dev
分支的工作成果合并到master
分支上:
$ git merge dev Updating d46f35e..b17d20e Fast-forward readme.txt | 1 + 1 file changed, 1 insertion(+)
git merge
命令用于合并指定分支到当前分支。合并后,再查看readme.txt
的内容,就可以看到,和dev
分支的最新提交是完全一样的。
注意到上面的Fast-forward
信息,Git 告诉我们,这次合并是“快进模式”,也就是直接把master
指向dev
的当前提交,所以合并速度非常快。
当然,也不是每次合并都能Fast-forward
,我们后面会讲其他方式的合并。
合并完成后,就可以放心地删除dev
分支了:
$ git branch -d dev Deleted branch dev (was b17d20e).
删除后,查看branch
,就只剩下master
分支了:
$ git branch * master
因为创建、合并和删除分支非常快,所以Git 鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master
分支上工作效果是一样的,但过程更安全。
2.本地分支 / 远程分支的概念
分支分为本地分支,远程分支两大类;注意,这是学习分支内容的重要前提!
a.本地分支的理解
现在你的导师给你分配了一个任务 —— 给某一个服务新增字段;可能他会给你具体的工程路径,也可能让你自己推测(我导师即是如此)
好的,经过努力,你找到了新增服务所在的工程路径,现在回到第二步
此时你的代码就从 远程的工程路径分支(可能是master 也可能是 你导师的分支)克隆到 → 你的本地分支(如果你没有创建新的分支,则为master)
接下来,导入拉取的工程,进行你的修改并保存到暂存区,再提交到版本库中,对应于前文的 第三点,第四点,第五点
b.远程分支的理解(master release rel_xx_xx)
我们知道,公司的代码存放于 GitLab,我们也是从 GitLab 上拉取工程代码,回顾第二步 ,其实,我们忽略了一步——选择分支
Master分支:主分支,公司服务正在使用的代码分支;开发过程中的最后一步即合并到Master分支,Master为生产环境,对Master的操作不当将直接影响生产环境,因此对Master分支的操作需要极度小心(如果你有权限的话)
Release分支:可以理解为Master分支的防线,我们所作的改动会现在Release分支上运行检验,通过后才会合并到Master分支,Release分支的内容与Master分支完全一致
Rel_xx_xx需求分支:由于 Git 创建分支的便捷性,我们提倡每一个需求都创建一个对应的分支,这些分支可以命名为 Rel_xx_xx(根据公司规定),这些分支里面包含我们的具体改动,比如
注意,需求分支一般需要先在本地创建,然后推送到远程仓库时,远程仓库会创建对应同名分支
3.创建和合并分支
对于Master分支和Release分支,我们一般没有权限操作,在此不表;
那么对于需求分支,我们先在本地创建即可
a. 使用Git branch
命令创建本地分支
b. 使用 IDEA 分支面板 创建本地分支
右下角master→New Branch →Create 即可
注意:IDEA 关于 Git 的操作面板即为此处,可完成分支的新增,删除,切换,查看等
4.切换分支
当前你工作在rel_test_002分支上,你需要查看rel_test_001分支状态的代码,那么你可以 切换分支
a. 使用git checkout <name>
或者git switch <name>
命令切换
b.使用 IDEA 的分支面板切换分支
右下角→选择分支→checkout 即可
Git 鼓励大量使用分支:
查看分支:git branch
创建分支:git branch <name>
切换分支:git checkout <name>
或者git switch <name>
创建+切换分支:git checkout -b <name>
或者git switch -c <name>
合并某分支到当前分支:git merge <name>
删除分支:git branch -d <name>
5.对比分支
6.分支冲突
人生不如意之事十之八九,合并分支往往也不是一帆风顺的。
准备新的feature1
分支,继续我们的新分支开发:
$ git switch -c feature1 Switched to a new branch 'feature1'
修改readme.txt
最后一行,改为:
Creating a new branch is quick AND simple.
在feature1
分支上提交:
$ git add readme.txt $ git commit -m "AND simple" [feature1 14096d0] AND simple 1 file changed, 1 insertion(+), 1 deletion(-)
切换到master
分支:
$ git switch master Switched to branch 'master' Your branch is ahead of 'origin/master' by 1 commit. (use "git push" to publish your local commits)
Git 还会自动提示我们当前master
分支比远程的master
分支要超前1个提交。
在master
分支上把readme.txt
文件的最后一行改为:
Creating a new branch is quick & simple.
提交:
$ git add readme.txt $ git commit -m "& simple" [master 5dc6824] & simple 1 file changed, 1 insertion(+), 1 deletion(-)
现在,master
分支和feature1
分支各自都分别有新的提交,变成了这样:
这种情况下,Git 无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突,我们试试看:
$ git merge feature1 Auto-merging readme.txt CONFLICT (content): Merge conflict in readme.txt Automatic merge failed; fix conflicts and then commit the result.
果然冲突了!Git 告诉我们,readme.txt
文件存在冲突,必须手动解决冲突后再提交。git status
也可以告诉我们冲突的文件:
$ git status On branch master Your branch is ahead of 'origin/master' by 2 commits. (use "git push" to publish your local commits) You have unmerged paths. (fix conflicts and run "git commit") (use "git merge --abort" to abort the merge) Unmerged paths: (use "git add <file>..." to mark resolution) both modified: readme.txt no changes added to commit (use "git add" and/or "git commit -a")
我们可以直接查看readme.txt的内容:
Git is a distributed version control system. Git is free software distributed under the GPL. Git has a mutable index called stage. Git tracks changes of files. <<<<<<< HEAD Creating a new branch is quick & simple. ======= Creating a new branch is quick AND simple. >>>>>>> feature1
Git 用<<<<<<<
,=======
,>>>>>>>
标记出不同分支的内容,我们修改如下后保存:
Creating a new branch is quick and simple.
再提交:
$ git add readme.txt $ git commit -m "conflict fixed" [master cf810e4] conflict fixed
现在,master
分支和feature1
分支变成了下图所示:
用带参数的git log
也可以看到分支的合并情况:
$ git log --graph --pretty=oneline --abbrev-commit * cf810e4 (HEAD -> master) conflict fixed |\ | * 14096d0 (feature1) AND simple * | 5dc6824 & simple |/ * b17d20e branch test * d46f35e (origin/master) remove test.txt * b84166e add test.txt * 519219b git tracks changes * e43a48b understand how stage works * 1094adb append GPL * e475afc add distributed * eaadf4e wrote a readme file
最后,删除feature1
分支:
$ git branch -d feature1 Deleted branch feature1 (was 14096d0).
工作完成。
7.分支合并
a.Fast forward模式
通常,合并分支时,如果可能,Git 会用Fast forward
模式,但这种模式下,删除分支后,会丢掉分支信息。
如果要强制禁用Fast forward
模式,Git 就会在merge时生成一个新的commit,这样,从分支历史上就可以看出分支信息。
b.不使用Fast forward
模式
merge后就像这样:
8.Git 的Head指针
Git 中的 HEAD 可以理解为指针,指向当前仓库所处的分支。 一般在有 Git 管理的目录下打开 Git 终端都能在当前路径的尾巴上,看到所处的分支名。 还可以在命令行中输入 查看当前 HEAD 指向哪里。
9.Bug分支
软件开发中,bug就像家常便饭一样。有了bug就需要修复,在Git 中,由于分支是如此的强大,所以,每个bug都可以通过一个新的临时分支来修复,修复后,合并分支,然后将临时分支删除。
当你接到一个修复一个代号101的bug的任务时,很自然地,你想创建一个分支issue-101
来修复它,但是,等等,当前正在dev
上进行的工作还没有提交:
$ git status On branch dev Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: hello.py Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: readme.txt
并不是你不想提交,而是工作只进行到一半,还没法提交,预计完成还需1天时间。但是,必须在两个小时内修复该bug,怎么办?
幸好,Git 还提供了一个stash
功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作:
$ git stash Saved working directory and index state WIP on dev: f52c633 add merge
现在,用git status
查看工作区,就是干净的(除非有没有被 Git 管理的文件),因此可以放心地创建分支来修复bug。
首先确定要在哪个分支上修复bug,假定需要在master
分支上修复,就从master
创建临时分支:
$ git checkout master Switched to branch 'master' Your branch is ahead of 'origin/master' by 6 commits. (use "git push" to publish your local commits) $ git checkout -b issue-101 Switched to a new branch 'issue-101'
现在修复bug,需要把“Git is free software …”改为“Git is a free software …”,然后提交:
$ git add readme.txt $ git commit -m "fix bug 101" [issue-101 4c805e2] fix bug 101 1 file changed, 1 insertion(+), 1 deletion(-)
修复完成后,切换到master
分支,并完成合并,最后删除issue-101
分支:
$ git switch master Switched to branch 'master' Your branch is ahead of 'origin/master' by 6 commits. (use "git push" to publish your local commits) $ git merge --no-ff -m "merged bug fix 101" issue-101 Merge made by the 'recursive' strategy. readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
太棒了,原计划两个小时的bug修复只花了5分钟!现在,是时候接着回到dev
分支干活了!
$ git switch dev Switched to branch 'dev' $ git status On branch dev nothing to commit, working tree clean
工作区是干净的,刚才的工作现场存到哪去了?用git stash list
命令看看:
$ git stash list stash@{0}: WIP on dev: f52c633 add merge
工作现场还在,Git 把stash内容存在某个地方了,但是需要恢复一下,有两个办法:
一是用git stash apply
恢复,但是恢复后,stash内容并不删除,你需要用git stash drop
来删除;
另一种方式是用git stash pop
,恢复的同时把stash内容也删了:
$ git stash pop On branch dev Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: hello.py Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: readme.txt Dropped refs/stash@{0} (5d677e2ee266f39ea296182fb2354265b91b3b2a)
再用git stash list
查看,就看不到任何stash内容了:
$ git stash list
你可以多次stash,恢复的时候,先用git stash list
查看,然后恢复指定的stash,用命令:
$ git stash apply stash@{0}
10.Feature分支
软件开发中,总有无穷无尽的新的功能要不断添加进来。
添加一个新功能时,你肯定不希望因为一些实验性质的代码,把主分支搞乱了,所以,每添加一个新功能,最好新建一个feature分支,在上面开发,完成后,合并,最后,删除该feature分支。
现在,你终于接到了一个新任务:开发代号为Vulcan的新功能,该功能计划用于下一代星际飞船。
于是准备开发:
$ git switch -c feature-vulcan Switched to a new branch 'feature-vulcan'
5分钟后,开发完毕:
$ git add vulcan.py $ git status On branch feature-vulcan Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: vulcan.py $ git commit -m "add feature vulcan" [feature-vulcan 287773e] add feature vulcan 1 file changed, 2 insertions(+) create mode 100644 vulcan.py
切回dev
,准备合并:
$ git switch dev
一切顺利的话,feature分支和bug分支是类似的,合并,然后删除。
但是!
就在此时,接到上级命令,因经费不足,新功能必须取消!
虽然白干了,但是这个包含机密资料的分支还是必须就地销毁:
$ git branch -d feature-vulcan error: The branch 'feature-vulcan' is not fully merged. If you are sure you want to delete it, run 'git branch -D feature-vulcan'.
销毁失败。Git 友情提醒,feature-vulcan
分支还没有被合并,如果删除,将丢失掉修改,如果要强行删除,需要使用大写的-D
参数。。
现在我们强行删除:
$ git branch -D feature-vulcan Deleted branch feature-vulcan (was 287773e).
终于删除成功!
十一、Pull最新代码
Pull操作会把远程仓库最新的代码拉取下来,这样能确保你本地分支的代码是最新的,因此,在开发过程中,如果你发现其他人修改了你工作对应的远程分支,你应该立即Pull最新的代码
十二、Push到远程仓库
现在,你终于完成了代码修改,你确定一切准备就绪,那么可以将你的分支推送到远程了;之前我们所有的操作均在本地分支上,如果想让我们的修改生效或者被其他人所看到,则需要推送到远程仓库
右键工程→Git →Push
当然,建议在Push你的修改之前,先Pull一下,Pull操作会把远程仓库最新的代码拉取下来,这样能确保你本地分支的代码是最新的,因此,在开发过程中,如果你发现其他人修改了你工作对应的远程分支,你应该立即Pull最新的代码(再次强调)
十三、修改 commit 描述信息
完结
Git 是目前世界上最先进的分布式版本控制系统(没有之一)。如果你想要学习更多的 Git 操作,可以前往廖雪峰前辈的博客网站进行学习!