什么是版本控制

在开发中借助版本控制可以实现以下作用

  1. 备份
  2. 代码还原
  3. 协同开发
  4. 追溯问题代码的编写人和编写时间

版本控制器方式

  • 集中式版本控制工具
    集中式版本控制工具,版本库是集中存放在中央服务器的,team里每个人work时从中央服务器下载代码,是必须联网才能工作,局域网或互联网。个人修改后然后提交到中央版本库。举例:SVN和CVS 【淘汰】
  • 分布式版本控制工具
    分布式版本控制系统没有“中央服务器”,每个人的电脑上都是一个完整的版本库,这样工作的时候,无需要联网了,因为版本库就在你自己的电脑上。多人协作只需要各自的修改推送给对方,就能互相看到对方的修改了。举例:Git

为什么选择Git

SVN和CVS是集中式版本控制工具,他们需要借助中心服务器,一旦中心服务器崩了那么就玩完,而Git是分布式的,Git不需要有中心服务器,我们每台电脑拥有的东西都是一样的。
我们使用Git并且有中心服务器,仅仅是为了方便交换大家的修改,但是这个服务器的地位和我们每个人的PC是一样的。
我们可以把它当做一个开发者的pc就可以就是为了大家代码容易交流不关机用的。没有它大家一样可以工作,只不过“交换”修改不方便而已。

安装配置

下载安装

直接从官网下载,改一下路径确认安装即可。若桌面右键有下面两个命令即为安装成功

  • Git GUI:Git提供的图形界面工具
  • Git Bash:Git提供的命令行工具——类似一个极简的Linux命令行窗口
Tip

Git Bash窗口中不能使用Ctrl+CCtrl+V,选中内容就会自动复制,右键可以粘贴(有快捷键Ctrl+Insert替代)

当安装Git后首先要做的事情是设置用户名称和email地址。这是非常重要的,因为每次Git提交都会使用该用户信息

基本配置

  1. 打开Git Bash

  2. 设置用户信息

    git config --global user.name “itcast”
    git config --global user.email “[email protected]
  3. 查看配置信息——设置完后可以使用下面的命令进行验证

    git config --global user.name
    git config --global user.email
    

为常用指令配置别名(可选)

有些常用的指令参数非常多,每次都要输入好多参数,我们可以使用别名。最简单的配置方式如下:

  1. 打开用户目录,创建 .bashrc 文件
    部分windows系统不允许用户创建点号开头的文件,可以打开Git Bash,执行 touch ~/.bashrc

  2. 在 .bashrc 文件中输入如下内容

    #用于输出git提交日志 
    alias git-log='git log --pretty=oneline --all --graph --abbrev-commit' 
    #用于输出当前目录所有文件及基本信息 
    alias ll='ls -al'
    
  3. 打开gitBash,执行 source ~/.bashrc

解决GitBash乱码问题

较为通用的方案

  1. 打开GitBash执行下面命令

    git config --global core.quotepath false
    
  2. ${git_home}/etc/bash.bashrc 文件最后加入下面两行

    ${git_home}即你Git的安装根目录
    export LANG="zh_CN.UTF-8" 
    export LC_ALL="zh_CN.UTF-8"
    

基础操作指令

先初始化一个本地仓库:随意创建一个文件夹,在文件夹内右键打开GitBash并执行命令git init,如果创建成功后可在文件夹下看到隐藏的.git目录。初始化仓库后的这一个文件夹内除了.git目录外的所有区域就是工作区,即平时存放项目代码的地方。

Git工作目录下对于文件的修改(增加、删除、更新)会存在几个状态,这些修改的状态会随着我们执行Git的命令而发生变化。

命令来控制这些状态之间的转换:

  1. git add (工作区 --> 暂存区) git add .添加所有文件、文件夹和子文件夹,包括.gitignore和以点开头的任何其他内容;
  2. git commit (暂存区 --> 本地仓库)

Git远程仓库

常用的托管服务【远程仓库】

