Git学习总结
Git相关
git
常用命令速查表如下所示:
二、工作原理
当我们通过git init
创建或者git clone
一个项目的时候,项目目录会隐藏一个.git
子目录,其作用是用来跟踪管理版本库的
Git
中所有数据在存储前都计算校验和,然后以校验和来引用,所以在我们修改或者删除文件的时候,git
能够知道
Git
用以计算校验和的机制叫做 SHA-1 散列(hash,哈希), 这是一个由 40 个十六进制字符(0-9 和 a-f)组成字符串,基于 Git 中文件的内容或目录结构计算出来,如下:
24b9da6552252987aa493b52f8696cd6d3b00373
当我们修改文件的时候,git
就会修改文件的状态,可以通过git status
进行查询,状态情况如下:
- 已修改(modified):表示修改了文件,但还没保存到数据库中。
- 已暂存(staged):表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中。
- 已提交(committed):表示数据已经安全的保存在本地数据库中。
文件状态对应的,不同状态的文件在Git
中处于不同的工作区域,主要分成了四部分:
- 工作区:相当于本地写代码的区域,如 git clone 一个项目到本地,相当于本地克隆了远程仓库项目的一个副本
- 暂存区:暂存区是一个文件,保存了下次将提交的文件列表信息,一般在 Git 仓库目录中
- 本地仓库:提交更新,找到暂存区域的文件,将快照永久性存储到 Git 本地仓库
- 远程仓库:远程的仓库,如 github
三、命令
从上图可以看到,git
日常简单的使用就只有上图6个命令:
- add
- commit
- push
- pull
- clone
- checkout
但实际上还有很多命令,如果想要熟练使用,还有60个多命令,通过这些命令的配合使用,能够提高个人工作效率和团队协助能力
参考文献
面试官:说说Git常用的命令有哪些?
一、前言
git
的操作可以通过命令的形式如执行,日常使用就如下图6个命令即可
实际上,如果想要熟练使用,超过60多个命令需要了解,下面则介绍下常见的的git
命令
二、有哪些
配置
Git
自带一个 git config
的工具来帮助设置控制 Git
外观和行为的配置变量,在我们安装完git
之后,第一件事就是设置你的用户名和邮件地址
后续每一个提交都会使用这些信息,它们会写入到你的每一次提交中,不可更改
设置提交代码时的用户信息命令如下:
- git config [--global] user.name "[name]"
- git config [--global] user.email "[email address]"
启动
一个git
项目的初始有两个途径,分别是:
- git init [project-name]:创建或在当前目录初始化一个git代码库
- git clone url:下载一个项目和它的整个代码历史
日常基本操作
在日常工作中,代码常用的基本操作如下:
-
git init 初始化仓库,默认为 master 分支
-
git add . 提交全部文件修改到缓存区
-
git add <具体某个文件路径+全名> 提交某些文件到缓存区
-
git diff 查看当前代码 add后,会 add 哪些内容
-
git diff --staged查看现在 commit 提交后,会提交哪些内容
-
git status 查看当前分支状态
-
git pull <远程仓库名> <远程分支名> 拉取远程仓库的分支与本地当前分支合并
-
git pull <远程仓库名> <远程分支名>:<本地分支名> 拉取远程仓库的分支与本地某个分支合并
-
git commit -m "<注释>" 提交代码到本地仓库,并写提交注释
-
git commit -v 提交时显示所有diff信息
-
git commit --amend [file1] [file2] 重做上一次commit,并包括指定文件的新变化
本地开发代码已提交后发现代码有问题或者漏交一些文件
关于提交信息的格式,可以遵循以下的规则:
- feat: 新特性,添加功能
- fix: 修改 bug
- refactor: 代码重构
- docs: 文档修改
- style: 代码格式修改, 注意不是 css 修改
- test: 测试用例修改
- chore: 其他修改, 比如构建流程, 依赖管理
分支操作
- git branch 查看本地所有分支
- git branch -r 查看远程所有分支
- git branch -a 查看本地和远程所有分支
- git merge <分支名> 合并分支
- git merge --abort 合并分支出现冲突时,取消合并,一切回到合并前的状态
- git branch <新分支名> 基于当前分支,新建一个分支
- git checkout --orphan <新分支名> 新建一个空分支(会保留之前分支的所有文件)
- git branch -D <分支名> 删除本地某个分支
- git push <远程库名> :<分支名> 删除远程某个分支
- git branch <新分支名称> <提交ID> 从提交历史恢复某个删掉的某个分支
- git branch -m <原分支名> <新分支名> 分支更名
- git checkout <分支名> 切换到本地某个分支
- git checkout <远程库名>/<分支名> 切换到线上某个分支
- git checkout -b <新分支名> 把基于当前分支新建分支,并切换为这个分支
远程同步
远程操作常见的命令:
- git fetch [remote] 下载远程仓库的所有变动
- git remote -v 显示所有远程仓库
- git pull [remote] [branch] 拉取远程仓库的分支与本地当前分支合并
- git fetch 获取线上最新版信息记录,不合并
- git push [remote] [branch] 上传本地指定分支到远程仓库
- git push [remote] --force 强行推送当前分支到远程仓库,即使有冲突
- git push [remote] --all 推送所有分支到远程仓库
撤销
- git checkout [file] 恢复暂存区的指定文件到工作区
- git checkout [commit] [file] 恢复某个commit的指定文件到暂存区和工作区
- git checkout . 恢复暂存区的所有文件到工作区
- git reset [commit] 重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变
- git reset --hard 重置暂存区与工作区,与上一次commit保持一致
- git reset [file] 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变
- git revert [commit] 后者的所有变化都将被前者抵消,并且应用到当前分支
reset
:真实硬性回滚,目标版本后面的提交记录全部丢失了
revert
:同样回滚,这个回滚操作相当于一个提价,目标版本后面的提交记录也全部都有
存储操作
你正在进行项目中某一部分的工作,里面的东西处于一个比较杂乱的状态,而你想转到其他分支上进行一些工作,但又不想提交这些杂乱的代码,这时候可以将代码进行存储
- git stash 暂时将未提交的变化移除
- git stash pop 取出储藏中最后存入的工作状态进行恢复,会删除储藏
- git stash list 查看所有储藏中的工作
- git stash apply <储藏的名称> 取出储藏中对应的工作状态进行恢复,不会删除储藏
- git stash clear 清空所有储藏中的工作
- git stash drop <储藏的名称> 删除对应的某个储藏
三、总结
git
常用命令速查表如下所示:
面试官:说说 git 发生冲突的场景?如何解决?
一、是什么
一般情况下,出现分支的场景有如下:
- 多个分支代码合并到一个分支时
- 多个分支向同一个远端分支推送
具体情况就是,多个分支修改了同一个文件(任何地方)或者多个分支修改了同一个文件的名称
如果两个分支中分别修改了不同文件中的部分,是不会产生冲突,直接合并即可
应用在命令中,就是push
、pull
、stash
、rebase
等命令下都有可能产生冲突情况,从本质上来讲,都是merge
和patch
(应用补丁)时产生冲突
二、分析
在本地主分值master
创建一个a.txt
文件,文件起始位置写上master commit
,如下:
然后提交到仓库:
- git add a.txt
- git commit -m 'master first commit'
创建一个新的分支featurel1
分支,并进行切换,如下:
git checkout -b featurel1
然后修改a.txt
文件首行文字为 featurel commit
,然后添加到暂存区,并开始进行提交到仓库:
- git add a.txt
- git commit -m 'featurel first change'
然后通过git checkout master
切换到主分支,通过git merge
进行合并,发现不会冲突
此时a.txt
文件的内容变成featurel commit
,没有出现冲突情况,这是因为git
在内部发生了快速合并
如果当前分支的每一个提交(commit)都已经存在另一个分支里了,git 就会执行一个“快速向前”(fast forward)操作
git 不创建任何新的提交(commit),只是将当前分支指向合并进来的分支
如果此时切换到featurel
分支,将文件的内容修改成featrue second commit
,然后提交到本地仓库
然后切换到主分支,如果此时在a.txt
文件再次修改,修改成mastet second commit
,然后再次提交到本地仓库
此时,master
分支和feature1
分支各自都分别有新的提交,变成了下图所示:
这种情况下,无法执行快速合并,只能试图把各自的修改合并起来,但这种合并就可能会有冲突
现在通过git merge featurel
进行分支合并,如下所示:
从冲突信息可以看到,a.txt
发生冲突,必须手动解决冲突之后再提交
而git status
同样可以告知我们冲突的文件:
打开a.txt
文件,可以看到如下内容:
git
用<<<<<<<
,=======
,>>>>>>>
标记出不同分支的内容:
- <<<<<<< 和 ======= 之间的区域就是当前更改的内容
- ======= 和 >>>>>>> 之间的区域就是传入进来更改的内容
现在要做的事情就是将冲突的内容进行更改,对每个文件使用 git add
命令来将其标记为冲突已解决。 一旦暂存这些原本有冲突的文件,Git
就会将它们标记为冲突已解决然后再提交:
- git add a.txt
- git commit -m "conflict fixed"
此时master
分支和feature1
分支变成了下图所示:
使用git log
命令可以看到合并的信息:
三、总结
当Git
无法自动合并分支时,就必须首先解决冲突,解决冲突后,再提交,合并完成
解决冲突就是把Git
合并失败的文件手动编辑为我们希望的内容,再提交
参考文献
面试官:说说Git中 fork, clone,branch这三个概念,有什么区别?
一、是什么
fork
fork
,英语翻译过来就是叉子,动词形式则是分叉,如下图,从左到右,一条直线变成多条直线
转到git
仓库中,fork
则可以代表分叉、克隆 出一个(仓库的)新拷贝
包含了原来的仓库(即upstream repository,上游仓库)所有内容,如分支、Tag、提交
如果想将你的修改合并到原项目中时,可以通过的 Pull Request 把你的提交贡献回 原仓库
clone
clone
,译为克隆,它的作用是将文件从远程代码仓下载到本地,从而形成一个本地代码仓
执行clone
命令后,会在当前目录下创建一个名为xxx
的目录,并在这个目录下初始化一个 .git
文件夹,然后从中读取最新版本的文件的拷贝
默认配置下远程 Git
仓库中的每一个文件的每一个版本都将被拉取下来
branch
branch
,译为分支,其作用简单而言就是开启另一个分支, 使用分支意味着你可以把你的工作从开发主线上分离开来,以免影响开发主线
Git
处理分支的方式十分轻量,创建新分支这一操作几乎能在瞬间完成,并且在不同分支之间的切换操作也是一样便捷
在我们开发中,默认只有一条master
分支,如下图所示:
通过git branch
可以创建一个分支,但并不会自动切换到新分支中去
通过git checkout
可以切换到另一个testing
分支
二、如何使用
fork
当你在github
发现感兴趣开源项目的时候,可以通过点击github
仓库中右上角fork
标识的按钮,如下图:
点击这个操作后会将这个仓库的文件、提交历史、issues和其余东西的仓库复制到自己的github
仓库中,而你本地仓库是不会存在任何更改
然后你就可以通过git clone
对你这个复制的远程仓库进行克隆
后续更改任何东西都可以在本地完成,如git add
、git commit
一系列的操作,然后通过push
命令推到自己的远程仓库
如果希望对方接受你的修改,可以通过发送pull requests
给对方,如果对方接受。则会将你的修改内容更新到仓库中
整体流程如下图:
clone
在github
中,开源项目右侧存在code
按钮,点击后则会显示开源项目url
信息,如下图所示:
通过git clone xxx
则能完成远程项目的下载
branch
可通过git branch
进行查看当前的分支状态,
如果给了--list
,或者没有非选项参数,现有的分支将被列出;当前的分支将以绿色突出显示,并标有星号
以及通过git branch
创建一个新的分支出来
三、区别
其三者区别如下:
- fork 只能对代码仓进行操作,且 fork 不属于 git 的命令,通常用于代码仓托管平台的一种“操作”
- clone 是 git 的一种命令,它的作用是将文件从远程代码仓下载到本地,从而形成一个本地代码仓
- branch 特征与 fork 很类似,fork 得到的是一个新的、自己的代码仓,而 branch 得到的是一个代码仓的一个新分支
参考文献
说说对git pull 和 git fetch 的理解?有什么区别?
一、是什么
先回顾两个命令的定义
- git fetch 命令用于从另一个存储库下载对象和引用
- git pull 命令用于从另一个存储库或本地分支获取并集成(整合)
再来看一次git
的工作流程图,如下所示:
可以看到,git fetch
是将远程主机的最新内容拉到本地,用户在检查了以后决定是否合并到工作本机分支中
而git pull
则是将远程主机的最新内容拉下来后直接合并,即:git pull = git fetch + git merge
,这样可能会产生冲突,需要手动解决
在我们本地的git
文件中对应也存储了git
本地仓库分支的commit ID
和 跟踪的远程分支的commit ID
,对应文件如下:
- .git/refs/head/[本地分支]
- .git/refs/remotes/[正在跟踪的分支]
使用 git fetch
更新代码,本地的库中master
的commitID
不变
但是与git
上面关联的那个orign/master
的commit ID
发生改变
这时候我们本地相当于存储了两个代码的版本号,我们还要通过merge
去合并这两个不同的代码版本
也就是fetch
的时候本地的master
没有变化,但是与远程仓关联的那个版本号被更新了,接下来就是在本地merge
合并这两个版本号的代码
相比之下,使用git pull
就更加简单粗暴,会将本地的代码更新至远程仓库里面最新的代码版本,如下图:
二、用法
一般远端仓库里有新的内容更新,当我们需要把新内容下载的时候,就使用到git pull
或者git fetch
命令
fetch
用法如下:
git fetch <远程主机名> <远程分支名>:<本地分支名>
例如从远程的origin
仓库的master
分支下载代码到本地并新建一个temp
分支
git fetch origin master:temp
如果上述没有冒号,则表示将远程origin
仓库的master
分支拉取下来到本地当前分支
这里git fetch
不会进行合并,执行后需要手动执行git merge
合并,如下:
git merge temp
pull
两者的用法十分相似,pull
用法如下:
git pull <远程主机名> <远程分支名>:<本地分支名>
例如将远程主机origin
的master
分支拉取过来,与本地的branchtest
分支合并,命令如下:
git pull origin master:branchtest
同样如果上述没有冒号,则表示将远程origin
仓库的master
分支拉取下来与本地当前分支合并
三、区别
相同点:
- 在作用上他们的功能是大致相同的,都是起到了更新代码的作用
不同点:
- git pull是相当于从远程仓库获取最新版本,然后再与本地分支merge,即git pull = git fetch + git merge
- 相比起来,git fetch 更安全也更符合实际要求,在 merge 前,我们可以查看更新情况,根据实际情况再决定是否合并
参考文献
- https://zhuanlan.zhihu.com/p/123370920
- https://segmentfault.com/a/1190000017030384
- https://juejin.cn/post/6844903921794859021
面试官:说说你对git rebase 和 git merge的理解?区别?
一、是什么
在使用 git
进行版本管理的项目中,当完成一个特性的开发并将其合并到 master
分支时,会有两种方式:
- git merge
- git rebase
git rebase
与 git merge
都有相同的作用,都是将一个分支的提交合并到另一分支上,但是在原理上却不相同
用法上两者也十分的简单:
git merge
将当前分支合并到指定分支,命令用法如下:
git merge xxx
git rebase
将当前分支移植到指定分支或指定commit
之上,用法如下:
git rebase -i <commit>
常见的参数有--continue
,用于解决冲突之后,继续执行rebase
git rebase --continue
二、分析
git merge
通过git merge
将当前分支与xxx
分支合并,产生的新的commit
对象有两个父节点
如果“指定分支”本身是当前分支的一个直接子节点,则会产生快照合并
举个例子,bugfix
分支是从maste
r分支分叉出来的,如下所示:
合并 bugfix
分支到master
分支时,如果master
分支的状态没有被更改过,即 bugfix
分支的历史记录包含master
分支所有的历史记录
所以通过把master
分支的位置移动到bugfix
的最新分支上,就完成合并
如果master
分支的历史记录在创建bugfix
分支后又有新的提交,如下情况:
这时候使用git merge
的时候,会生成一个新的提交,并且master
分支的HEAD
会移动到新的分支上,如下:
从上面可以看到,会把两个分支的最新快照以及二者最近的共同祖先进行三方合并,合并的结果是生成一个新的快照
git rebase
同样,master
分支的历史记录在创建bugfix
分支后又有新的提交,如下情况:
通过git rebase
,会变成如下情况:
在移交过程中,如果发生冲突,需要修改各自的冲突,如下:
rebase
之后,master
的HEAD
位置不变。因此,要合并master
分支和bugfix
分支
从上面可以看到,rebase
会找到不同的分支的最近共同祖先,如上图的B
然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件(老的提交X
和Y
也没有被销毁,只是简单地不能再被访问或者使用)
然后将当前分支指向目标最新位置D
, 然后将之前另存为临时文件的修改依序应用
三、区别
从上面可以看到,merge
和rebasea
都是合并历史记录,但是各自特性不同:
merge
通过merge
合并分支会新增一个merge commit
,然后将两个分支的历史联系起来
其实是一种非破坏性的操作,对现有分支不会以任何方式被更改,但是会导致历史记录相对复杂
rebase
rebase
会将整个分支移动到另一个分支上,有效地整合了所有分支上的提交
主要的好处是历史记录更加清晰,是在原有提交的基础上将差异内容反映进去,消除了 git merge
所需的不必要的合并提交
参考文献
- https://zhuanlan.zhihu.com/p/361182707
- https://yuweijun.github.io/git-zh/1-git-branching.html#_rebasing
- https://backlog.com/git-tutorial/cn/stepup/stepup1_4.html
面试官:说说你对git reset 和 git revert 的理解?区别?
一、是什么
git reset
reset
用于回退版本,可以遗弃不再使用的提交
执行遗弃时,需要根据影响的范围而指定不同的参数,可以指定是否复原索引或工作树内容
git revert
在当前提交后面,新增一次提交,抵消掉上一次提交导致的所有变化,不会改变过去的历史,主要是用于安全地取消过去发布的提交
二、如何用
git reset
当没有指定ID
的时候,默认使用HEAD
,如果指定ID
,那么就是基于指向ID
去变动暂存区或工作区的内容
// 没有指定ID, 暂存区的内容会被当前ID版本号的内容覆盖,工作区不变
git reset
// 指定ID,暂存区的内容会被指定ID版本号的内容覆盖,工作区不变
git reset <ID>
日志ID
可以通过查询,可以git log
进行查询,如下:
commit a7700083ace1204ccdff9f71631fb34c9913f7c5 (HEAD -> master)
Author: linguanghui <linguanghui@baidu.com>
Date: Tue Aug 17 22:34:40 2021 +0800
second commit
commit e31118663ce66717edd8a179688a7f3dde5a9393
Author: linguanghui <linguanghui@baidu.com>
Date: Tue Aug 17 22:20:01 2021 +0800
first commit
常见命令如下:
- --mixed(默认):默认的时候,只有暂存区变化
- --hard参数:如果使用 --hard 参数,那么工作区也会变化
- --soft:如果使用 --soft 参数,那么暂存区和工作区都不会变化
git revert
跟git reset
用法基本一致,git revert
撤销某次操作,此次操作之前和之后的 commit
和history
都会保留,并且把这次撤销,作为一次最新的提交,如下:
git revert <commit_id>
如果撤销前一个版本,可以通过如下命令:
git revert HEAD
撤销前前一次,如下:
git revert HEAD^
三、区别
撤销(revert)被设计为撤销公开的提交(比如已经push)的安全方式,git reset
被设计为重设本地更改
因为两个命令的目的不同,它们的实现也不一样:重设完全地移除了一堆更改,而撤销保留了原来的更改,用一个新的提交来实现撤销
两者主要区别如下:
- git revert是用一次新的commit来回滚之前的commit,git reset是直接删除指定的commit
- git reset 是把HEAD向后移动了一下,而git revert是HEAD继续前进,只是新的commit的内容和要revert的内容正好相反,能够抵消要被revert的内容
- 在回滚这一操作上看,效果差不多。但是在日后继续 merge 以前的老版本时有区别
git revert是用一次逆向的commit“中和”之前的提交,因此日后合并老的branch时,之前提交合并的代码仍然存在,导致不能够重新合并
但是git reset是之间把某些commit在某个branch上删除,因而和老的branch再次merge时,这些被回滚的commit应该还会被引入
- 如果回退分支的代码以后还需要的情况则使用
git revert
, 如果分支是提错了没用的并且不想让别人发现这些错误代码,则使用git reset
参考文献
- https://juejin.cn/post/6844903542931587086
- https://marklodato.github.io/visual-git-guide/index-zh-cn.html#reset
面试官:说说你对git stash 的理解?应用场景?
一、是什么
stash,译为存放,在 git 中,可以理解为保存当前工作进度,会把暂存区和工作区的改动进行保存,这些修改会保存在一个栈上
后续你可以在任何时候任何分支重新将某次的修改推出来,重新应用这些更改的代码
默认情况下,git stash
会缓存下列状态的文件:
- 添加到暂存区的修改(staged changes)
- Git跟踪的但并未添加到暂存区的修改(unstaged changes)
但以下状态的文件不会缓存:
- 在工作目录中新的文件(untracked files)
- 被忽略的文件(ignored files)
如果想要上述的文件都被缓存,可以使用-u
或者--include-untracked
可以工作目录新的文件,使用-a
或者--all
命令可以当前目录下的所有修改
二、如何使用
关于git stash
常见的命令如下:
- git stash
- git stash save
- git stash list
- git stash pop
- git stash apply
- git stash show
- git stash drop
- git stash clear
git stash
保存当前工作进度,会把暂存区和工作区的改动保存起来
git stash save
git stash save
可以用于存储修改.并且将git
的工作状态切回到HEAD
也就是上一次合法提交上
如果给定具体的文件路径,git stash
只会处理路径下的文件.其他的文件不会被存储,其存在一些参数:
- --keep-index 或者 -k 只会存储为加入 git 管理的文件
- --include-untracked 为追踪的文件也会被缓存,当前的工作空间会被恢复为完全清空的状态
- -a 或者 --all 命令可以当前目录下的所有修改,包括被 git 忽略的文件
git stash list
显示保存进度的列表。也就意味着,git stash
命令可以多次执行,当多次使用git stash
命令后,栈里会充满未提交的代码,如下:
其中,stash@{0}
、stash@{1}
就是当前stash
的名称
git stash pop
git stash pop
从栈中读取最近一次保存的内容,也就是栈顶的stash
会恢复到工作区
也可以通过 git stash pop
+ stash
名字执行恢复哪个stash
恢复到当前目录
如果从stash
中恢复的内容和当前目录中的内容发生了冲突,则需要手动修复冲突或者创建新的分支来解决冲突
git stash apply
将堆栈中的内容应用到当前目录,不同于git stash pop
,该命令不会将内容从堆栈中删除
也就说该命令能够将堆栈的内容多次应用到工作目录中,适应于多个分支的情况
同样,可以通过git stash apply
+ stash
名字执行恢复哪个stash
恢复到当前目录
git stash show
查看堆栈中最新保存的stash
和当前目录的差异
通过使用git stash show -p
查看详细的不同
通过使用git stash show stash@{1}
查看指定的stash
和当前目录差异
git stash drop
git stash drop
+ stash
名称表示从堆栈中移除某个指定的stash
git stash clear
删除所有存储的进度
三、应用场景
当你在项目的一部分上已经工作一段时间后,所有东西都进入了混乱的状态, 而这时你想要切换到另一个分支或者拉下远端的代码去做一点别的事情
但是你创建一次未完成的代码的commit
提交,这时候就可以使用git stash
例如以下场景:
当你的开发进行到一半,但是代码还不想进行提交 ,然后需要同步去关联远端代码时.如果你本地的代码和远端代码没有冲突时,可以直接通过git pull
解决
但是如果可能发生冲突怎么办.直接git pull
会拒绝覆盖当前的修改,这时候就可以依次使用下述的命令:
- git stash
- git pull
- git stash pop
或者当你开发到一半,现在要修改别的分支问题的时候,你也可以使用git stash
缓存当前区域的代码
- git stash:保存开发到一半的代码
- git commit -m '修改问题'
- git stash pop:将代码追加到最新的提交之后