题目分类: git
面试频率:
涉及面试题:
- Git和SVN有什么区别?
- git 快照指的是什么?git 的工作区域由哪三个部分组成?在 git 中,如何提交的版本打标签?
- 对 git flow 了解么?应该如何使用?
- git pull --rebase 和 git pull 的区别是什么?
- 提交时发生冲突,你能解释冲突是如何产生的吗?你是如何解决的?
- 如果本次提交误操作,如何撤销?
- 如果我想修改提交的历史信息,应该用什么命令?
- 如何查看分支提交的历史记录?查看某个文件的历史记录呢?
- 怎样将 N 次提交压缩成一次提交?
- 什么是 git stash?
- 描述一下你所使用的分支策略
- 我们在本地工程常会修改一些配置文件,这些文件不需要被提交,而我们又不想每次执行git status时都让这些文件显示出来,我们该如何操作?
- 如何把本地仓库的内容推向一个空的远程仓库?
面试知识点
什么是Git
Git 是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。
Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。
Git 与常用的版本控制工具 CVS, Subversion 等不同,它采用了分布式版本库的方式,不必服务器端软件支持。
git与svn区别
- 1、Git 是分布式的,SVN 不是:这是 Git 和其它非分布式的版本控制系统,例如 SVN,CVS 等,最核心的区别。
- 2、Git 把内容按元数据方式存储,而 SVN 是按文件: 所有的资源控制系统都是把文件的元信息隐藏在一个类似 .svn、.cvs 等的文件夹里。
- 3、Git 分支和 SVN 的分支不同: 分支在 SVN 中一点都不特别,其实它就是版本库中的另外一个目录。
- 4、Git 没有一个全局的版本号,而 SVN 有: 目前为止这是跟 SVN 相比 Git 缺少的最大的一个特征。
- 5、Git 的内容完整性要优于 SVN: Git 的内容存储使用的是 SHA-1 哈希算法。这能确保代码内容的完整性,确保在遇到磁盘故障和网络问题时降低对版本库的破坏。
Git 工作流程
一般工作流程如下:
- 克隆 Git 资源作为工作目录。
- 在克隆的资源上添加或修改文件。
- 如果其他人修改了,你可以更新资源。
- 在提交前查看修改。
- 提交修改。
- 在修改完成后,如果发现错误,可以撤回提交并再次修改并提交。
git的工作区、暂存区和版本库
workspace
:工作区,本地目录,就是你在电脑里能看到的目录。
staging area
:暂存区,存放在.git目录下的index文件中,所以暂存区也叫索引index。
local repository
:本地仓库(版本库),.git目录。
remote repository
:远程仓库。
下图是本地仓库的细节,注意HEAD,分支指针,指向当前分支最近的一个提交。
按照工作流程:
- git clone/fetch 从远程仓库拉取资源到本地仓库,同时checout 更新到工作区(本地目录)。
- 当对工作区修改(或新增)的文件执行 git add 命令时,暂存区的目录树被更新,同时工作区修改(或新增)的文件内容被写入到对象库中的一个新的对象中,而该对象的ID被记录在暂存区的文件索引中。
- 当执行提交操作(git commit)时,暂存区的目录树写到版本库(对象库)中,master 分支会做相应的更新。即 master 指向的目录树就是提交时暂存区的目录树。
- 当执行上传远程代码并合并(git push)时,版本库就将资源更新到远程仓库进行合并。本地的 master 分支推送到 origin 主机的 master 分支。
Git Flow-分支管理策略
就像代码需要代码规范一样,代码管理同样需要一个清晰的流程和规范.
Git Flow的流程图
Git Flow 的常用分支
-
Production 分支
也就是我们经常使用的Master分支,这个分支最近发布到生产环境的代码,最近发布的Release, 这个分支只能从其他分支合并,不能在这个分支直接修改
-
Develop 分支
这个分支是我们是我们的主开发分支,包含所有要发布到下一个Release的代码,这个主要合并与其他分支,比如Feature分支
-
Feature 分支
这个分支主要是用来开发一个新的功能,一旦开发完成,我们合并回Develop分支进入下一个Release
-
Release分支
当你需要一个发布一个新Release的时候,我们基于Develop分支创建一个Release分支,完成Release后,我们合并到Master和Develop分支
-
Hotfix分支
当我们在Production发现新的Bug时候,我们需要创建一个Hotfix, 完成Hotfix后,我们合并回Master和Develop分支,所以Hotfix的改动会进入下一个Release
Git Flow 如何使用
-
Master/Devlop 分支
所有在Master分支上的Commit应该打上Tag,一般情况下Master不存在Commit,Devlop分支基于Master分支创建
-
Feature 分支
Feature分支做完后,必须合并回Develop分支, 合并完分支后一般会删点这个Feature分支,毕竟保留下来意义也不大。
-
Release 分支
Release分支基于Develop分支创建,打完Release分支之后,我们可以在这个Release分支上测试,修改Bug等。同时,其它开发人员可以基于Develop分支新建Feature (记住:一旦打了Release分支之后不要从Develop分支上合并新的改动到Release分支)发布Release分支时,合并Release到Master和Develop, 同时在Master分支上打个Tag记住Release版本号,然后可以删除Release分支了。
-
Hotfix 分支
hotfix分支基于Master分支创建,开发完后需要合并回Master和Develop分支,同时在Master上打一个tag。
Git Flow 命令示例
创建 Devlop
git branch develop
git push -u origin develop
开始 Feature
# 通过develop新建feaeure分支
git checkout -b feature develop
# 或者, 推送至远程服务器:
git push -u origin feature
# 修改md文件
git status
git add .
git commit
完成 Feature
git pull origin develop
git checkout develop
#--no-ff:不使用fast-forward方式合并,保留分支的commit历史
#--squash:使用squash方式合并,把多次分支commit历史压缩为一次
git merge --no-ff feature
git push origin develop
git branch -d some-feature
# 如果需要删除远程feature分支:
git push origin --delete feature
开始 Release
git checkout -b release-0.1.0 develop
完成 Release
git checkout master
git merge --no-ff release-0.1.0
git push
git checkout develop
git merge --no-ff release-0.1.0
git push
git branch -d release-0.1.0
git push origin --delete release-0.1.0
# 合并master/devlop分支之后,打上tag
git tag -a v0.1.0 master
git push --tags
开始 Hotfix
git checkout -b hotfix-0.1.1 master
完成 Hotfix
git checkout master
git merge --no-ff hotfix-0.1.1
git push
git checkout develop
git merge --no-ff hotfix-0.1.1
git push
git branch -d hotfix-0.1.1
git push origin --delete hotfix-0.1.1
git tag -a v0.1.1 master
git push --tags
git文件的4种状态
Untracked
:未跟踪,文件在文件夹中,但并没有加入到git库中,不参与版本控制。通过git add状态变为Staged。
Unmodified
:文件已经入库,未修改,即版本库中的文件快照内容与文件夹中完全一致。如果后续被修改,状态变为Modified;如果使用git rm移出版本库,状态变为Untracked。
Modified
:文件已修改,通过git add进入暂存区,状态变为Staged;或使用git checkout -- filename,用最近提交的版本覆盖这个文件(慎重)。
Staged
:暂存状态,git commit将修改同步到版本库中,版本库中的文件快照与本地文件又变为一致,文件状态变为Unmodifed;git reset HEAD filename,取消暂存,文件状态变为Modified,已修改的内容不变。
通过git status查看文件状态
git commit详解
假设项目文件夹目录结构如下:
git add暂存操作时:
1、为每个文件使用SHA-1哈希算法计算校验和
2、把当前版本的文件快照保存到Git仓库中(blob对象保存文件快照)
3、将校验和加入到暂存区等待提交,下图绿色blob对象6ad4c6就是校验和
git commit提交操作时:
1、先计算每个子目录的校验和,比如images子目录的校验和96b67e,在Git仓库中这些校验和保存为树对象(tree记录目录结构和blob的对象索引)
2、创建一个提交对象(commit),包含指向树对象和指针和所有提交信息(快照等),以及parent(父提交)。对于首次提交的对象,parent为空。
commit规范与提交验证
commit 规范是指 git commit 时填写的描述信息,要符合统一规范。
试想,如果团队成员的 commit 是随意填写的,在协作开发和 review 代码时,其他人根本不知道这个 commit 是完成了什么功能,或是修复了什么 Bug,很难把控进度。
为了直观的看出 commit 的更新内容,开发者社区诞生了一种规范,将 commit 按照功能划分,加一些固定前缀,比如 fix:,feat:,用来标记这个 commit 主要做了什么事情。
目前主流的前缀包括以下部分
:
- build:表示构建,发布版本可用这个
- ci:更新 CI/CD 等自动化配置
- chore:杂项,其他更改
- docs:更新文档
- feat:常用,表示新增功能
- fix:常用:表示修复 bug
- perf:性能优化
- refactor:重构
- revert:代码回滚
- style:样式更改
- test:单元测试更改
规范有了,在流程上对提交信息进行校验就需要用到git钩子-git hook git hook 的作用是在 git 动作发生前后触发自定义脚本。这些动作包括提交,合并,推送等,我们可以利用这些钩子在 git 流程的各个环节实现自己的业务逻辑。
git hook 分为客户端 hook 和服务端 hook。
客户端 hook 主要有四个:
pre-commit
:提交信息前运行,可检查暂存区的代码prepare-commit-msg
:不常用commit-msg
:非常重要,检查提交信息就用这个钩子post-commit
:提交完成后运行
服务端 hook 包括:
pre-receive
:非常重要,推送前的各种检查都在这post-receive
:不常用update
:不常用
git reset详解
git reset 命令用于回退版本,可以指定退回某一次提交的版本,或者说将当前branch重置到另外一个commit,这个动作会对index以及workspace产生影响,具体影响根据参数而定。
reset 命令的原理是根据 commitId
来恢复版本。因为每次提交都会生成一个 commitId,所以说 reset 可以帮你恢复到历史的任何一个版本。一个 commitId 就是一个版本.
commitId 是如何获取的?很简单,用 git log
命令查看提交记录,可以看到 commitId 值,这个值很长,我们取前 7 位即可。
初始状态:
git reset HEAD~1
soft参数
git reset --soft HEAD~1
HEAD指向另一个commit,index和workspace保留重置前状态。
hard参数
git reset --hard HEAD~1
彻底丢掉本地修改,所有都恢复到重置的commit状态。
HEAD^
表示上一个提交,可多次使用。
流程:
# 1. 回退到上次提交
$ git reset HEAD^
# 2. 修改代码... ...
# 3. 加入暂存
$ git add .
# 4. 重新提交
$ git commit -m 'fix: ***'
默认参数
git reset --mixed HEAD~1
workspace不变,保留原有的修改,可以重新git add. reset 还有一个非常重要的特性,就是真正的后退一个版本。
什么意思呢?比如说当前提交,你已经推送到了远程仓库;现在你用 reset 撤回了一次提交,此时本地 git 仓库要落后于远程仓库一个版本。此时你再 push,远程仓库会拒绝,要求你先 pull。
如果你需要远程仓库也后退版本,就需要 -f
参数,强制推送,这时本地代码会覆盖远程代码。
注意,-f
参数非常危险!如果你对 git 原理和命令行不是非常熟悉,切记不要用这个参数。
git revert详解
revert 与 reset 的作用一样,都是恢复版本,但是它们两的实现方式不同。
简单来说,reset 直接恢复到上一个提交,工作区代码自然也是上一个提交的代码;而 revert 是新增一个提交,但是这个提交是使用上一个提交的代码。
适用场景: 如果我们想撤销之前的某一版本,但是又想保留该目标版本后面的版本,记录下这整个版本变动流程,就可以用这种方法。
大致流程:
# 1. 反做,使用“git revert -n
$ git revert -n commit-id
# 2. 重新提交
$ git commit -m 'fix: ***'
git reset和git revert的区别
- git reset 是回滚到对应的commit-id,相当于是删除了commit-id以后的所有的提交,并且不会产生新的commit-id记录,如果要推送到远程服务器的话,需要强制推送-f
- git revert 是反做撤销其中的commit-id,然后重新生成一个commit-id。本身不会对其他的提交commit-id产生影响,如果要推送到远程服务器的话,就是普通的操作git push就好了
Git场景应用
代码拉取
git clone [url]
git clone https://github.com/tianqixin/runoob-git-test\
Cloning into 'runoob-git-test'...\
remote: Enumerating objects: 12, done.\
remote: Total 12 (delta 0), reused 0 (delta 0), pack-reused 12\
Unpacking objects: 100% (12/12), done.
默认情况下,Git 会按照你提供的 URL 所指向的项目的名称创建你的本地项目目录。 通常就是该 URL 最后一个 / 之后的项目名称。如果你想要一个不一样的名字, 你可以在该命令后加上你想要的名称。
例如,以下实例拷贝远程 git 项目,本地项目名为 another-runoob-name:
git clone https://github.com/tianqixin/runoob-git-test another-runoob-name
代码提交
# 1.初始化仓库。
git init
# 2. 修改代码
# 3.添加文件到暂存区。
git add .
# 4.将暂存区内容添加到仓库中。
git commit - 'css/...'
# 5. 先拉取最新代码,如果有冲突解决冲突
git pull
# 6.推到远程仓库
git push
创建分支
在master分支执行git checkout -b work-a
,创建work-a分支.
等同于
git branch work-a //创建分支work-a
git checkout work-a //切换到分支work-a,切换前一定确保当前分支的修改已经提交或缓存了。
合并分支
在git中合并分支有三种方法,分别是merge,rebase,cherry-pick,而其中merge又有三种区别。
快速合并 Fast-forward
有两个分支分别是master和test,他们都指向了A2这个提交,HEAD是一个特殊的指针,他永远指向你当前所在的位置;
如果待合并的分支在当前分支的下游,也就是说没有分叉时,会发生快速合并,从test分支切换到master分支,然后合并test分支。
# 1.创建分支 test,并切换到分支上去
git checkout -b test
# 2. 提交修改内容到暂存区
git add .
# 3. 提交内容到本地仓库
git commit -am 'test utils'
[test 9394cfd] test utils
1 file changed, 25 insertions(+)
create mode 100644 htmlCss/utils/utils.js
# 4.切换到master分支上
git checkout master
# 5.Fast-forward 快速合并分支
git merge test
Updating 7ab6ba6..9394cfd
Fast-forward
htmlCss/utils/utils.js | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
create mode 100644 htmlCss/utils/utils.js
这种方法相当于直接把master分支移动到test分支所在的地方,并移动HEAD指针。
非快速推荐合并
如果我们不想要快速合并,那么我们可以强制指定为非快速合并,只需加上--no-ff
参数
# 1.切换到master分支上
git checkout master
# 2.非快速推荐合并
git merge test-a --no-ff
Auto-merging htmlCss/utils/utils.js
CONFLICT (content): Merge conflict in htmlCss/utils/utils.js
Automatic merge failed; fix conflicts and then commit the result.
如果出现冲突就需要解决。
这种合并方法会在master分支上新建一个提交节点,从而完成合并。
squash
svn的在合并分支时采用的就是这种方式,squash会在当前分支新建一个提交节点
squash和no-ff非常类似,区别只有一点不会保留对合入分支的引用。
把一些不必要commit进行压缩,比如说,你的feature在开发的时候写的commit很乱,那么我们合并的时候不希望把这些历史commit带过来,于是使用–squash进行合并,此时文件已经同合并后一样了,但不移动HEAD,不提交。需要进行一次额外的commit来“总结”一下,然后完成最终的合并。
# 1.切换到master分支上
git checkout master
# 2.squash 合并
git merge test-b --squash
三种合并区别:
rebase
- rebase操作可以把本地未push的分叉提交历史整理成直线;
- rebase的目的是使得我们在查看历史提交的变化时更容易,因为分叉的提交需要三方对比。 应用场景:
C1是线上的版本,在C1的代码上线了之后我们发现了一个bug,于是我们checkout了一个叫做bugFix的分支。与此同时还有新的功能在开发,新的功能提交到了master之后形成了节点C2。这个时候我们在bugFix分支当然可以merge master这没有什么问题,但是也可以rebase master,rebase之后整棵git树会变成这样:
这个结果就好像是我们先到了C2然后checkout出了bugFix分支,然后在bugFix分支上将之前写过的代码重新写了一遍。这样的操作就是变基,当我们rebase了之后再提交合并请求我们的合并记录里面会非常干净,没有多余merge的信息。对于多人协同开发的场景非常有帮助。
master是正常的线上分支,在C2节点处代码上线。上线之后继续开发新的需求checkout了新的分支feature,与此同时master也经过了一些合并,合并了另外的一些改动到了C4节点。之后新的分支feature开发完成了一个重要性能提升的改动C5,这时,我们发现了线上代码的一个bug并且性能不佳,我们需要紧急修复。于是在C5处checkout了新的分支bugFix,我们在bugFix分支当中修复了bug,想要发布上线。
这时候feature分支继续开发到了C6节点,仍然没有开发完成,也没有经过系统测试。所以我们并不希望C6的代码发布上线,我们希望合并进入master的代码之后C5,C7和C8。我们只需要在bugFix分支rebase到master,然后修复冲突之后提交。提交完成了之后,我们再checkout到master把bugFix分支merge进来。整个流程如下:
git checkout bugFix
git rebase master
git checkout master
git merge bugFix
最后我们得到的结果会是这样:
git rebase --onto master feature bugFix
git执行这条命令的时候会先找到feature和bugFix的共同祖先,然后将共同祖先之后的部分rebase到master。
还有一个问题是如果我们要把两个分支合并进master,我们要rebase merge两次不免有些麻烦,我们也可以这么操作。比如我们要把刚才说的feature和bugFix都merge进来,我们可以直接执行git rebase master feature,它会先checkout到feature分支然后执行rebase master的操作,之后我们再checkout到master进行合并即可。也就是说我们在命令当中可以把要执行的执行分支和目标分支都写出来,这样可以一步到位。
比如我现在在master分支,我执行git rebase master bugFix之后会变成这样:
解决分支冲突
假设我们在分支work-b上切出新分支work-b-1,然后在两个分支上都对README.md同一部分进行了不同的修改并提交,然后将work-b-1合并到work-b分支
发现README.md文件有冲突,查看该文件发现内容的差异,HEAD表明work-b分支的修改.
修改README.md后,查看当前状态.
重新提交,解决冲突.
git打版本标签
一般推荐打带附注信息的标签,这样可以最大限度查看标签版本的修改情况。
# 命令格式
git tag -a 标签名 -m "附注信息"
# 示例
git tag -a v0.1.0 -m "完成了文章a和文章b的撰写"
# 查看
$ git show v0.1.0
# 用 git push 将 tag 推到远程
git push origin v0.1.0
这里注意:tag 和在哪个分支创建是没有关系的,tag 只是提交的别名。因此 commit 的能力 tag 均可使用,比如上面说的 git reset
,git revert
命令。
当生产环境出问题,需要版本回退时,可以这样:
$ git revert [pre-tag]
# 若上一个版本是 v1.2.3,则:
$ git revert v1.2.3
面试题解答
Git和SVN有什么区别?
这题上面已经有解答了。
git 快照指的是什么?git 的工作区域由哪三个部分组成?在 git 中,如何提交的版本打标签?
git 快照
快照的基本思想和 immutable.js 类似,使用 immutable 生成的对象都是不可变对象,每次对该对象进行修改都会生成一个新的对象,immutable 会维护一个树形结构,新对象的修改只会添加某些节点,然后返回一个新的指针,并与旧对象共用相同的部分。
git 快照也是这样。只不过是在文件系统层面实现的。我们都知道当你删除一个文件后,文件原来所占的磁盘空间并不是被清空,而是被文件系统标记为已废弃,可修改
的状态,快照的作用就相当于将旧文件所占的空间保留下来,并且保存一个引用,而新文件中会继续使用与旧文件内容相同部分的磁盘空间,不同部分则写入新的磁盘空间。总的来说,git 其实也算是保存 diff 的方式,只不过是在文件系统层次上实现的。
git 的工作区域
- 工作区:用户本地的工作目录
- 暂存区:一个临时的用于放置文件改动的缓存区域
- 版本区:既包含了所有版本以及分支的仓库。该仓库里面的文件能够被 git 所管理,文件的增加、删除、修改都能被 git 所跟踪。
git 的工作流程
- 在工作区添加、修改文件
- 将修改后的文件放入暂存区
- 将暂存区的文件提交到本地仓库
- 将本地仓库的修改推送到远程仓库
在 git 中,如何为提交的版本打标签
-
- 切换到需要打标签的分支上
-
- 敲命令
git tag <name>
可以打上一个新的标签
- 敲命令
-
- 用
git tag
查看所有的标签
- 用
-
- 推送标签
git push --tags
- 推送标签
对 git flow 了解么?应该如何使用?
看文章上面分类分支策略管理 git flow
git pull --rebase 和 git pull 的区别是什么?
首先git rebase 后面应该接branch,而不是origin。我默认这里是rebase远程同名分支,即git rebase origin/BRANCH_NAME
其次,git pull有如下关系:
git pull = git fetch + git merge
git pull --rebase = git fetch + git rebase origin/BRANCH_NAME
不一定严格相等,但效果是等价的。
所以,问题的答案是,git rebase相比git pull,少了git fetch,即前者只会基于已获取的origin分支,而后者会先获取origin分支的最新版本再合并。
提交时发生冲突,你能解释冲突是如何产生的吗?你是如何解决的?
文章上面分类解决分支冲突的方法
如果本次提交误操作,如何撤销?
文章上面分类 git reset
git revert
如果我想修改提交的历史信息,应该用什么命令?
# 将新的修改追加到上一个提交中
git commit --amend
# 提交一个空内容,在没有任何修改又想增加一个提交时可用
git commit --allow-empty
我们已经提交过好多次记录并且已经push到了远程分支
但是发现之前的一些信息弄错了,这是可以通过rebase操作,来修改历史提交信息
$ git rebase -i HEAD~3
# 通过continue命令回到正常状态
$ git rebase --continue
如何查看分支提交的历史记录?查看某个文件的历史记录呢?
git log
会按时间先后顺序列出所有的提交,最近的更新排在最上面。 正如你所看到的,这个命令会列出每个提交的 SHA-1 校验和、作者的名字和电子邮件地址、提交时间以及提交说明。
#查看 提交的历史记录
$ git log
$ git log --oneline --graph
# 查看某个文件的历史记录
$ git log -- begin.txt
怎样将 N 次提交压缩成一次提交?
$ git rebase -i master
什么是 git stash?
git stash
保存当前工作区和暂存区进度(默认只会保存加入到版本管理的文件,即未被追踪的文件不会存储)
git stash 需要在git add之前执行
保存
git stash ----将当前工作区和暂存区进度保存
查看保存列表
git stash list ----查看所有保存的记录列表,记录列表前有{序号}可用于删除或取出。
不可重复恢复
git stash pop stash@{序号} ----恢复,{序号}是可选项,不选会全部恢复。恢复后会从git stash list中删除掉被恢复的项。
可重复恢复
git stash apply stash@{序号} ----恢复,{序号}是可选项,不选会全部恢复。恢复后不会从git stash list中删除掉被恢复的项。
删除某项
git stash drop stash@{序号} ----删除git stash list中的某项保存,{序号}是可选项。
删除所有
git stash clear ----删除git stash list中的所有保存。
描述一下你所使用的分支策略
看文章上面分类分支策略管理 git flow
我们在本地工程常会修改一些配置文件,这些文件不需要被提交,而我们又不想每次执行git status时都让这些文件显示出来,我们该如何操作?
新建一个 .gitignore 文件 例如我们想隐藏所有的 .project 文件,则只需在 .gitignore 里输入 *.project 即可。
我们想隐藏gen目录下的所有的文件,则只需在 .gitignore 里输入 gen/ 即可。
vue通用模板
.DS_Store
node_modules/
/dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
/test/unit/coverage/
/test/e2e/reports/
selenium-debug.log
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
如何把本地仓库的内容推向一个空的远程仓库?
#方法一:
# 1、新建一个代码仓库
# 2、将本地和远程厂库关联起来
git remote add origin git@github.com:地址/远程项目名称.git
# 3、将本地代码推送到库上
git add .
git commit -m ‘first’ -n
git push -u origin master
# 方法二 强行推送 -f
git push origin <分支名> -f