前面我们已经知道了Git中存在两种类型的仓库,即本地仓库和远程仓库。那么我们如何搭建Git远程仓库呢?我们可以借助互联网上提供的一些代码托管服务来实现,其中比较常用的有GitHub、码云、GitLab等。

  • GitHub( 地址:https://github.com/ )是一个面向开源及私有软件项目的托管平台,因为只支持Git 作为唯一的版本库格式进行托管,故名GitHub

  • 码云(地址: https://gitee.com/ )是国内的一个代码托管平台,由于服务器在国内,所以相比于 GitHub,码云速度会更快

  • GitLab (地址: https://about.gitlab.com/ )是一个用于仓库管理系统的开源项目,使用Git作为代码管理工具,并在此基础上搭建起来的web服务,一般用于在企业、学校等内部网络搭建git私服。

创建和配置远程仓库

以码云为例,先创建账号,创建账号后

1、在云端新建仓库

可以勾选gitigrove模板为java或者说具体的一门语言,那么就会自动生成一份

仓库创建完成后可以看到仓库地址,如下图所示:

img

2、配置SSH公钥

ssh-keygen -t rsa -b 4096 -C “[email protected]

可以指定生成的文件夹名:/c/Users/cy/.ssh/shareGroop,生成多个不重复的公钥。

cat ~/.ssh/shareGroop.pub


要想将在本地将仓库推送上去就需要进行身份的认证,有账号密码登录(一般不用)、SSH、Token等。此处以SSH公钥为例

所谓公钥就是在自己本地生成一个Git公钥来作为身份标识再将这个公钥放进Git远程仓库里,这样在本地仓库访问远程仓库域名的时候远程仓库就会从公钥比对来判断来客的身份

  • 生成SSH公钥

    • ssh-keygen -t rsa
    • 不断回车
      • 如果公钥已经存在,则自动覆盖
  • 获取公钥

    • cat ~/.ssh/id_rsa.pub
  • Gitee设置账户共公钥

  • 验证是否配置成功

操作远程仓库

当成功与远程仓库连接后会在计算机本地生成凭证

image-20230115143934740

添加远程仓库

此操作是先初始化本地库,然后与已创建的远程库进行对接。

git remote add <远端名称> <仓库地址>
  • 远端名称:默认是origin,取决于远端服务器设置
  • 仓库地址:从远端服务器获取此url

例如:

git remote add origin [email protected]:czbk_zhang_meng/git_test.git

查看远程仓库

在Git中,您可以通过以下命令来查看当前仓库的远程地址:

git remote -v

执行这个命令后,您将看到类似于以下的输出,其中包含了远程仓库的名称和地址:

origin  https://github.com/username/repository.git (fetch)
origin  https://github.com/username/repository.git (push)

这里的origin是远程仓库的默认名称,https://github.com/username/repository.git是该远程仓库的URL地址。(fetch)(push)表示获取和推送数据时使用的地址。
如果您想要查看或修改特定的远程仓库地址,可以使用以下命令:

  • 查看所有远程仓库:
    git remote
    
  • 重命名远程仓库:
    git remote rename OLD_NAME NEW_NAME
    
  • 添加一个新的远程仓库:
    git remote add NAME [URL]
    
  • 删除一个远程仓库:
    git remote rm NAME
    
  • 更新远程仓库的URL地址:
    git remote set-url NAME [NEW_URL]
    

推送到远程仓库

git push [-f] [--set-upstream] [远端名称] [本地分支名][:远端分支名]
  • 如果远程分支名和本地分支名称相同,则可以只写本地分支

    本来是:git push origin master :master 表示将本地仓库的master分支提交到远程仓库的master分支

    • git push origin master 这里表示将本地仓库当前master分支的内容推到远程仓库上面去
  • -f 表示强制覆盖

    公司的GatLab一般禁用了这个权限

  • –set-upstream 推送到远端的同时并且建立起和远端分支的关联关系。

    • git push --set-upstream origin master:master
  • 如果当前分支已经和远端分支关联,则可以省略分支名和远端名。

手动书写完整的命令是一个好习惯(第一次进行配置,之后默认就是上次的关联)

在远程仓库即可查看到我们提交的仓库内容

初次推送事项

主要是两个方面本地仓库未有提交记录;远程仓库还没有初始化

强制拉取

有些时候本地仓库提交管理混乱,需要从远程仓库强制拉取,以刷新本地仓库,覆盖所有add和commit操作。可执行以下代码:

git fetch --all  
git reset --hard origin/master 
git pull

这和前面的强制推送差不多

本地分支与远程分支的关联关系

  • 查看关联关系我们可以使用 git branch -vv 命令

从远程仓库克隆

如果已经有一个远端仓库,我们可以直接clone到本地。

  • 命令: git clone <仓库路径> [本地目录]
    • 本地目录可以省略,会自动生成一个目录,就是SSH后面那部分

不同的主机都把修改完了版本资源放在远程仓库上,然后其他人都是克隆,这样就可以实现不同主机之间的数据同步了,数据都是一样的

克隆一般只会执行一次,就是在你进去公司的时候,别人提交了以后,我们不需要去克隆整个仓库,仓库是很大的,克隆也需要花很多时间,所以要去远程仓库中抓取我们要的版本信息,就是那些别人新增加的提交

从远程仓库中抓取和拉取

Warning

只执行抓取命令 git fetch,本地仓库是没有远程仓库来的内容的

远程分支和本地的分支一样,我们可以进行merge操作,只是需要先把远端仓库里的更新都下载到本地,再进行操作。

  • 抓取 命令:git fetch [remote name] [branch name]

    • 抓取指令就是将仓库里的更新都抓取到本地,不会进行合并,不合并本地仓库就是没有更新,此时还没有拿到远程仓库的内容,合并后才会拿到更新的内容
    • 如果不指定远端名称和分支名,则抓取所有分支。
  • 拉取 命令:git pull [remote name] [branch name]

    • 拉取指令就是将远端仓库的修改拉到本地并自动进行合并,等同于fetch+merge
    • 如果不指定远端名称和分支名,则抓取所有并更新当前分支。

所以我们一般先执行拉取命令,然后执行git pull
因为我们此时我们获取到的是远程仓库的版本更新,但是我们本地的版本不是最新的,也就是说此时我们本地和远程仓库不是同步的,所以我们要将远端拿到的修改合并到本地仓库的master上,使得本地的版本修改变为最新的

解决合并冲突

我们要更新远程仓库的资源时,先要获取此时远程仓库的资源后,在合并到自己的master分支中,然后再上传到远程仓库上

在一段时间,A、B用户修改了同一个文件,且修改了同一行位置的代码,此时会发生合并冲突。

A用户在本地修改代码后优先推送到远程仓库,此时B用户在本地修订代码,提交到本地仓库后,也需要

推送到远程仓库,此时B用户晚于A用户,故需要先拉取远程仓库的提交,经过合并后才能推送到远端分支,如下图所示。

在B用户拉取代码时,因为A、B用户同一段时间修改了同一个文件的相同位置代码,故会发生合并冲突。就是b在更新一个资源之前,有一个a在b之前率先改掉了这个资源,此时就会出现分支冲突的问题,git不知道是要取a修改的值,还是b修改的值,此时就要我们手动去对应文件里去修改,到底要保留哪一个。远程分支也是分支,所以合并时冲突的解决方式也和解决本地分支冲突相同相同,在此不再赘述

status查看修改的状态

作用:查看修改的状态(暂存区、工作区)

git status
Git文件状态标志:
  • A: 本地新增的文件(服务器上没有)
  • C: 文件的一个新拷贝
  • D: 本地删除的文件(服务器上还在)
  • M: 文件的内容或者mode被修改了
  • R: 文件名被修改了
  • T: 文件的类型被修改了
  • U: 文件没有被合并(需要完成合并才能进行提交)
  • X: 未知状态(很可能是遇到了git的bug,可以向git提交bug report)

add添加工作区到暂存区

作用:添加工作区一个或多个文件的修改到暂存区

命令形式:git add 单个文件名|通配符

git add file.txt # 添加单个文件
git add . # 将所有修改加入暂存区

commit提交暂存区到本地仓库

作用:提交暂存区内容到本地仓库的当前分支

命令形式:git commit -m ‘注释内容’

git commit -m "XXX update"

log查看提交日志

配置的别名git-log就包含了这些参数,所以后续可以直接使用指令 git-log

作用:查看提交记录

命令形式:git log [option] 或者 git-log

  • —all 显示所有分支
  • —pretty=oneline 将提交信息显示为一行
  • —abbrev-commit 使得输出的commitId更简短
  • —graph 以图的形式显示

版本回退

作用:版本切换

命令形式:

git reset --hard commitID # commitID 可以使用 git-log 或 git log 指令查看

每一次提交文件都会生成一个唯一的ID,我们就是通过这个ID进行回退版本。当然上面的git-log(这是我们配置的指令别名其实是加了多个限制条件来优化结果的log命令)和git log只能看到未清除的历史记录,那么如何查看已经删除的记录?

git reflog:这个指令可以看到已经删除的提交记录

我们可以在reflog里面知道删除文件的id,我们可以直接使用命令:
git reset --hard commitID 还原

所以git reset --hard commitID既可以做版本回退,也可以做版本还原(找回删除的记录)

打标签

标签操作(相当于快照,给某个时间点的状态手动打上标签)

  • 列出已有的标签 :git tag
  • 创建标签:git tag [name]
  • 将标签推送至远程仓库:git push [shortName] [name]
  • 检出标签:检出的时候需要创建一个新的分支来记录
    • git checkout -b [branch] [name]

添加文件至忽略列表

一般我们总会有些文件无需纳入Git 的管理,也不希望它们总出现在未跟踪文件列表。 通常都是些自动生成的文件,比如日志文件,或者编译过程中创建的临时文件等。 在这种情况下,我们可以在工作目录中创建一个名为 .gitignore 的文件(文件名称固定),列出要忽略的文件模式(语法像正则,也支持路径)

分支管理

几乎所有的版本控制系统都以某种形式支持分支。 使用分支意味着你可以把你的工作从开发主线上分离

开来进行重大的Bug修改、开发新的功能,以免影响开发主线。master是我们的主线

每个人开发的那一部分就是一个分支,HEAD指的就是当前分支,使得每个人的开发互不影响,在每个人都开发完后就将所有的代码汇总到一起,此时就要执行分支的合并操作,一般是将其他分支的修改合并到master主分支

工作区只能在一个分支工作,每个分支存放的文件或者资源是不一样的,就相当于不同的文件夹

查看本地分支

git branch

创建本地分支

git branch 分支名

切换分支

git checkout 分支名

我们还可以直接切换到一个不存在的分支(创建并切换):

git checkout -b 分支名

合并分支

一个分支上的提交可以合并到另一个分支

git merge 分支名

该项职责由git管理员来完成。例如当开发分支Develop上的所有功能已经完成时,需要合并到master上,此时:在本地创建dev分支并与远程dev分支对应

git checkout -b dev origin/dev

切换到master分支

git checkout master

本地的dev合并到master上(遇到冲突解决完后再次提交)

git merge dev

推送到远程的master上(执行这项操作时,需要有操作远程master分支的权限)

git push origin master
  • 释放权限:

  • 操作结果:

补充:合并的快进模式

参考文档:git 快进式合并

其实说白了,分支的修改类似一个链表,我们追求让它倾向于线性结构。假设我们原来有两个分支的初始状态相同,其中一个分支完成了很多修改,而另一个分支没有进行任何修改的时候,我们可以直接让另一个分支的指针指向最新修改的版本,从而让两个分支回归“线性”

删除分支

不能删除当前分支,只能删除其他分支
git branch -d 分支名 # 删除分支时,需要做各种检查(智能提示防止误删,一般是提到了暂存区但没有提交的内容)
git branch -D 分支名 # 不做任何检查,强制删除

解决冲突

当两个分支上对文件的修改可能会存在冲突,例如同时修改了同一个文件的同一行,这时就需要手动解决冲突,如下图所示:

  • 第一个count值表示的是当前分支修改的值

  • 第二个count值是在dev分支修改的值

解决冲突步骤如下:

  1. 处理文件中冲突的地方
  2. 将解决完冲突的文件加入暂存区(add)
  3. 提交到仓库(commit)

解决冲突后git-log查看记录:可以看到发生冲突的合并版本也会被记录

开发中分支使用原则与流程

几乎所有的版本控制系统都以某种形式支持分支。 使用分支意味着你可以把你的工作从开发主线上分离

开来进行重大的Bug修改、开发新的功能,以免影响开发主线。

在开发中,一般有如下分支使用原则与流程:

  • master (生产) 分支
    线上分支,主分支,中小规模项目作为线上运行的应用对应的分支——经测试无误后发布的稳定版本供用户使用;

  • develop(开发)分支
    是从master创建的分支,一般作为开发部门的主要开发分支,如果没有其他并行开发不同期上线要求,都可以在此版本进行开发,阶段开发完成后,需要是合并到master分支,准备上线。

例如我们要开发新功能,我们要可以在develop分支上在建一个分支,新功能一般叫做feature分支,开发完以后在合并到 develop分支上面去,而不是直接提交到master分支,最后项目做完了develop再合并到master分支上

develop和master分支是不可删除的

  • feature/xxxx分支(用完可删)
    从develop创建的分支,一般是同期并行开发,但不同期上线时创建的分支,分支上的研发任务完成后合并到develop分支,用完后可删除。

  • hotfifix/xxxx分支
    从master派生的分支,一般作为线上bug修复使用,修复测试完成后需要合并到master、test、develop分支。因为主分支线上代码存在bug,那么作为开发分支肯定也有相同的bug那么hotfifix分支修改bug后必然要合并到主分支的同时也要合并到开发分支来修复bug代码

  • 还有一些其他分支,在此不再详述,例如test分支(用于代码测试)、pre分支(预上线分支)等等。

rebase和merge🎯

简要小结,推荐使用场景:
  • 往公共分支上合代码的时候,推荐使用merge。
  • 拉公共分支最新代码的时候,推荐使用rebase,也就是git pull -rgit pull --rebase,但有个缺点就是rebase以后我就不知道我的当前分支最早是从哪个分支拉出来的了,因为基底变了嘛。

HEAD的理解

HEAD 指向当前所在的分支,类似一个活动的指针,表示一个「引用」。

HEAD 既可以指向「当前分支」的最新 commit,也可以指向历史中的某一次 commit (「分离头指针」的情况)。归根结底,HEAD 指向的就是某个提交点。

当我们做分支切换时,HEAD 会跟着切换到对应分支。

fast-forward 与 —no-ff 的区别

假如有一个场景:有两个分支,master 分支和 feature 分支。现在,feautre 分支需要合并回 master 分支。

fast-forward 合并方式是条件允许的情况,通过将 master 分支的 HEAD 位置移动到 feature 分支的最新提交点上,这样就实现了快速合并。这种情况,是不会新生成 commit 的。(快进模式)

git checkout master # 先切换到master分支
git merge feature # 将feature分支合并到当前分支上(master)

--no-ff 的方式进行合并,master 分支就会新生成一次提交记录。

git checkout master # 先切换到master分支
git merge --no-ff feature # 将feature分支合并到当前分支上(master)

如果条件满足时,merge 默认采用的 fast-forward 方式进行合并,除非你显示的加上 --no-ff 选项;而条件不满足时,merge 也是无法使用 fast-forward 合并成功的!

merge操作

git merge 操作是区分上下文的。当前分支始终是目标分支,其他一个或多个分支始终合并到当前分支。这个注意点记住了,方便记忆!所以,当需要将某个分支合并到目标分支时,需要先切到目标分支上。

快进模式能够进行的条件是:源分支和目标分支之间没有分叉。 下图则是无法通过 HEAD 的快速移动实现分支的合并。

如果执行合并操作,默认会尝试 fast-forward 的方式进行合并,但是因为分叉了,所以此时会采用 no-ff 的方式进行合并,有新的 commit 生成了。最终的结果图如下:

git checkout master # 先切换到目标分支
git merge feature

rebase操作

rebase 合并往往又被称为 「变基」。这里的「基」就是一个「基点」、「起点」的意思。git rebase 命令通常称为向前移植(forward porting)。

「变基」就是改变当前分支的起点。注意,是当前分支! rebase 命令后面紧接着的就是「基分支」。与merge操作相反。

  • 变基前:

  • 执行命令:
git checkout feature # 切换到当前分支,或待变基分支
git rebase master # 变基

# 可合并为下面的语句
git rebase master feature
  • 变基后:

解释:rebase,变基,可以直接理解为改变基底。feature分支是基于master分支的B拉出来的分支,feature的基底是B。 而master在B之后有新的提交,就相当于此时要用master上新的提交来作为feature分支的新基底。实际操作为把B之后feature的提交存下来,然后删掉原来这些提交,再找到master的最新提交位置,把存下来的提交再接上去(新节点新commit id),如此feature分支的基底就相当于变成了E而不是原来的B了。(注意,如果master上在B以后没有新提交,那么就还是用原来的B作为基,rebase操作相当于无效,此时和git merge就基本没区别了,差异只在于git merge会多一条记录Merge操作的提交记录),图示:

从Develop分支分出两个分支,分属两个人员进行开发。F1分支开发完毕后,push到总分支。F2分支开发到F2_5时需要拉取最新代码。

  • 如果F2分支采用git pull拉取最新代码:

    • F1分支的视角(F1分支的commit记录):

    • F2分支的视角:这将会把F1分支的修改直接拉下来于本地代码merge,且产生一个commit F2_5,也就是merge commit。

  • 如果F2分支采用git pull --rebase 拉取最新代码:

    • F1分支视角不变
    • F2分支视角: