一.核心基础
核心概念有六个
首先请把与svn有关的一切概念暂时从你的脑海中移除掉,我们要重新认识本文所讲述的所有概念。
1.worktree
worktree是一个目录,你在这里对文件进行增加、删除、修改。也就是我们常说的工作区。在git中worktree必须要与一个branch(分支)关联。在常规情使用的况下,我们感知不到有这样一个关系存在,因为当我们clone一个git仓库时,默认的就已经把worktree关联到head指向的分支上了。请看下图
我在master分支的时候worktree是关联到master上的,我在wtr分支上的时候它就关联到wtr上了。
2.stage
这里我们先来看一下stage这个单词的根本含义,是指一个可以展现、进行或体验某些事件或活动的场所或状态。说白了就是舞台,供一些演员在上面表演用的。基于这层意思,worktree就是台下,stage就是台上,worktree里面的文件就是演员了,我们对演员的塑造就相当于我们对文件的增、删、改。我们对一个文件修改满意了,就可以把它丢到台上去。然而我们还可以继续在台下对这个文件进行修改,而不影响台上的文件,直到我们再次把这个文件丢到台上去。
使用 git add . 将台下所有修改丢到台上去
使用 git add xxxx 将某一个具体的文件的修改丢到台上去
3.commit
commit是一次交付,只有台上(stage)的内容才有资格交付。所有的commit都保存在git仓库中。这里有一个非常重要的观念需要明确一下,千万不要把commit的归属与branch(分支)联系在一起,commit不属于任何分支,在git中,分支的概念的基础是commit,没有commit就没有分支。现在我们来看一下commit中都包含了哪些内容,你就明白了。
commit的内容如下:
- commitID:在整个的git仓库中唯一标识一个commit。
- 父commitID: 在进行commit的时候,它的上一个commit的ID,就像区块链一样,每一个commit都有一个基础的commit。
- changes:本次commit的修改,也就是在进行commit时stage中的内容,说的再深入点就是本次commit与其父commit对比产生的变化的内容。
- 描述:包括作者、时间以及在commit时你通过-m 输入的描述信息。等等吧。
综上所述,一个commit就是一个完整的交付链或者是叫做提交历史,因为每一个commit都可以通过其父commitID往前追溯。这样来看,在某种意义上,是否每一个commit都可以成为一个分支呢?下面我们来说一下branch。
4.branch(分支)
分支,我们对分支最直观的感受就是,一个时间线上分布着这个分支的所有commit。而究其根本,在git中分支就两个作用。
一. 维护一个本分支当前最新的commit的ID,因为我通过一个commit就完全可以确定一个时间线了。这样我就只需要用一个分支文件记录当前最新的commitID即可。
看看我的wtr分支上的最新提交是95e5137afceffdbf172dbcc8ce590695bba8e3d3
而分支文件refs/heads/wtr里记录的也是这个commit。
二. 当进行一次commit时,把分支文件中记录的commitID作为本次commit的父commitID。然后把本次commit的ID更新的分支文件中去。
这就是分支,他是我们维护交付链的工具。而不是commit的载体。
5.head
当前分支指针,它指向的是一个具体的分支文件。git永远把head指向的分支作为当前分支。
HEAD文件里只记录了一个分支文件的地址。
6.git仓库
弄明白了上面5个概念,那么git仓库就很容易理解了。就是用于存储stage、commit、branch、head等等这些数据的地方。
二.git的基础行为
1. git init 仓库初始化
将一个目录初始化为一个git仓库,通常用于创建本地仓库。例如:
我将/d/workspace/gitdoc/gitrepos这个目录初始化成了一个git仓库,这个目录可以是个空目录也可以是一个有内容的目录(如果你想把目录下的内容都纳入到git仓库中去管理)。
我们也可以使用 git init --bare 来创建一个不带工作区和stage的仓库,这一般用来在服务器上创建一个共享仓库,因为不需要在这个仓库里工作,所以不带工作区和stage,它只是用来共享用的。如下
我把我的私服上的/home/gitdoc/reps.git 这个目录初始化成了一个共享仓库。我就可以在我本地使用git clone命令将其克隆下来了。
2. git clone 克隆
把一个远程仓库克隆到本地。一般是初次下载仓库时用这个命令。如下:
我从我的私服上克隆一个空的仓库。
3. git add 加入stage
现在我们在刚才克隆的空仓库中创建三个文件file1、file2、file3.然后执行git status看一下效果。
git status命令是显示git仓库当前的状态信息的。
我们可以看到3个文件是红色的,并且明确告诉你没有被git跟踪,这里我们就可以理解为这三个文件都在台下呢。现在我们先执行 git add file1 来看看效果。
可以看到file1已经被丢到台上了,现在如果执行git commit命令,就只会把file1打包交付。
好现在我们再执行 git add . 看看有什么效果。
我们可以看到file1 和file2 都被丢到台上了。这个命令就是把台下的所有内容都丢到台上去。
这里需要说明一下台上的内容到底是什么,台上的内容就是,台上的文件与本分支当前的commit做对比产生的变化的内容。它并不是整个文件的内容,这里需要注意一下。
4. git commit 交付
就是把当前的stage中的内容打包成一个commit,存储到git仓库中去。也就是存放到 .git/objects目录下。我们现在执行 git commit -m "本次提交的描述" 看看什么效果。
使用git log 查看交付记录。这里我们看到成功创建了一个commitID为e94210ba5933b364d12f35b7841d1cbad3d963b8的commit。
我们可以看到.git/objects/e9/4210ba5933b364d12f35b7841d1cbad3d963b8,这个文件也被创建出来了。
5. git branch 分支管理
git branch可以列出本地仓库的分支列表,我们执行一下看看有什么效果。
可以看到,我目前只有一个分支master。现在我们再创建一个dev分支,执行命令git branch dev
可以看到我们的分支列表中多了一个dev分支,但是我们当前还是再master分支上。我们可以执行git checkout dev来切换到dev分支。
可以看到我们的分支已经切换了,并且当前分支head指针的内容也更新了。
然后我们再dev分支上把file1文件修改一下再次提交。
我们可以看到master分支还指向e94210ba5933b364d12f35b7841d1cbad3d963b8这个commit上。dev已经指向新的commit了。
6.git push 推送
推送就是向远程仓库推送一些东西,都可以是什么东西呢?
1. 推送commit,这是我们最经常推送的东西,我们现在切换回master分支,然后执行git push
这里我们实际上只把第一次提交的commit(e94210ba5933b364d12f35b7841d1cbad3d963b8)推送到了远程仓库了。
2. 推送分支,当我们想把本地分支推送到远程库上时使用的,下面我们来把dev分支推送到远程库里,执行 git push origin dev。这里的origin先不要管,下面会有讲解。
git branch -r 是查看远程仓库的分支列表,可以看到,我们推送分支之前,远程库只有一个分支,推送后有两个分支了。
上面那个origin是远程仓库的名字,使用git clone的仓库会默认把你克隆的远程库命名为origin。然而在git中,一个本地仓库是可以关联n多个远程库的,每一个远程库都是一个名字对应一个url。
使用 git remote -v 查看本地仓库关联的所有远程库
使用 git remote add 远程库名字 url 可以关联一个远程库。
当然,如果你想要往指定的远程库上推送东西的话,git push 后面就要加 远程库的名字了。
7.git merge 合并
我们对合并传统的理解是把两个分支合并到一起,但在git中,我们不能简单的这么去理解merge。上文中我们说过,在git里的一个commit就是一个完整的交付历史,甚至一个commit就可以代表一个分支。
那么在git中,merge的作用其实就是把两个commit的内容合并到一起(注意,在合并中,commit代表的是一段交付链)。现在我们假设有两个commit,A 和 B,我们当前分支指向的是commit A,我们要合并commit B。那么会产生两个情况。
第一种情况,commit B可以通过其父commit直接追溯到commit A,如下图
这种情况下的merge有两个选择,一选择是直接把当前分支的commitID更新为commitB,这就是merge的fast-forward模式。第二个选择是为这次merge创建一个新的commit。
第二种情况,commit B无法追溯到 commitA的情况,如下
commitB的交付链条里无法找到 commit A,也就是无法追溯到当前分支的最新commit。但是他们两个继续往前追溯,一定只能找到一个共同的祖先的,如下
这时merge的任务就是,把自这个共同祖先往上的两个交付链合并到一起,并为这次合并创建一个新的commit,这种情况下是无法进行fast-forward模式合并的。合并后的效果如下
命令使用:git merge 分支名称 或者 commitID,
8.git fetc 获取
从远程仓库获取更新,如果有的话,获取到的新内容不会应用到当前的worktree上。
现在我们切换到一个新的目录里,重新克隆一下我们的远程仓库。然后我们修改file1,然后创建一个commit,并push到远程仓库,然后回到原先的本地仓库里执行git fetch看看是什么效果。
可以看到有内容被取下来了,但是我本地仓库的commit历史没有变化。现在我们执行一下git merge看看效果。
可以看到,我们本地的提交历史发生了变化。这里的merge并没有加任何参数,不加参数的merge实际上是默认的把fetch到的最新的commitID作为目标commit进行合并了,如果没有冲突的话,那就是一次fast-forward的合并,如果有冲突的话,解决冲突后会创建一个新的commit。
9.git pull 拉取
就是git fetch 加 git merge的组合。明白了上面的merge 和fetch,这个行为就没有什么好讲解的了。
三.git的多人协作
在一个项目中多人协作的基础是共享与交流。git通过分支管理、远程仓库操作、commit来支持这两个基础,然而原生的git对其支持也就仅限于此,对于一个小型团队也足够用了。然后对于大型团队的多人协作就有些力不从心了。大型团队意味着大型项目,它主要面临一下几个问题。
1.团队人员多
团队人员众多就意味着人员分散、沟通成本高、工作成果的管理成本高。
2.项目复杂
功能模块多,版本的迭代管理面困难。
3.文档管理
对于大型项目来讲,文档是非常重要的内容,他是这个项目能正常运作的重要基础。
4.交流
在大型项目中我们经常要对一些议题、功能模块进行讨论
所以在大型项目中我们还要求git至少具备人员管理、文档管理的支持。还要对人员交流、版本迭代有更强大的支持。原生的git还无法做到这一点,所以就有了github、gitcode、gitee、gitea、gitlab这些产品出现。下面我们会分别介绍Gitflow、Githubflow、Gitlabflow。
四.Gitflow 工作流
Gitflow 是一种流行的 Git 分支管理工作流程,由 Vincent Driessen 提出,专门为团队协作开发而设计,特别适合拥有较长开发周期的项目。它通过明确的分支策略,使开发、发布和维护的过程更加清晰和规范。
Gitflow 的核心概念
Gitflow 主要依赖以下几个分支来组织代码:
-
主分支(
main
):- 存储的是已经发布到生产环境的稳定代码。
- 每个版本的发布都在此分支打标签(
tag
)。
-
开发分支(
develop
):- 用于开发的主分支,团队成员日常开发的代码最终都会合并到这个分支。
- 此分支上的代码反映当前的最新开发状态。
-
功能分支(
feature/*
):- 每个新功能都有一个独立的分支,从
develop
分支创建。 - 功能开发完成后,合并回
develop
分支。 - 命名通常以
feature/功能名称
命名。
- 每个新功能都有一个独立的分支,从
-
发布分支(
release/*
):- 从
develop
分支创建,用于准备即将发布的版本。 - 可以在此分支上进行最后的测试、修复 bug、完善文档等。
- 完成后合并到
main
和develop
,并打上版本标签。
- 从
-
热修复分支(
hotfix/*
):- 用于修复生产环境中的紧急问题,从
main
分支创建。 - 修复完成后,合并到
main
和develop
,并打上版本标签。
- 用于修复生产环境中的紧急问题,从
Gitflow 的工作流程
-
初始化仓库:
- 主分支和开发分支初始化:
main
和develop
。
- 主分支和开发分支初始化:
-
开发新功能:
- 从
develop
创建feature/功能名称
分支。 - 在
feature
分支上完成开发后,合并回develop
。
- 从
-
准备发布:
- 当
develop
上的功能足够多并且稳定时,从develop
创建release
分支。 - 在
release
分支上完成必要的修复和优化,最后合并到main
和develop
。
- 当
-
生产环境问题修复:
- 当
main
上的代码发现问题时,从main
创建hotfix
分支。 - 修复完成后,合并到
main
和develop
。
- 当
Gitflow 的优点
- 清晰的分支管理:每种任务都有明确的分支,减少了代码冲突的风险。
- 支持并行开发:多个功能可以在各自的分支上独立开发。
- 易于追踪版本历史:版本管理有条理,通过标签标记版本。
Gitflow 的缺点
- 操作复杂:分支和合并的数量较多,增加了操作成本。
- 不适合持续交付:如果需要频繁发布,Gitflow 可能显得繁琐。
- 对小型项目过于重型:小型团队或短开发周期可能不需要如此复杂的分支策略。
适用场景
Gitflow 非常适合如下场景:
- 团队开发的大型软件项目。
- 具有固定的发布周期。
- 需要严格版本控制的项目。
五.Github Flow github工作流
GitHub Flow 是一种简单、轻量的工作流,旨在帮助开发团队高效地协作和发布高质量的软件。它特别适用于持续部署和发布的项目,以下是 GitHub Flow 的关键步骤和特点:
核心步骤
-
创建分支(Branching)
- 每个新功能、修复或改进都从主分支(
main
或master
)创建一个新的分支。 - 分支命名通常清晰描述工作内容,例如
feature/user-authentication
或fix/login-bug
。
- 每个新功能、修复或改进都从主分支(
-
提交更改(Committing Changes)
- 在分支上开发时,将小而独立的更改提交到分支。
- 提交信息应简洁且有意义,便于追踪。
-
创建拉取请求(Pull Request, PR)
- 当工作完成后,提交代码并在 GitHub 上发起一个拉取请求。
- 拉取请求描述应包含代码更改的背景、目的以及测试细节。
-
代码审查(Code Review)
- 团队成员可以在拉取请求中提供反馈,确保代码质量和一致性。
- 如果需要,可以多次更新分支来响应反馈。
-
合并分支(Merging)
- 拉取请求通过审查并经过自动化测试后,可以将分支合并回主分支。
- 在合并前,通常会运行所有必要的 CI/CD 测试。
-
部署(Deploying)
- 每次合并到主分支后,代码应该是可以部署到生产环境的。
- GitHub Flow 鼓励频繁的小规模部署,以减少风险。
关键原则
- 保持主分支稳定: 主分支始终是可部署的,因此任何工作都应在分支上完成。
- 频繁部署: GitHub Flow 的设计支持持续集成和持续部署(CI/CD)。
- 小而快速的迭代: 小的、更频繁的更改降低了回滚的风险并提高了团队效率。
六.GitLab Flow gitlab工作流
GitLab Flow 是一种灵活的工作流模型,结合了 GitHub Flow 和 Git Flow 的优点,适合复杂的开发场景,例如包含多环境部署的项目。GitLab Flow 更加强调环境管理(如开发、测试、生产)和持续集成/持续部署(CI/CD)的最佳实践。
以下是 GitLab Flow 的核心内容和工作步骤:
核心概念
-
环境驱动开发:
支持多环境(如开发、测试、生产),代码通过分支或标签逐步推进到不同的环境。 -
简单分支模型:
避免复杂的分支设计,通常使用少量分支,比如main
(或master
)、feature
分支、release
分支和production
分支。 -
持续集成和部署(CI/CD):
每次代码提交后触发自动化测试和部署,确保高效交付和生产环境稳定。 -
合并请求(Merge Request):
所有更改都通过合并请求进行协作和代码审查,确保代码质量。 -
基于需求的分支策略:
根据项目需求选择不同的分支模型,例如:- 单环境项目:简单分支策略。
- 多环境项目:分支对应环境。
核心步骤
-
创建分支:
根据工作任务,从主分支(main
或master
)创建一个新的分支。常见分支类型包括:- Feature 分支: 用于开发新功能,命名如
feature/add-authentication
。 - Bugfix 分支: 用于修复问题,命名如
bugfix/fix-login-error
。
- Feature 分支: 用于开发新功能,命名如
-
开发和提交更改:
在分支上完成开发,每次提交(commit
)都应与单一变更对应,确保改动清晰可追踪。 -
发起合并请求(Merge Request):
当分支开发完成后,发起合并请求将分支合并到目标分支(如main
或develop
),在请求中描述改动内容和测试方法。 -
代码审查和测试:
团队成员审查代码并运行 CI 测试,确保代码符合质量标准并不会破坏现有功能。 -
合并到主分支:
通过审查后,分支合并到主分支或目标分支。- 对于多环境项目,可将代码从
main
分支逐步合并到staging
和production
分支。
- 对于多环境项目,可将代码从
-
部署:
- 单环境项目: 合并到主分支后直接部署到生产环境。
- 多环境项目: 使用 CI/CD 管道部署到不同环境(如
staging
测试,production
发布)。
分支模型类型
-
简单分支模型(适合单环境项目):
- 主分支(
main
或master
):生产环境代码。 - 功能分支(
feature
):用于开发。
- 主分支(
-
多环境分支模型(适合复杂项目):
- Main 分支: 稳定代码,用于最终发布。
- Production 分支: 与生产环境同步。
- Staging 分支: 用于测试环境。
- Feature/Bugfix 分支: 用于开发。
-
版本发布模型:
- 使用
release-x.y.z
分支管理版本发布。 - 通过标签(Tag)标记版本号。
- 使用
七.附录:
1.Git介绍
Git 是一个开源的分布式版本控制系统,用于高效地管理代码和文件的更改。它由 Linus Torvalds 于 2005 年开发,最初是为 Linux 内核的开发而设计的。Git 因其强大的功能、灵活性和性能而广泛用于软件开发、项目协作和版本管理。
2.Git 的核心命令
基础命令
命令 | 描述 |
---|---|
git init | 初始化一个新的 Git 仓库 |
git clone | 克隆远程仓库到本地 |
git status | 查看当前分支的状态 |
git add | 添加文件到暂存区 |
git commit | 提交暂存区的更改到仓库 |
git log | 查看提交历史 |
分支与合并
命令 | 描述 |
---|---|
git branch | 列出、创建或删除分支 |
git checkout | 切换到指定分支 |
git merge | 合并指定分支到当前分支 |
git rebase | 变基操作,重新整理提交历史 |
同步与远程操作
命令 | 描述 |
---|---|
git remote | 管理远程仓库 |
git fetch | 下载远程仓库的最新数据 |
git pull | 获取并合并远程分支的更改 |
git push | 将本地分支的更改推送到远程仓库 |
Git 的高级功能
-
Rebase(变基): 重新整理分支的提交历史,使开发历史更清晰。
-
Cherry-pick(挑选提交): 将特定的提交应用到当前分支,而不是合并整个分支。
-
Stash(暂存): 保存当前未提交的更改以便稍后恢复。
-
Tag(标签): 创建对重要版本的标记,如发布版本。
分布式版本控制系统
Git 的分布式版本控制系统(Distributed Version Control System,DVCS)是其最显著的特性之一。这种系统设计使每个开发者都拥有完整的项目副本,不依赖中央服务器,赋予了 Git 强大的灵活性和可靠性。
分布式版本控制系统是一种允许多个开发者独立工作且不依赖于单点服务器的版本控制方法。与集中式版本控制系统(如 SVN、Perforce)不同,分布式系统在每台开发者的计算机上都保存完整的代码库,包括项目的所有历史记录。
分布式 vs 集中式
特性 | 集中式版本控制系统 | 分布式版本控制系统 |
---|---|---|
依赖中央服务器 | 强依赖:所有操作都需要服务器 | 弱依赖:本地操作独立完成 |
数据完整性 | 如果服务器崩溃,可能导致丢失 | 每个用户都有完整备份 |
性能 | 网络操作缓慢 | 本地操作快速 |
离线工作 | 不支持 | 支持 |
Git 的分布式架构
仓库结构
Git 仓库分为两部分:
- 本地仓库(Local Repository): 包含完整的项目代码和历史记录,存储在开发者的计算机上。
- 远程仓库(Remote Repository): 通常托管在服务器(如 GitHub、GitLab)上,用于共享和协作。
本地和远程的关系
- 本地仓库可以独立操作:提交(
git commit
)、创建分支(git branch
)、回退(git reset
)等。 - 远程仓库用于团队协作,通过
git push
和git pull
进行同步。