版本控制(Version control)在软件开发中,可以帮助程序员进行代码的追踪、维护、控制等等一系列的操作
通过版本控制工具,也可以帮助我们在软件开发的过程中,确保由不同人所编辑的同一程序文件都得到同步
简而言之,日常开发中,我们可能需要记录我们什么时候,对哪一个文件进行了那些修改以便于我们进行维护以及版本管理
同时开发中需要多人进行协作开发,那么我们就需要将多人编写的代码在开发中进行合并
为此,我们需要一个工具,可以自动化的实现上述功能,这个工具就是版本控制工具
分类
集中式版本控制
CVS和SVN都是是属于集中式版本控制系统(Centralized Version Control Systems,简称 CVCS)
- 它们的主要特点是单一的集中管理的服务器,保存所有文件的修订版本
- 协同开发人员通过客户端连接到这台服务器,取出最新的文件或者提交更新
但是集中式版本控制也有一个核心的问题:中央服务 器不能出现故障:
-
如果宕机一小时,那么在这一小时内,谁都无法提 交更新,也就无法协同工作
-
如果中心数据库所在的磁盘发生损坏,又没有做恰 当备份,毫无疑问你将丢失所有数据
-
即使本地存在对应的代码,因为所有的提交记录和版本迭代信息都是保存在服务器的,本地并不存在
所以也只能恢复对应的代码,所有的提交记录和分支管理将无法恢复
分布式版本控制
Git是属于分布式版本控制系统(Distributed Version Control System,简称 DVCS)
-
客户端并不只提取最新版本的文件快照, 而是把代码仓库完整地镜像下来,包括完整的历史记录
-
这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复
-
因为每一次的克隆操作,实际上都是一次对代码仓库的完整备份
配置
开发的时候是基于多人进行协作开发的,那么每次提交记录就需要携带对应的用户名和用户邮箱
通过git hook的方式,可以在用户提交记录后,以邮件的形式向用户邮箱推送对应的提交记录
默认安装的git是不存在这些配置信息的,我们需要手动进行配置
对于git的配置信息,每台计算机上只需要配置一次,程序升级时会保留配置信息,我们也可以在任何时候再次通过运行命令来修改它们
配置信息分类
分类 | 位置 | 参数 | 说明 |
---|---|---|---|
系统配置 | /etc/gitconfig | --system | 针对于整个系统 同一个系统的不同用户对应的git信息配置都将会相同 |
用户配置 | ~/.gitconfig | --global | 只针对当前用户 |
项目配置 | 当前使用仓库的 Git 目录中的 config 文件(即 .git/config) | --local | 只针对该仓库 |
# 设置
git config --global user.name 'Klaus'
git config --global user.email 'klaus@gmail.com'
# 查看
# 查看所有配置
git config --list
# 查看具体某一项配置信息
# 在配置文件中,可以使用 # 来注释某一行具体的配置信息
git config user.name
初始化查仓库
-
初始化一个Git仓库,并且可以将当前项目的文件都添加到Git仓库中
该命令将创建一个名为 .git 的子目录,这个子目录含有你初始化的 Git 仓库中所有的必须文件,这些文件是 Git 仓库的核心
.git
本质上就是项目的本地仓库
# 初始化本地仓库
git init
# 但是,在这个时候,我们仅仅是做了一个初始化的操作,你的项目里的文件还没有被跟踪
# 还需要执行 git add 和 git commit 操作才可以将对应代码添加到奥本地仓库中
- 从其它服务器 克隆(clone) 一个已存在的 Git 仓库
# 克隆后,会将项目下载到 命名行执行所在的那个文件夹中
# 该项目中也存在.git目录,该目录管理着本地git仓库,以及远程仓库地址
# 所以我们下载远程仓库到本地后,即使修改了本地项目的名称,也依旧可以正常和远程仓库进行连接
git clone <remote registory url>
git文件状态
在实际开发中,我们需要将某些文件交由Git仓库来管理
并且我们之后会修改文件的内容,当达成某一个目标时,想要记录下来这次操作,就会将它提交到仓库中
那么我们需要对文件来划分不同的状态,以确定这个文件是否已经归于Git仓库的管理
在git中文件主要可以被划分为如下两类
分类 | 说明 |
---|---|
未跟踪(untracked) | 默认情况下,执行文件的操作(如新建一个文件或修改文件内容)是在工作区中进行的 而工作区中的代码默认并不会被git所管理 Git仓库下的文件没有添加到Git仓库管理中,我们需要通过add命令来操作 |
已跟踪(tracked) | 添加到Git仓库管理的文件处于已跟踪状态,Git可以对其进行各种跟踪管理 |
对于已跟踪文件,有可以被划分成以下几个分类
分类 | 说明 |
---|---|
staged | 暂缓区(staged 又叫索引区 Index)中的文件状态 表示文件刚刚被git所管理,还未被添加到git仓库中 此时对应的提交并没有和commit对象进行关联 也就是没有对应的commitId,无法通过git log来查看对应的操作日志 |
unmodified | commit命令,可以将staged中文件提交到Git仓库 |
modified | 修改了某个文件后,会处于modified状态 也就是说修改的那部分内容并没有被git所管理 需要执行 git add 命令和git commit 命令才可以将修改的那部分内容添加到git仓库中 |
常见指令
# 查看文件状态
git status
# 查看文件状态 - 只要简要信息
git status -s
# 添加到暂存区
git add <文件名> <文件名>
# 一次性将新建文件和修改的文件全部提交
# 可以使用通配符 .
git add .
# 将暂存区中的内容 提交到 本地仓库中
git commit -m '这里是本次提交的描述信息'
# 将对应内容 先添加到暂存区后 直接进行提交
# 此命名只能对修改部分进行提交的简化操作
# 如果是一个新建的文件 需要先使用git add 加入git管理中
# 所以此简化操作 只适用于文件的修改部分
git commit -a -m '这里是本次提交的描述信息'
提交日志
在提交了若干更新,又或者克隆了某个项目之后,有时候我们想要查看一下所有的历史提交记录
这个时候我们可以使用git log命令:
- 不传入任何参数的默认情况下,git log 会按时间先后顺序列出所有的提交,最近的更新排在最上面
- 这个命令会列出每个提交的 SHA-1 校验和、作者的名字和电子邮件地址、提交时间以及提交说明
# 查看提交日志
git log
# 将日志信息在一行中进行显示
git log --pretty=oneline
# 以图形化方式 去查看本次提交对应的提交日志
git log --pretty=oneline --graph
版本回退
如果想要进行版本回退,我们需要先知道目前处于哪一个版本, Git通过HEAD指针记录当前版本
HEAD 是当前分支引用的指针,它总是指向该分支上的最后一次提交,也就是指向最新一次提交
我们可以通过HEAD来改变Git目前的版本指向
# 上一个版本就是HEAD^,上上一个版本就是HEAD^^
# HEAD 可以大写 也可以小写 一般推荐使用大写形式
git reset HEAD^
# 如果是上1000个版本,我们可以使用HEAD~1000
git reset --hard HEAD~1000
# 我们可以指定某一个commit id
# commit id 是非常长的,我们可以只取前几位,只要保证可以体现commit id的唯一性即可
git reset --hard 2d44982
当版本回退的时候,使用git log
只能看到当前状态下的日志记录
如果需要前进版本,可以使用git relog
查看完整日志记录
在relog中除了记录提交日志,也记录着操作日志
我们可以通过relog中的commit id 进行版本前进
git relog
忽略文件
一般我们总会有些文件无需纳入 Git 的管理,也不希望它们总出现在 未跟踪文件列表
这些文件通常都是些自动生成的文件,比如日志文件,或者编译过程中创建 的临时文件等,例如node_modules
,打包文件夹,临时文件等
我们可以创建一个名为 .gitignore 的文件,列出要忽略的文件的模式
在实际开发中,这个文件通常不需要手动创建,可以在github/gitignore查找我们所需要的.gitignore
文件, 在必须的时候添加自 己的忽略内容即可
.gitignore
一旦被添加到工作区,其功能就会生效,但是其本身也是一个需要被提交到git仓库中的一个文件
git 校验和
Git 中所有的数据在存储前都通过SHA-1 散列(hash,哈希)算法计算校验和,
校验和(有时候也被称之为 commitId) 是一个基于基于 Git 中文件的内容或目录结构计算出的 40 个十六进制字符(0-9 和 a-f)组成的字符串
我们可以通过校验和在.git/object
中找到某一次提交的快照,从而进行版本的回退和前进
远程仓库
在真实开发中,我们通常是多人开发的,所以我们会将管理的代码共享到远程仓库中
远程仓库通常是搭建在某一个服务器上的, 所以我们需要在Git服务器上搭建一个远程仓库
我们可以使用第三方的Git服务器(比如GitHub、Gitee、Gitlab等等)
或者使用gitlab应用在自己服务器搭建一个Git服务
验证
对于私有的仓库我们想要进行操作,远程仓库会对我们的身份进行验证
如果没有验证,任何人都可以随意操作仓库是一件非常危险的事情
目前Git服务器验证手段主要有两种:
- 基于HTTP的凭证存储(Credential Storage)
- 基于SSH的密钥
凭证
因为本身HTTP协议是无状态的连接,所以每一个连接都需要用户名和密码
如果每次都这样操作,那么会非常麻烦,幸运的是,Git 拥有一个凭证系统来处理这个事情
选项 | 说明 |
---|---|
默认模式 | 默认所有都不缓存 每一次连接都会询问你的用户名和密码 |
cache模式 | 将凭证存放在内存中一段时间 密码永远不会被存储在磁盘中,并且在15分钟后从内存中清除 |
store模式 | 将凭证用明文的形式存放在磁盘中,并且永不过期 |
在mac电脑中,Git 还有一种 “osxkeychain” 模式,它会将凭证缓存到你系统用户的钥匙串中(加密的)
在第一次连接的时候,会需要手动添加对应的凭证,之后会自动将凭证存储在mac钥匙串中
在之后的连接中,会自动去钥匙串中取出对应凭证后再发送请求
如果你使用的是 Windows,你可以安装一个叫做 “Git Credential Manager for Windows” 的辅助工具
这个工具在git安装的时候会提示安装,以达到mac钥匙串的功能
SSH
Secure Shell(安全外壳协议,简称SSH)是一种加密的网络传输协议,可在不安全的网络中为网络服务提供安全的传输环境。
SSH以非对称加密实现身份验证
-
其中一种方法是使用自动生成的公钥-私钥对来简单地加密网络连接,随后使用密码认证进行登录
第一次需要输入用户名和密码, 会根据用户名和密码生成对应的密钥
在之后的每次请求中,都是要对应的密钥即可,不需要在输入对应凭证
-
人工生成一对公钥和私钥,通过生成的密钥进行认证,这样就可以在不输入密码的情况下登录
公钥需要放在待访问的电脑之中,而对应的私钥需要由用户自行保管
这也是git使用的SSH方式
# ssh-keygen 生成ssh私钥和公钥的方式
# -t 表示的是 加密类型 常见的有 ed25519 和 rsa
# -C 是 生成的密钥信息 一般添加邮箱地址即可
ssh-keygen -t ed25519 -C “your email"
# 或者
ssh-keygen -t rsa -b 2048 -C “your email"
对应的ssh密钥会存放在~/.ssh
文件中
id_ed25519 或 id_rsa
是对应的私钥文件
id_ed25519.pub 或 id_rsa.pub
是对应的需要配置在服务器中的公钥文件
远程服务端地址管理
我们之前从GitHub上clone下来的代码,它就是有自己的远程仓库的
git clone <remote repository url>
# git clone http://www.example.com/foo.git
# 当远程地址使用http或https开头的时候,使用的就是http校对
# 当远程地址使用git@开头的时候 表示对应的验证方式是 SSH
# git clone git@www.example.com/foo.git
我们也可以继续添加远程服务器(让本地的仓库和远程服务器仓库建立连接)
git remote add <shortname> <url>
# 默认情况下 远程分支名为origin
# 我们可以自己指定 但是一般最主要的那个远程分支名依旧是命名为 origin
# git remote add origin http://www.example.com/foo.git
# 查看远程地址
# 输出的是远程地址名称
git remote
# -v -> --verbose
# 输出的是远程地址名称 + 远程地址URL链接
git remote -v
# 重命名远程仓库名
git remote rename <old remote name> <new remote name>
# 移除远程地址
git remote remove <remote name>
上游分支
默认情况下,添加远程分支后,使用git pull
拉取远程分支是无法拉取的
因为git pull
操作的本质是git fetch + git merge
对于git而言,git无法知道是需要从远程仓库的哪一个分支下载对应资源
所以我们需要设置当前分支对应的上游分支(也叫做跟踪分支)
在没有跟踪的情况下,我们直接执行pull操作的时候必须指定从哪一个远程仓库中的哪一个分支中获取内容
git pull <registry> <branch>
# git pull origin master
如果我们希望设置默认的拉取分支的时候,那么我们可以指定对应的上游分支
# 注意: 本地分支的上游分支 实质是在本地git仓库中对应的分支
# 所以一般在设置上游分支之前,会执行fetch操作 以确保本地一定会有我们所设置的对应上游分支
git fetch
# 如果本地分支名不设置 那么默认指代的就是为当前分支设置上游分支
git branch --set-upstream-to=<registry>/<branch> <local branch>
# git branch --set-upstream-to=origin/<branch>
拒绝合并不相干的历史
在设置了上游分支后,就设置了git的默认拉取分支,也就是说此时git fetch
的时候,已经可以成功执行
但是在执行git merget
的时候,依旧存在如下问题
fatal: refusing to merge unrelated histories
这是git在v2.9版本时候新添加的特性
在gitV2.9之前允许合并两个默认情况下没有共同基础的分支
但是有的时候,我们的确需要合并两个没有任何联系的分支
例如在本地设置完远程分支地址后,拉取远程仓库中代码
此时本地分支和远程仓库的对应分支就是完全独立的两个分支
但是此时的确需要将两个没有任何联系的分支进行合并
为此我们可以使用--allow-unrelated-histories
选项来强制合并两个没有任何联系的分支
git merge --allow-unrelated-histories
此时我们就可以正常将远程仓库中的内容拉取到本地仓库了
远程仓库交互
# 从远程仓库clone代码:将存储库克隆到新创建的目录中
# 1. 在命令执行所在的目录 新建一个和远程仓库同名的文件夹
# 2. 将远程仓库中的代码 clone 一份到本地
# 3. 设置本地仓库的remote 为 远程仓库对应的git地址
# 4. 在本地新建和远程仓库主分支同名分支 并自动track(跟踪) origin/<远程仓库主分支名>
git clone http://www.example.com/example.git
# 将本地仓库的代码推送到远程仓库中
# 在进行git push操作之前 务必需要使用git pull 或 git fetch|rebase 命令 更新远程仓库最新代码
git push origin master
# push指令的完整写法如下
# git push origin <本地分支名>:<远程分支名>
git push origin master:main
# 如果当前分支 即为master分支
# 上述代码可修改为
git push origin HEAD:main
# 当本地分支名和远程分支名同名的时候 可以简写成一个
git push origin master
# 上述代码 等价于 git push origin master:master
# 直接git push 的等价于 git push origin <当前分支名>:<与当前分支同名的远程分支名>
git push
# 如果我们希望默认推送到上游分支
# 我们可以对push.default进行修改
# push.default默认值为simple 即默认推送分支为上游分支,但当上游分支名与本地分支名不一致的时候,拒绝推送
# 可以将push.default的值设置为upstream 此时默认推送到对应上游分支,无论上游分支名称和本地分支名是否一致
# 对应的push.default选项可选值还有 current 表示推送到于本地分支同名的远程分支中
# 如果对应远程分支不存在,则会新建一个同名的远程分支 并进行推送
git config push.default upstream
# 因为上述配置是项目级配置 所以可以通过cat .git/config 来查看git对应配置信息
# 我们也可以在push的时候 为当前分支设置上游分支
git push --set-upstream origin master
# 从远程仓库获取最新的代码
git fetch origin master
# 如果我们设置了上游分支,那么我们可以省略远程仓库名 和 远程分支名
git fetch
# fetch 获取到的代码默认仅仅只是合并到了本地仓库中,并没有合并到工作区中
# 我们需要通过merge来合并
git merge origin/master
# 这里之所以设置的是origin/master 而不是 origin master
# 是因为 对于git fetch 而言是对远程仓库进行操作
# 而对于git merge而言 代码已经在本地仓库了 是对本地仓库的操作
# 我们也可以省略对于分支名
# 默认是将当前分支对应的上游分支拉取到本地仓库的对应本地仓库分支 合并到 当前分支
git merge
# 我们可以使用git pull命令来简化上述的操作
git pull
# git pull === git fetch + git merge|rebase
开源协议
开源许可证(Open Source License)就是版权许可证,开源协议是一种非签署的,使用即同意的协议
里面详尽表述了你获得代码后拥有的权利,可以对别人的作品进行何种操作,何种操作又是被禁止的