Git教程
前言
在阅读本篇博文前,请确认自己有对版本管理的基本概念,下面的内容会默认读者已经了解过版本管理相关内容,或者在代码编写中曾经使用过GitHub、Git等工具。
分类
版本管理是软件开发过程中不可缺少的一环,市面上常见的版本管理软件一般分为两种:
- 集中式管理:代表产品 SVN
优点:简单,server-client一对多模式,使用时对client来说只需将修改内容推送和拉取。
缺点:存在单体故障问题,当server故障时,所有的client服务崩溃。 - 分布式管理:代表产品 Git
优点:使用者本地保存完整版本库,当远端仓库故障时不影响本地仓库,需要共享时同步进度即可。
缺点:当拉取和推送时需要同步进度,手动处理内容冲突情况。
实际的开发中Git用的更多一点,因此下面的内容只针对Git做相关介绍。
安装
Git是一款客户端软件,需要安装在使用者的电脑上,并配置好环境才能使用:
- 官网下载并安装,安装过程很简单。
- 安装完成后配置环境变量,将git的路径添加到环境变量Path,添加环境变量不会的可以搜教程,很多的。
- 验证安装和环境是否配置成功:在文件夹或桌面空白处右键,选择菜单中的
Open git bash here
打开git命令行窗口,这个窗口也是我们之后使用git工具常用的窗口,下文说的命令行统一指的是这个git bash窗口。 - 在命令行中输入
git -v
命令查看git版本,当回显包含git版本信息时说明git已经安装完成。这里说的使用git bash命令行不是必须的,如果你习惯使用cmd或PowerShell你当然可以直接用他们的命令行来做git的相关操作,因为我们已经将git添加到环境变量中了。
使用方式
Git的使用方式很多样:
- 命令行:包括cmd命令行,git bash命令行,IDE中的terminal终端命令行都可直接使用。
- GUI:图形化界面的Git,需要的话可以在官网下载(Download那里)。
- IDE插件:将git作为插件集成在IDE上使用,例如VSCode、IDEA的git插件。
可以根据自己的喜好选择,建议使用命令行方式,能熟悉git命令的同时,对于Shell命令也了解一些,在一些只支持命令行的服务器上也能使用。
可以安装posh-Git模块美化命令行,高亮显示当前目录和分支及分支状态,安装教程参考 地址1或 地址2,地址1是项目github地址,访问不了的可以访问地址2,是CSDN对github地址访问加速后的地址。
配置
- 配置用户名
打开命令行终端,运行如下命令如果名字中包含空格则名字必须加上双引号,加上双引号是为了区分空格的作用,下面的邮箱同理。1
git config --global user.name exampleName # exampleName是自定义的用户名
参数说明:
- 省略(
Local
): 本地配置,只对本地仓库有效 --global
:全局配置,所有仓库生效--system
:系统配置,对所有用户生效(不太用)
- 配置邮箱下面两条命令也是比较常用的:
1
git config --global user.email example@example.com # example@example.com是自定义邮箱
1
2git config --global credential.helper store # 保存用户名和密码
git config --global --list # 查看配置信息
新建仓库
git配置完成后就可以在本地新建自己的仓库并开始Git版本管理了。
- 方式1:本地创建仓库
创建一个文件夹,命名为自己想要的仓库名,进入命令行进入文件夹目录,使用下面的命令初始化仓库:或者不创建文件夹,在工作目录使用下面命令创建仓库,自动生成仓库文件夹1
git init # 本地直接创建仓库
1
git init exampleRepoName
git init
与git init exampleRepoName
的区别:前者是以当前目录初始化仓库,当前目录为仓库根目录,后者是在当前目录下创建仓库,名字为exampleRepoName,exampleRepoName文件夹才是仓库根目录,可以通过看.git
目录在哪来快速判断仓库根目录所在。
上面两条命令都会初始化仓库,生成.git
目录用来保存仓库的相关信息。.git
目录默认是隐藏的,要查看可通过下面命令:
1 | 查看所有文件信息,包含隐藏文件 |
当你不了解此目录中文件内容作用时,不要私自修改这个目录中内容,可能会导致仓库不可用。
- 方式2:从远端仓库克隆
在git配置完成后,使用下面命令将远端仓库拉取到本地称为本地仓库。拉取完成后,进入仓库目录也可以找到1
git clone exampleURL # exampleURL为远端仓库的clone地址
.git
目录,同样是隐藏的。
注意:要成功执行git clone前提是配置好用户名和密码或者SSH密钥。
仓库结构
仓库可以分为三个部分:工作区,暂存区,版本库。
- 工作区:
.git
所在目录为工作区,工作目录,主要存放开发的代码。 - 暂存区:
.git/index
文件为暂存区,临时保存修改文件的地方。 - 版本库:
.git/objects
版本库,.git/
目录保存配置信息,日志信息和文件版本信息等。
数据流:
- 工作区 — git add —> 暂存区 — git commit —> 版本库
整个过程并不像表面看起来的“提交”过程,更像是“同步”的过程,git add
是让暂存区同工作区同步,commit
是让版本库和暂存区同步。
文件状态
在工作目录使用下面命令可以查看目录下文件的状态,此命令非常常用:
1 | git status # 查看状态 |
文件的几个状态:
- untracked: 新创建未被Git仓库进行版本控制,红色字体显示。
- unmodified:文件已被Git进行版本控制,且版本库中文件与工作区文件内容一致。
- Modified:文件在工作区被修改了,但还未将修改内容提交到暂存区。
- Staged:文件已被提交到暂存区,但还未将修改内容提交到版本库。
- Deleted:文件已被从工作区删除,但还没有将删除操作提交。
git add
将工作区的文件修改提交到暂存区可以使用下面的命令:
1 | git add exampleFile |
提交完成后使用git status
命令查看文件状态可以看到文件名变为绿色,此时此文件就已被Git纳入版本控制。
如果想撤销刚才的git add
操作,可以使用这条命令(本质上是将修改的内容从暂存区删除):
1 | git rm --cached exampleFile |
同时git add还有更多快捷用法,例如:
1 | git add . # 添加当前目录所有文件 |
在仓库目录中使用下面命令可以查看工作区和暂存区文件:
1 | ls # 查看工作区文件 |
git commit
文件的修改经过git add
从工作区提交到暂存区,接下来应该将修改内容添加到版本库中,通过git commit
命令:
1 | git commit -m "commit discription" # 注意:只能提交已经进入暂存区的文件 |
如果不加 -m
参数则Git会自动进入一个交互式文本编辑界面,默认使用 vim,在vim界面中方向键移动光标,i
键进入编辑模式,编辑完commit描述信息后,esc
键回到命令模式,:wq
保存退出,提交就完成了。
你也可以在文件修改后直接使用下面的命令,同时完成提交暂存和提交commit的工作:
1 | git commit -a -m "commit discription" |
Git的版本控制是通过回退到指定commit版本来实现的,根据回退时不同的工作区暂存区保留策略分为三种模式:
--soft
: 回退到指定版本,保留当前工作区和暂存区内容。--hard
:回退到指定版本,不保留当前工作区和暂存区内容。--mixed
:回退到指定版本,不保留当前暂存区内容,只保留当前工作区内容。
使用方式:1
2git reset --soft <commit版本id>` # 回退到指定的commit版本,保留当前工作区和暂存区内容。
git reset --hard HEAD^ # HEAD^表示上一个commit版本 回退到上一个commit版本,不保留当前工作区和暂存区内容。commit版本id可以通过这俩命令来查看:Git中所有操作都是可以回溯的,如果不小心使用了
git reset --hard
模式清空了工作区和暂存区,可以通过git reflog
查看操作的历史记录找到误操作之前的版本号,再通过git reset
命令回退到指定版本即可。上述的1
2git log # 查看commit历史记录
git log --oneline # 查看缩略commit历史记录,显示出的第一个参数是commit版本id,第二个参数是commit描述git reset
方法用于在本地回滚版本,如果你已经将commit通过git push提交到远端仓库,则应使用git revert
代替git reset
来回滚版本,如果不小心使用了git reset
回滚了,此时只是你的本地仓库回滚了,而远端仓库没有回滚,当git push提交时会提醒你commit冲突,这是通过git push --force
的方式强制提交,用本地提交强制替换远端提交记录。
git diff
在Git使用过程中由于我们是通过同步的方式来共享仓库,因此当一个仓库被多个人共享的时候就会出现文件冲突的场景,这时候就需要使用git diff
命令来查看差异后处理冲突。git diff
可以查看不同版本差异,查看工作区、暂存区、版本库间的差异,查看不同分支的差异,一般通过可视化工具来看更为直观,不过掌握git diff
命令行方式能帮助我们在一些不支持可视化工具的场景来使用。git diff
的也是平时较为常用的一个命令:
1 | git diff # 无参数时是查看工作区和暂存区间的差异,红字表示删除的内容,绿色表示添加的内容 |
删除
本地仓库中的删除操作不是简单的删除文件,要根据文件所在空间来分情况:
- 要删除的文件在工作区,且没有通过
git add
命令提交暂存区,即文件还没有被Git纳入版本管理,此时文件的删除最简单,linux上直接使用rm example.txt
命令删除即可,windows上直接右键删除放回收站即可。 - 要删除的文件已经通过
git add
命令提交到过暂存区,此时文件已被Git纳入版本管理,要删除此文件就需要先在工作区删除(删除后使用git status
可看到被删除的文件显示为红色),之后使用git add example.txt
将删除操作提交到暂存区,暂存区就会自动清理暂存区中相应的这个文件(使用git ls-files
可以查看暂存区的文件)。这个过程也可以使用下面命令来代替,直接完成了从工作区和暂存区中删除的操作:1
git rm example.txt
- commit提交删除操作到版本库中。
git rm的命令还有其他的用法,例如:
1 | git rm --cached example.txt # 从暂存区中删除,但是保留在工作区中 |
忽略
我们的项目中不会完全都是代码文件,有时会有这样一些文件我们不想让其被Git追踪纳入版本管理:
- 系统或软件自动生成的文件。
- 编译产生的中间文件和结果文件。
- 运行时生成的日志、缓存、临时文件。
- 涉及身份密码口令密钥等敏感信息文件。
Git针对我们的这个需求提供了一种忽略策略,即使用.gitignore
文件。.gitignore
文件是一个文本文件,位于仓库根目录。
Git不会对.gitignore
中声明的文件或目录进行版本控制。
举个例子:我们有两个文件example.txt
和example.log
,我们想要Git忽略example.log
不对其进行版本控制,那么我们就要在example.log
提交版本库之前将example.log
信息添加到.gitignore
中,这样Git在进行版本控制时只会追踪example.txt
而忽略example.log
,之后对example.log
的修改都不会被Git追踪。
这里需要注意的是.gitignore
忽略生效的前提是,要忽略的文件还没有被添加到版本库中,如果已经添加到版本库中的文件,再添加到.gitignore
是没有作用的,如果想放弃Git对此文件的追踪,就应该使用git rm --cached example.log
命令将此文件从版本库中删除,但保留在工作区中,此时将example.log
信息添加到.gitignore
中就会生效,之后此文件的修改就不会被跟踪了。
git版本控制中不会跟踪空文件夹,只有当文件夹中包含文件时才会被跟踪纳入版本控制。
说了这么多修改.gitignore
的操作,那么到底应该如何修改,在其中添加规则的语法是怎样?.gitignore
文件匹配规则:
- 从上至下逐行匹配,每一行表示一个忽略模式。
#
开头的行表示注释行。- 使用标准的Blob模式匹配(即简化版的正则表达式)。
- 两个
*
,即**
表示匹配任意的中间目录。 - 感叹号
!
表示取反。
举个例子Github上提供了各种常用语言的1
2
3
4
5
6
7
8
9
10
11
12# 忽略所有.a文件
*.a
# 忽略所有的.a文件,但lib.a例外
!lib.a
# 忽略当前目录下的TODO文件,不包含子目录下的TODO文件
/TODO
# 忽略任何目录下名为build的文件夹
build/
# 忽略 doc目录下的txt文件 但不忽略 doc的子目录中的txt文件
doc/*.txt
# 忽略 doc/目录及其子目录下的所有.pdf文件
doc/**/*.pdf.gitignore
文件模板,在初建仓库的时候会让你选择是否添加,也可以在使用过程中自行修改。
SSH配置
Github代码托管平台不用说了吧,大名鼎鼎,“全球最大同性交友平台”(手动狗头)。
Github进入想要拉取到本地的仓库页面,点击绿色的Code按钮,他会提供多种拉取方式,可以直接下载压缩包,在本地解压打开就是仓库,也可以通过给出的https方式或ssh方式从命令行使用git clone url
方式拉取远程仓库到本地。
https方式在每次推送代码到远程仓库时需要输入用户名和密码来登录,而ssh方式需要在本地生成ssh密钥,将ssh公钥添加到GithubSSH配置里。
更推荐SSH方式,安全快捷,区分这两个的区别是看url是以https开头的还是以git开头的。
在没有配置SSH时,直接在命令行git clone url
拉取时会提示Permission denied
没有访问权限,这就是提示你要先去配置SSH密钥。
如何配置SSH密钥?
- 进入用户根目录,windows上就是
users/你的用户名/
linux就是~
。 - 进入
.ssh
目录,此目录一般会隐藏,如果没有此目录就新建一个,可以在命令用mkdir .ssh
来建立。 - 使用
ssh-keygen -t rsa -b 4096 -C "youremail"
设置密钥规则,-t rsa
表示指定加密方式为rsa
,-b 4096
表示指定大小为4096
。
如果提示ssh-keygen不是内部命令什么的,这是因为没有将ssh-keygen所在目录添加到环境变量中,找到Git安装路径,找不到的可以在命令行用下面命令找到:进入Git安装目录,以我的Git安装目录为例,我的Git装在1
where git
D:\Git
,则将D:\Git\usr\bin
路径添加到环境变量中,ssh-keygen文件就在此路径中,添加到环境变量中就能在命令行直接启动了。 - 这一步是生成密钥文件 如果是第一次生成SSH密钥文件,就直接三次回车,会在
.ssh
目录下生成id_rsa密钥文件,如果不是第一次生成密钥文件,即你的.ssh
目录下已经有id_rsa密钥文件,那么直接回车的操作就会覆盖掉我们之前的密钥,此过程不可逆,我们需要输入新的密钥文件名称,回车后输入密码,再确认输入一次密码,输入密码时为了保密不会显示密码,所以当你看到敲密码时命令行没变化时不要乱按,就正常输入完成敲回车确认就行。 - 完成上一步后会生成两个密钥文件,没有扩展名的是你的私钥谁都不能给,
.pub
扩展名的是公钥文件,打开公钥文件全选复制公钥密钥。 - 进入github -> settings -> SSH and GPG keys -> New SSH Key,将复制的内容内容粘贴到key下的选项框中,title的内容就是给这个ssh密钥起个名字方便区分记忆即可。
- 如果你是第一次创建SSH密钥,在密钥配置那一步直接回车没有指定文件名的话,生成的密钥文件是id_rsa,那到这一步就配置完成了,如果是刚指定了新的密钥文件名,生成了新的密钥文件,那么我们需要在
.ssh
目录下创建一个config文件,并将下面的内容加入其中:解释一下内容:1
2
3
4
5github
Host github.com
HostName github.com
PreferredAuthentications publickey
IdentityFile ~/.ssh/test
# github
是注释。Host github.com
说的是目标网站地址。HostName github.com
说的是目标网站名称。IdentityFile ~/.ssh/test
指定使用的密钥文件,参数~/.ssh/test
修改为自己的密钥文件路径。
- 这时候配置已经完成,此时去命令行
git clone url
就可以了,SSH配置好初次使用clone和push时可能会让你输入一次SSH密码,就是生成SSH密钥时输入的密码,直接三次回车的就直接回车。
关联远端仓库
远端仓库与本地仓库关联
先明确远程仓库与本地仓库的关系,这是两个不同的仓库,本地仓库与远程仓库是分离的,各自内容的修改是不会相互影响的,如何建立这两者的联系,就需要借助pull和push这个两个命令,将更新内容拉取和推送。
如何将远端仓库和本地已有仓库关联?
- 进入远端仓库界面点击Code查看远端仓库地址,就可上面
git clone
那个操作一样。 - 进入本地仓库根目录,使用命令:
1
git remote add origin url
origin
是你给远程仓库起的名字。url
是远程仓库的地址, 注意不要将remote敲成remove。
- 在本地仓库根目录使用命令查看当前本地仓库关联的远程仓库配置信息:前一个信息就是远程仓库的别名,后面信息就是远程仓库的地址。
1
git remote -v
如果你是直接通过git clone url
的方式将远程仓库克隆到本地作为你的本地仓库,那就不用做关联这一步操作,直接在克隆下来的仓库中进行你的工作即可。
下面介绍几个关联远端仓库后可以进行的操作:
- 指定分支名称为main,我们如果没修改过的话默认名称就是main,这条指令不用执行:
1
git branch -M main
- 向远端仓库分支推送更新
1
git push -u origin main
-u
:-upstream。origin
:我们给远程仓库的别名。main
:全称是main:main
表示将本地main分支推送到origin的main分支,如果两个分支名称不相同则要用完整格式:git push -u origin branch_local:branch_remote
- 从远端仓库拉取
1
git pull <远端仓库名> <分支名> # 省略参数则采用默认参数`git pull origin main`
git pull
命令会在拉取修改后尝试进行合并,如果远端仓库与本地仓库内容产生冲突会导致合并失败,就需要手动处理冲突,用git fetch
来拉取远端修改但不会尝试合并,后面分支部分有详细介绍冲突产生时如何处理。
建议在仓库中添加一个README.md文件,用来说明仓库项目的介绍,在访问仓库时会展示在仓库首页,其中使用Markdown轻量级标记语言,建议都学一下。
其他可选代码托管平台
Github虽然在代码托管平台是大名鼎鼎,但是在国内其访问难度大,访问速度慢也是名气不小。因此也就有了替代品:gitee.com
码云,国内平台,如果你的业务都是国内的话,用这个速度很快。gitlab.com
特点是私有化部署,搭建自己的gitlab服务器,在服务器上进行代码管理,对代码安全性要求较高的场景使用。
Git图形化工具
在Git官网上Downloads部分点击CUI client选项能看到提供的几款Git的图形化工具,下面简单介绍一下:
- GitHub Desktop: 界面简洁,功能较为单一,只能用于Github上托管的代码管理。
- SourceTree:支持多种代码托管平台。
分支
分支创建和切换
Branch分支,多用于团队协作开发,减少错误冲突影响。
1 | git branch # 查看分支列表 |
切换分支操作时也会同时切换工作区,举个例子:在develop分支上做了修改,还没有合并到main分支,此时将当前分支从develop切换到mian分支,则工作区中不显示在develop分支上做的修改内容。
分支合并
开发过程中我们在自己的分支上开发完成后,需要合并到主分支上才行,合并分支要经过一下步骤:
- 切换到主分支,一般为main分支,具体看自己的团队的主分支名称是什么。
- 在主分支上使用合并命令发起合并请求,例如:
1
git merge develop # 注意此时工作分支应该切换为目标分支,merge后的参数为要被合并的分支名,这条命令的效果是发起将develop分支合并到当前分支的请求
- 使用图形化界面可以清晰地看到合并过程,当然在命令行也可以通过下面的命令查看简易的分支图:
1
git log --graph --oneline --decorate --all
- 合并完成后,被合并的分支是仍然存在的,要删除被合并过的分支可以使用下面的命令:
1
2
3git branch -d develop # -d参数含义是删除已经被合并过的分支
如果想要强制删除还没有合并的分支,可以使用-D参数
git branch -D develop
有时候合并并不是这么顺利,特别是在团队开发的过程中,合并步骤时常会发生冲突,发生冲突是因为多个人同时修改了同一个文件的同一部分内容,导致Git在自动合并时不知道保留哪一部分,这是就需要手动处理冲突问题:
- 在发生冲突后,使用
git status
可以看到发生冲突的文件。 - 使用
git diff
命令可以看到发生冲突文件中冲突的具体内容,比对差异。 - 在发生冲突后自动进入处理冲突过程,这时在当前分支编辑发生冲突的文件内容,手动合并,之后添加暂存区,提交commit,Git就会自动保留现在手动合并后的版本,完成合并过程。
- 如果在发生冲突后,不想处理这次冲突,而是想直接放弃此次合并,也可以使用下面的命令:
1
git merge --abort # 放弃此次合并
GitFlow模型
分支类型
- 主线分支
main或master,项目的核心分支,包含了项目最新稳定版本的代码,应随时保证main分支的代码是可发布的,主线分支的代码一般会被部署到生产环境中,主线分支代码不允许直接修改,只能通过合并分支来更新。 - 问题修复分支
hotfix,一般是从主分支上分出来的,在修复完成后,合并到主分支和开发分支上,完成合并后一般会删除此分支。 - 开发分支
develop,从主线分支分出来的,是长期存在的分支。 - 功能分支
feature,从开发分支中分出来的,针对特定的功能开发,最终合并在开发分支。 - 预发布分支
release,一般是从开发分支分出来的,预发布完成后一般会合并到主分支和开发分支中,合并完成后会删除此分支。
主分支和开发分支是核心分支,长期存在,其他分支是辅助分支,完成任务后一般会删除分支。
版本号
建议每次合并分支生成一个版本号,方便追踪和回溯,版本号的生成可以通过打标签的方式记录。
1 | git tag # 打标签 |
版本号的规则:
主版本(major version):主要的功能变化或重大更新
次版本:新的功能、改进和更新,通常不会影响现有功能
修订版本:一些小的bug修复,安全漏洞补丁等,通常不会更改现有功能和接口
GitHubFlow模型
一个主分支,长期存在,主分支上的代码可以直接部署在生产环境,开发成员从主分支上分出Feature分支,进行开发,提交commit,完成后发起合并请求(Pull Request),如果没有冲突问题就会进行分支合并。
发起合并请求在Gitlab中称为MergeRequest。
规范
命名规范
分支命名,一般应使用带有意义描述性名称来命名分支,例如:
- 版本发布分支/Tag分支示例:v1.0.0
- 功能分支示例:feature-login-page
- 修复分支示例:hotfix-#issueid-desc
分支管理
- 定期合并已经成功验证的分支,及时删除已经合并的分支
- 保持合适的分支数量
- 为分支设置合适的管理权限
git commit规范
虽然并没有强制要求git commit以什么格式进行书写,但是还是建议选择一个规范来遵守,从而让整个代码改动的历史更加清晰,而且格式化的commit message后续也可用于自动化输出Change log。下面是一个建议的格式:
1 | <type>(<scope>): <subject> |
- type(必须):用于说明git commit的类别,可使用下面的标识:
- feat:新功能。
- fix/to:修复bug。
- fix:适合于一次提交直接修复问题。
- to:适合于多次提交,最终修复问题提交时使用fix。
- docs:文档。
- style:格式(不影响代码运行的变动)。
- refactor:重构(即不是新增功能,也不是修改bug的代码变动)。
- perf:优化相关,比如提升性能、体验。
- test:增加测试。
- chore:构建过程或辅助工具的变动。
- revert:回滚到上一个版本。
- merge:代码合并。
- sync:同步主线或分支的Bug。
scope(可选):用于说明 commit 影响的范围,比如数据层、控制层、视图层等等,视项目不同而不同。
subject(必须)
subject是commit目的的简短描述,不超过50个字符,以最容易理解的语言来书写。
结尾不加句号或其他标点符号。
示例:
1 | fix(DAO):用户查询缺少username属性 |