git版本控制工具(二)
远程仓库:
- 目前我们的开发都是在本地进行的,代码也是保存在本地仓库的,我们只能对本地代码进行操作。
- 但在真实开发中,一个项目通常是由多人同时进行的,所以我们需要将代码共享到远程仓库中。
- 而远程仓库也很好实现,只需要在Git服务器上搭建一个仓库即可,在Git服务器上的仓库我们称之为Git远程仓库。
我们可以通过如下方式使用Git服务器:
远程仓库的验证
如果我们想要操作Git服务器中的私有仓库时,远程仓库会对我们进行身份验证。
目前Git服务器验证手段有两种:
-
基于HTTP的凭证存储(Credential Storage)。
-
基于SSH的密钥。
基于HTTP的凭证存储
由于HTTP协议是无状态的连接,这意味着每次连接都需要携带用户名和密码。
基于这种操作非常麻烦,Git提供了一个凭证系统来处理这个事情。
Git凭证系统有一些模式可以供我们选择:
(1) 临时输入模式
-
不缓存凭证,每次操作(
git clone
、git push
)时都需要输入用户名和密码。 -
安全性高,但频繁操作时过于麻烦。
-
触发条件:未配置任何凭证助手(credential hepler)。
凭证助手:当我们安装git时,
(2) 明文存储模式
-
将凭证铭文保存在磁盘文件中,通过
git config --global credential.helper store
进行配置。 -
凭证长期有效,无需重复输入。
-
安全性低(明文存储,需严格保护文件权限)。
(3) 内存缓存模式
-
将凭证临时保存在内存中(默认缓存15分钟)。
-
配置方式:
-
短期缓存,适合临时使用。
-
重启后凭证失效。
1 | git config --global credential.helper cache |
(4) 系统密钥管理器集成
-
将凭证存储在操作系统的密钥管理器中(如 Windows 凭据管理器、macOS Keychain)。
-
我们在安装git时默认会为我们安装一个Git Credential Manager凭证管理工具。
-
我们可以在
控制面板->用户账户->凭证管理器->Windows凭证
中看到我们的git凭证信息。
基于SSH的远程仓库验证
Secure Shell(安全外壳协议,简称SSH)是一种加密的网络传输协议,可在不安全的网络中为网络服务提供安全的传输环境。
我们需要先生成一个SSH密钥
(1) 打开Git黑窗口
(2) 生成ssh key
1 | ssh-keygen -t rsa -C "你的邮箱" |
(3) 获取ssh key公钥内容
1 | cd ~/.ssh |
(4) 复制获取的公钥,然后存储到git服务器上,以Gitee为例:

管理远程仓库
我们可以通过git remote
进行有关远程操作的操作。
-
git remote -v/--verbose
:查看当前项目关联的远程仓库地址。 -
git remote add <shortname> <url>
:添加远程仓库。- 如
git remote add origin http://xxx//xxx.git
- 如
-
git reomte rename origin main
:重命名远程地址。 -
git remote remove origin
:移除远程地址。
本地分支的上游分支(跟踪分支)
当我们与成功添加远程仓库后,如果远程仓库的部分内容与本地不同的话,我们需要先通过git pull
命令将远程仓库的内容拉取到本地才能进行提交,但当我们进行git pull
操作时,可能会出现以下情况:

出现这个问题的原因是因为当前分支没有和远程的分支进行跟踪。
git pull
其实是git fetch
和git merge
的合并操作,在没有进行跟踪的情况下,是不允许git fetch
直接拉取到本地的,但我们可以指定远程仓库的分支进行获取:
1 | # git pull <本地设置的远程仓库的shortname> <远程仓库的分支> |
但又会出现以下问题:

我们可以看出,已经拉取到本地了,但无法进行合并,这个问题后面再说。
我们不能每次都进行指定远程仓库的分支进行操作,为了解决这个问题,我们需要给当前分支设置一个跟踪分支。
1 | # git branch --set-upstream-to=<本地设置的远程仓库的shortname>/<远程仓库的分支> |
进行上述操作后我们就可以直接进行git pull
操作了:

可以看出,git fetch
这一步已经没有问题了,接下来,我们需要解决的是fatal: refusing to merge unrelated histories这个问题了。
拒绝合并不相干的历史
上文提到,当我们进行合并远程分支操作时,会出现拒绝合并不相干历史的情况:

出现上述的原因如下

要解决这个问题,我们可以通过下面操作来处理:
1 | git merge --allow-unrelated-histories |
进行上述操作以后,我们就可以正常使用git pull
操作了。
Git对远程仓库的常见操作
-
git clone <远程仓库地址>
:克隆远程仓库到本地。 -
git push <本地设置的远程仓库的shortname> <当前分支>
:将代码推送到远程仓库,只输入git push
的情况默认是将当前分支推送到远程仓库中。
注意:对于git push
的提交操作,会将当前分支的代码提交到远程仓库的相同名称的分支中,否者就会报以下错误:

上图已经给出了解决方案:git push origin HEAD[:本地分支名]
。
进行上面操作后会在远程创建一个与本地分支名一致的新分支,但这样每次提交都需要指定远程仓库名和本地分支。
为了解决这个问题,我们只需要将当前分支名改为与远程分支一样即可:
1 | # git branch -m old-name new-name 如果改变当前的分支名,old-name可以省略 |
造成上述问题的原因其实与push.default
配置项有关,我们看一下push.default
可配置选项:
配置项 | 说明 |
---|---|
nothing | 不推送任何内容 |
matching | (Git 2.0 之前的默认值)推送所有匹配的分支 |
upstream | 将当前分支推送到其上游分支(tracking 是 upstream 的已弃用的同义词) |
current | 将 Current 分支推送到同名分支 |
simple | (Git 1.7.11 中的新功能,Git 2.0 后默认)与 upstream 类似,但如果上游分支的名称与本地分支的名称不同,则拒绝推送 |
根据上文内容,我们可以知道,如果我们不想改变当前分支的名称,直接想推送到上游分支中,只需要将push.default
的值改为upstream
即可:
1 | git config push.default upstream |
-
git fetch
:拉取远程仓库的代码。 -
git merge
:合并拉取的远程代码到本地。 -
git pull
:git fetch
+git merge
。
Git标签
当我们进行项目开发时,当完成一项重大开发后,通常会在当前版本打上一个标签,起到标识作用,是一项里程碑进度,表示当前版本的重要性。
创建Git标签
通过Git命令创建标签:
-
创建轻量标签(lightweight):
git tag v1.0
。 -
创建附注标签(annotated):
git tag -a v1.0 -m '附注信息'
。
在默认情况下,git push
操作并不会传送标签到远程服务器上,我们需要显示的推送标签到服务器上,这样别人在获取时也能获得标签。
1 | # git push <远程仓库shortname> <标签> |
删除和检出标签
删除标签的操作我们对本地和远程分别操作:
-
删除本地分支:
git tag -d <标签名>
git tag -d v1.2
-
删除远程标签:
git push <远程仓库shortname> --delete <标签名>
git push origin --delete v1.2
检出标签:用于查看某个标签所指向的版本:
1 | # git checkout <标签名> |
但这个操作会创建一个游离分支,新分支处于detached HEAD(分离头指针)
状态。
detached HEAD(分离头指针)
-
HEAD 在 Git 中通常指向当前分支(例如 main, develop),而分支本身是一个指向某个特定提交的指针。
-
在分离头指针状态下,HEAD 直接指向一个具体的提交,而不是指向一个分支指针。
-
简单说:你不是站在某个命名的分支道路上,而是直接站在历史长河中的某个具体快照(提交)点上。
好处:
-
安全地探索历史(Look Around):我们可以安全的查看某个版本是什么样子的。
-
进行实验性修改(Make Experimental Changes): 尝试一些想法、修复一个 bug、写个新功能,但还不确定是否要保留这些改动,或者不想干扰当前的工作分支。
-
快速修复基于特定版本的 Bug:比如用户报告了线上版本 (v1.0) 的一个严重 bug,但你的 main 分支已经开发 v2.0 了,代码变化很大。
注意:不能直接将分离头指针(detached HEAD)状态下的提交推送到远程仓库的常规分支(如 origin/main)
但如果我们真想提交的话,可以使用这个命令:
1 | git push origin HEAD:<name-of-remote-branch> |
-
<name-of-remote-branch
>: 指定你想要在远程仓库创建或更新的目标分支名称(例如 my-experiment, temp-fix, 甚至 main - 强烈不推荐覆盖重要分支!)。
Git提交对象(Commit Object)
Git版本控制系统以Commit Object(提交对象)
的形式支持分支。
在每次提交时,都会创建一个提交对象:
-
该提交对象会包含一个指向暂存内容快照的指针。
-
该提交对象还包含了作者的姓名和邮箱、提交时输入的信息以及指向它的父对象的指针。
- 首次提交产生的提交对象没有父对象,普通提交操作产生的提交对象有一个父对象
- 而由多个分支合并产生的提交对象有多个父对象。
Git分支
Git分支,本质上是指向提交对象的可变指针。
1 | main |
在上面的结构中,A、B、C、D、E都是一个提交对象,main和feature是不同的分支,我们对于分支的切换其实就是将HEAD这个头指针指向不同分支的可变指针(即分支引用),从而间接指向该分支所指向的提交对象。
Git对分支的操作
-
git branch <分支名>
:创建分支。 -
git checkout <分支名>
:切换分支。 -
git checkout -b <分支名>
:创建并切换到新分支。 -
git branch
:查看当前所有的分支。 -
git branch -v
:查看当前所有的分支,同时查看最后一次提交。 -
git branch --merged
:查看所有合并到当前分支的分支。 -
git branch --no-merged
:查看所有没有合并到当前分支的分支。 -
git branch –d <分支名>
:删除当前分支。 -
git branch –D <分支名>
:强制删除某一个分支。 -
git push origin <branch>
:推送分支到远程。 -
git checkout --track <remote>/<branch>
:跟踪远程分支,如果远程分支存在,本地不存在,会创建一个与远程分支相同名的分支。 -
git push origin --delete <branch>
:删除远程分支。
Git工作流
Git工作流的出现源于软件开发的协作需求与Git分布式架构的特性的深度结合。
我们在大型项目开发时,可能会出现以下问题:
-
协作冲突:多人并行修改同一文件。
-
版本管理:稳定版 vs 开发版 vs 紧急修复。
-
质量控制:代码审查与自动化测试。
-
责任追溯:故障定位与代码溯源。
通过Git工作流可以解决以上问题,其实本质上就是为特定协作场景设计的分支管理策略框架,通过规范分支的创建、合并规则及生命周期,解决团队协作中的关键问题。
简单来说,就是创建不同的分支,不同的分支有不同的职责,可以同步开发或测试,最后合并到主分支成为一个完整的项目。
目前比较常见的工作流如下图:

Git分支合并操作
场景:我们有一个hotfix
分支是用于修复系统bug的,我们修复完后,但当前master
分支是没有修复后的代码的,我们就可以将master
分支和hotfix
分支进行合并:
1 | # 切换到master分支 |
Git变基操作
在Git中整合来自不同分支的修改有merge和rebase两种方法。
merge
操作会保留分支拓扑(查看图结构时可以看到有分叉),rebase
是生成线性结构,rebase
找到当前分支和目标分支的最近共同祖先,然后提取当前分支在共同祖先之后的所有提交,将这些提交重新应用到目标分支的最新提交之后,如果是相同文件的操作,可能会需要处理合并冲突,然后将当前分支的可变指针指向变基后的最新提交对象上。
我们用图的形式来更直观的看一下:
假设我们有一个提交历史,如下图:

目标:target-branch
合并current-branch
分支。
1 | git checkout target-branch |

目标:current-branch
分支变基到目标分支。

注意:
- 不要对已推送到远程的公共分支执行rebase 。
- 不要对多人协作的分支执行rebase。
原因:重写历史会破坏其他开发者环境
对于merge
和rebase
的使用场景:
-
私有分支(仅自己使用)→ 大胆使用
rebase
。 -
公共分支(多人协作)→ 始终使用
merge
。
目标:将target-branch
合并到current-branch
。
1 | git checkout target-branch |

目标:current-branch
分支变基到目标分支。

注意:
- 不要对已推送到远程的公共分支执行rebase 。
- 不要对多人协作的分支执行rebase。
原因:重写历史会破坏其他开发者环境
对于merge
和rebase
的使用场景:
-
私有分支(仅自己使用)→ 大胆使用
rebase
。 -
公共分支(多人协作)→ 始终使用
merge
。