Bootstrap

Git 的原理与使用(下)

Git 的原理与使用(上)Git 的原理与使用(中) 中介绍了Git及其相关概念,本地与远程的Git命令操作等,本文接着以上两篇,将对Git的标签管理、多人协作与企业级开发模型三方面展开介绍。

目录

七、标签管理

1.理解标签

2.创建标签

(1)如何创建标签

(2)如何在指定的commit上打标签

(3)如何给新创建的标签添加标签说明

3.操作标签

(1)删除标签

(2)推送本地标签到远程仓库

(3)删除远程标签

八、多人协作

1.完成准备工作

2.同一分支下多人协作(不常见)

3.不同分支下多人协作(一般情况)

Pull Request流程演示

补充:本地 git branch -a 依然能看到删除的远程分支的解决办法

九、企业级开发模型

1.讲个故事

2.系统开发环境

3.Git分支设计规范

master分支

feature分支

develop分支

release分支

hotfix 分支

4.企业级项目管理实战

准备工作

DevOps研发平台

创建项目

创建仓库

添加成员

开发场景-基于gitflow模型的实践

新需求加入

修复测试环境Bug

修改预发布环境Bug

修改正式环境Bug

紧急修复正式环境Bug

拓展阅读

其他的DevOps研发平台

拓展实践

**小结


七、标签管理

1.理解标签

标签 tag,可以简单地理解为是对某次 commit 的一个标识,相当于起了一个别名。

例如,在项目发布某个版本时,通常会针对最后一次 commit 起一个 v1.0 这样的标签,来标识“里程碑”的意义。

这有什么用呢?相较于难以记住的 commit id , tag 是一个自定义的、更简短好记且有意义的标识。在当我们需要回退到某个重要版本时,直接使用标签便能很快地定位到。

2.创建标签

(1)如何创建标签

我们可以给某一次提交(commit)来“打标签”。

在Git中打标签非常简单。

首先切换到需要打标签的分支上(git chekcout <分支名>),然后敲命令

git tag [name]

就可以打上一个新标签:

git tag v1.0    # 默认对最新的一次提交进行打标签

回车之后,会默认给我们最新一次的提交打一个标签,此处打上的标签就是v1.0。

可以用命令

git tag

可以查看当前存在的所有标签(注:标签不是按时间顺序列出,而是按字母排序的):

执行tree .git,查看打了标签之后git版本库中有什么变化:

(2)如何在指定的commit上打标签

首先找到历史提交的某个commit id,然后通过命令打上即可:

# 命令
git tag [name] [commitID]

# 对44e30f7这一次提交进行打标签
git tag v0.9 44e30f7   

(3)如何给新创建的标签添加标签说明

Git 还提供了创建带有说明标签的功能。用-a指定标签名,-m指定说明文字,格式为:

git tag -a [name] -m "XXX" [commit_id]

例如: 

git tag -a v8.0 -m "important tag:xxxx" 7222302    # 对7222302这一次提交进行打标签,并附上标签说明

使用命令

git show [tag名称]

可以查看这个tag对应的详细信息,包括tag的标签说明:

3.操作标签

(1)删除标签

如果标签打错了,也可以删除:

git tag -d <tag名称>

因为创建的标签都只存储在本地,不会自动推送到远程。所以,打错的标签可以在本地安全删除。

(2)推送本地标签到远程仓库

本地的标签也可以推送到远程仓库中。

远端码云上也有标签一栏:

如果要推送某个标签到远程,使⽤命令

git push origin <tagname>

此时查看远端码云,可以看到标签已经被更新:

如果本地有很多标签,也可以一次性的全部推送到远端:

git push origin --tags

(3)删除远程标签

如果标签已经推送到远程,要删除远程标签,会相对麻烦一点:先从本地删除,然后把操作push到远端:

git push origin :<tag名称>

(冒号后面跟tag的名称) 

冒号后也可以跟tag的路径:

git push origin :refs/tags/v1.0

在码云上查看,已删除成功:


八、多人协作

1.完成准备工作

目前,我们已经完成了如下学习:

  • 基本完成Git的所有本地库的相关操作,git基本操作,分支理解,版本回退,冲突解决等等。

  • 申请码云账号,将远端信息clone到本地,以及推送和拉取。

为了实现多人协作开发,我们先做一些准备工作来模拟有两个用户协作开发的情景:

之前的演示中,我们已经将项目clone到了Linux主机上,那么现在我们再将同一个远程仓库clone到Windows主机上。远程仓库与主机的关系如下:

注意,在实际开发中,每个用户都有自己的Gitee或Github账号,如果要多人进行协同开发,必须要将用户添加进开发者,用户才有权限进行代码提交:

邀请用户:

至此,我们相当于有了分别在Linux和Windows上针对于同一项目进行协作开发的两个用户。关于用户的准备工作到这里就完成了。

但是目前,我们的仓库中只有一个master主分支。而在实际的项目开发中,在任何情况下其实都是不允许直接在master分支上修改代码的,这是为了保证master分支的稳定。所以在开发新功能时,常常会新建其他分支,供开发时进行迭代使用。

我们可以直接先在远程仓库新建dev分支:

创建成功:

然后,我们在Linux主机上以pull的方式,并在Windows上以clone的方式,把远端仓库同步到本地:

在Linux主机上pull的远程仓库代码

这里补充到了在本地查看远程分支的命令:

git branch -r

pull后便可以看到远程的dev分支:origin/dev,但本地并没有dev分支,因此接着要切换到本地的dev分支供我们进行本地开发。

以下命令会将本地分支和远程分支的进行关系链接:

git checkout -b [本地分支] [远程分支]

查看本地仓库分支和远程仓库分支对应关系:

git branch -vv

建立起连接后,在本地分支和远程分支之间进行push或pull就可以直接使用短命令:

而在Windows中,我们通过clone操作将远程仓库clone至本地即可。 

2.同一分支下多人协作(不常见)

现在开发者1在Linux本地对file.txt进行修改,并将修改push到远程仓库:

push后,码云上仓库的dev分支就有了我们上面进行过的修改(在file.txt中增加了一行aaa)。

此时,另一位开发者2(Windows)与开发者1(Linux)协同开发,也在本地新建分支dev:

注意,此处的dev并没有和远程仓库origin/dev关联。因此如果试图直接git pull拉取远程仓库,会失败:

使用

git branch --set-upstream-to=origin/<远程分支名> <本地分支名>

可以手动给二者建立关联:

建立完关联后,如果开发者2在Windows上也要对file.txt文件作修改,并试图push,此时push会失败:

这是因为当前push的commit和开发者1的push的commit有冲突。

解决办法也很简单,Git已经提示我们,正确的做法是先用git pull把最新的提交从origin/dev抓下来,然后,在本地进行合并并解决冲突,最后再推送。

pull提示冲突:

解决冲突:

重新推送:

此时,我们看到远端的码云已经能看到我们的新提交了:

以上就是二位开发者协同开发、遇到冲突并且解决的一个情景。

此后,两名开发者就可以进行协同开发了,不断的 git pull/add/commit/push ,遇到了冲突,就使用我们之前讲的冲突处理方式解决冲突。

对于其中一位开发者来说,要想看到小伙伴的代码,也只需要pull一下即可。

最后不要忘记,虽然我们是在分支上进行多人协作开发,但最终的目的是要将开发后的代码合并到master上去,让我们的项目运行最新的代码。

master没有将dev分支下的内容进行merge,在master下是看不到dev中有的修改的(即“aaa”和“bbb”这两行代码)。

想让dev分支中的代码merge进master,有两种选择:

1、令本地的master分支merge dev,再将本地的master分支push到远端master。这样远端的master分支也就能包含dev分支上的修改了。

tips:在master分支merge dev,是可能会和dev分支出现冲突的。更安全的做法是先让dev来merge master,然后在dev上解决完冲突后,再用master merge dev。

注意:用dev来merge master时,要让master处于最新的状态。存在一种可能:在本地master还未与本地dev合并时,有其他人修改了origin/master导致此时我本地的master并不是最新的,那么再后面将master push到远程的时候,依然可能产生冲突。

master:merge dev之后,就能把master push给origin/master了

2、在远程仓库提一个PR(Pull Request)申请单,然后由仓库的管理员审批这个单子。管理员同意之后,就会在远端自动执行merge操作,将远程的dev分支merge进远程的master分支。【推荐这个方式,因为让审批员审查是对代码的一层保障。】

最后点击「创建Pull Request」,就把这个PR申请提交给了管理员,让管理员审批。

在完成origin/dev分支合并到origin/master分支的操作后,origin/dev分支对于我们来说就没用了,那么dev分支就可以被删除掉。我们可以直接在远程仓库中将dev分支删除掉:

总结一下,在同一分支下进行多人协作的工作模式通常是这样:

  • 首先,在本地发开完后,可以试图 push 自己的修改。

  • 如果push失败,则因为远程分支比你的本地更新,需要先用 git pull 拉取远程分支以更新本地分支。

  • 如果合并有冲突,则解决冲突,并在本地再次提交。

  • 没有冲突或者解决掉冲突后,再用push就能成功。

  • 功能开发完毕,将origin/dev分支 merge 进 origin/master(两种方式),最后删除origin/dev分支。

3.不同分支下多人协作(一般情况)

一般情况下,如果有多需求需要多人同时进行开发,是不会在一个分支上进行多人开发的,而是每一个需求或一个功能点就要创建一个分支。

现在同时有两个需求需要你和你的小伙伴进行开发,那么你们俩便可以各自创建一个远程分支来完成自己的工作。

根据功能点创建远程分支有两种方法:

1、直接在远程仓库的gitee上创建分支。【推荐】

直接在远程仓库上基于master创建分支,而远程master就是最新、最全、最稳定的代码,能够保证基于它创建出的其它分支也是最新最全最稳定的。

2、在本地创建分支,然后将本地的分支push到远程仓库下。【本文演示】

  1. 本地的master无法保证是最新的,因为有可能其他的协作者刚好push了新的代码到远程。

  2. 要在本地创建分支,必须先让本地master:pull。

本文对上面提到的第二种方法进行演示:

命令

git branch -a

可以查看最近一次pull时本地和远程所有分支的情况。(注意它并不会自动更新本地的远程分支列表,即在pull之后有其他人在远程仓库修改了远程分支,在本地是看不到的。)

通过 git push origin feature-1命令把本地仓库feature-1分支下的内容推送给远程仓库feature-1分支下的内容(远程仓库的feature-1分支自动创建):

查看gitee远程仓库发现feature-1分支已创建,并且feature-1下有刚才新增的func1文件。

对于另外一位开发者来说,操作步骤和第一位开发者是一样的:

  1. 先对master进行git pull 使本地master最新。

  2. git branch -b feature-2创建并切换到分支feature-2。

  3. 在feature-2分支下新建func2文件,并将func2文件git push origin feature-2到远程仓库。

  4. 远程仓库中可以看到feature-2分支,且feature-2分支下有文件func2。

二人的操作是彼此独立的。在本地,你看不见他新建的文档,他看不见你新建的文档。并且push的是各自的分支,不会产生任何冲突,二人互不影响。

正常情况下,你俩就可以在自己的分支上进行专业的开发了。

但天有不测风云,你的小伙伴突然生病了,但需求还没开发完,需要你帮他继续开发,于是他便把

feature-2分支名告诉你了。这时你就需要在自己的机器上切换到feature-2分支帮忙继续开发。

此时可以执行git pull(短命令)更新远程分支列表:

对于git pull(短命令)有两个作用:

  1. 拉取分支内的内容。如把origin/dev中的文件拉取到本地的dev中。这个情况必须提前建立好dev和origin/dev之间的连接才能有效。

  2. 拉取远程仓库的内容。此时和分支是否建立联系没有关系,可以直接执行git pull来更新本地的远程仓库列表。

更新完本地记录的远程仓库列表后,在本地也创建并切换到分支feature-2:

git checkout -b feature-2 origin/feature-2 # 自动与远程仓库的feature-2分支关联

关联并切换成功后,便可以在本地的feature-2分支上看见开发者2提交的function2文件了。

接着就可以帮开发者2小伙伴进行开发,开发完毕后push到远程的origin/feature-2中。

这时,你的小伙伴已经休养地差不多,可以继续进行自己的开发工作了,那么他回到岗位首先要获取到你帮他开发的内容,然后再接着你的代码继续开发。

这时直接pull是无效的,原因是之前远程仓库的feature-2分支是通过git push origin feature-2命令创建的,但这个方式并不会将远程分支feature-2和本地分支feature-2自动关联。因此,小伙伴没有指定本地feature-2分支与远程origin/feature-2分支的链接,。

根据提示,设置feature-2和origin/feature-2的链接即可:

git branch --set-upstream-to=origin/feature-2 feature-2

目前,小伙伴的本地代码就和远端保持严格一致了。你和你的小伙伴可以继续在不同的分支下进行协同开发了。各自功能开发完毕后,不要忘记我们需要将代码合并到master中才算真正意义上的开发完毕。

Pull Request流程演示

  • 开发者1先提交PR(feature-1 -> master),审查通过后master与feature-1成功合并。

  • 开发者2提交PR时,有几点要注意:

    • feature-2可能会与feature-1存在conflict,而feature-1已经被合并入master,因此feature-2可能与master存在conflict。

    • 如果feature-2与master之间存在conflict,那解决冲突是在master上,不安全。

    • 正确的做法是:先在feature-2中合并master,有conflict在feature-2上解决。然后再提PR,此时master和feature-2之间就不会有冲突了。(上面提到过)

    • 由于feature-1已经merge进了master,在本地执行feature-2中合并master的操作时不要忘了先在master上pull一下,保证master最新。

这就是多人协作的工作模式,一旦熟悉了,就非常简单。

补充:本地 git branch -a 依然能看到删除的远程分支的解决办法

当前我们已经删除了远程的几个分支,使用 git branch -a 命令查看本地分支和远程分支列表时却发现,很多在远程仓库已经删除的分支依然能看到。例如:

使用命令

git remote show origin

可以查看remote地址、远程分支还有本地分支与之相对应关系等信息。

此时我们可以看到那些远程仓库已经不存在的分支。

提示信息说明了使用 git remote prune 就能移除stale的远端分支(即在本地git branch -a不被显示)。根据提示,使用 git remote prune origin命令:

这样就删除了那些远程仓库不存在的分支:

九、企业级开发模型

1.讲个故事

我们知道,⼀个软件从零开始到最终交付,大概包括以下几个阶段:规划、编码、构建测试发布、部署和维护,即【开发->测试->发布上线】。

最初,程序比较简单,工作量不大,程序员⼀个人可以完成所有阶段的工作。但随着软件产业的日益发展壮大,软件的规模也在逐渐变得庞大。软件的复杂度不断攀升,⼀个人已经hold不住了,就开始出现了精细化分工。如下图所示:

但在传统的 IT 组织下,开发团队(Dev)和运维团队(Ops)之间诉求不同:

  • 开发团队(尤其是敏捷团队)是追求「变化」的。

  • 运维团队是追求「稳定」的。

双方往往存在“利益的冲突”。比如,精益和敏捷的团队把持续交付(即 CD)作为目标,而运维团队则为了线上的稳定而强调变更控制。部门墙由此建立起来,这当然不利于IT价值的最大化。

为了弥合开发和运维之间的鸿沟,需要在文化、工具和实践方面的系列变革⸺DevOps由此诞生。

DevOps(Development 和 Operations 的组合词)是一种重视“软件开发人员(Dev)”和“IT运维技术人员(Ops)”之间沟通合作的文化、运动或惯例。透过自动化“软件交付”和“架构变更”的流程,来使得构建、测试、发布软件能够更加地快捷、频繁和可靠。在DevOps的软件开发过程包含计划、编码、构建、测试、预发布、发布、运维、监控。

由此可见DevOps的强大。

讲了这么多,这到底和Git有什么关系呢?

举⼀个很简单的例子就能说明问题:一个软件的迭代,在我们开发人员看来,说白了就是对代码进行迭代,那么就需要对代码进行管理;如何管理我们的代码呢,那使用的不就是Git(分布式版本控制系统)了!所以Git对于我们开发人员来说,其重要性就不言而喻了。

2.系统开发环境

言归正传,对于开发人员来说,在系统开发过程中最常用的几个环境必须要了解一下:

  1. 开发环境:开发环境是程序猿们专门用于日常开发的服务器。为了开发调试方便,一般打开全部错误报告和测试工具,是最基础的环境。

  2. 测试环境:一个程序在测试环境工作不正常,那么肯定不能把它发布到生产机上。该环境是开发环境到生产环境的过渡环境。

  3. 预发布环境:该环境是为避免因测试环境和线上环境的差异等带来的缺陷漏测而设立的一套环境。其配置等基本和生产环境一致,目的是能让我们发正式环境时更有把握!所以预发布环境是你的产品质量最后一道防线,因为下一步你的项目就要上线了。要注意预发布环境服务器不在线上集成服务器范围之内,为单独的一些机器。

  4. 生产环境:是指正式提供对外服务的线上环境,例如我们目前在移动端或PC端能访问到的APP都是生产环境。(代码稳定+环境配置稳定)

这几个环境也可以说是系统开发的三个重要阶段:开发->测试->上线。一张图总结:

各个环境之间是隔离的。对于规模稍大的公司来说,可不止这么几个环境,比如项目正式上线前还存在仿真/灰度环境(灰度策略,地域灰度:让小范围一部分人先使用新产品,如果这一小部分人没有问题再逐渐推送到全部人;人群灰度:如先对老年人开放使用等),再比如还存在多套测试环境,以满足不同版本上线前测试的需要。

一个项目的开始从设计开始,而一个项目的成功则从测试开始。一套良好的测试体系可以将系统中绝大部分的致命Bug解决在系统上线之前。

测试系统的完善和成熟也是衡量一个软件企业整体水平的重要指标之一,测试往往被忽视,但是它却是软件质量的最终保障,乃至项目能否成功的重要因素。

3.Git分支设计规范

有了对「环境」的概念后,那么对于开发人员来说,一般会针对不同的环境来设计分支,举一个例子:

分支

名称

适用环境

master

主分支

生产环境

release

预发布分支

预发布/测试环境

develop

开发分支

开发环境

feature

需求开发分支

本地

hotfix

紧急修复分支

本地

注:以上表格中的分支和环境的搭配仅是常用的一种,可视情况而定不同的策略。

master分支

  • master为主分支,该分支为只读且唯一分支。用于部署到正式发布环境,一般由合并release分支得到。(测试人员测完之后认为release分支没有问题了,就可以和master进行合并。)

  • 主分支作为稳定的唯一代码库,任何情况下不允许直接在master分支上修改代码。

  • 产品的功能全部实现后,最终在master分支对外发布,另外所有在master分支的推送应该打标签(tag)做记录,方便追溯。

  • master分支不可删除。

feature分支

  • feature 分支通常为新功能或新特性开发分支,以 develop 分支为基础创建 feature 分支。

  • 命名以 feature/ 开头,建议的命名规则: feature/user_createtime_feature

  • 新特性或新功能开发完成后,开发人员需合到 develop 分支。

  • 一旦该需求发布上线,便将其删除。

develop分支

  • develop 为开发分支,基于master分支创建的只读且唯一分支,始终保持最新完成以及 bug 修复后的代码。可部署到开发环境对应集群。

  • 可根据需求大小程度确定是由分支合并,还是直接在上面开发(非常不建议,应该在feature上开发完了之后,再把代码合并到develop)。

release分支

  • release 为预发布分支,基于本次上线所有的 feature 分支合并到 develop 分支之后,基于 develop 分支创建。可以部署到测试或预发布集群。

  • 命名以 release/ 开头,建议的命名规则: release/version_publishtime 。

  • release 分支主要用于提交给测试人员进行功能测试。发布提测阶段,会以 release 分支代码为基准进行提测。

  • 如果在 release 分支测试出问题,需要回归验证 develop 分支看否存在此问题。

  • release 分支属于临时分支,产品上线后可选删除。

hotfix 分支

  • hotfix 分支为线上 bug 修复分支或叫补丁分支,主要用于对线上的版本进行 bug 修复。当线上出现紧急问题需要马上修复时,需要基于 master 分支创建 hotfix 分支。

  • 命名以 hotfix/ 开头,建议的命名规则: hotfix/user_createtime_hotfix

  • 当问题修复完成后,需要合并到 master 分支和 develop 分支并推送远程。一旦修复上线,便将其删除。

一张图总结:

以上所讲解的,就是企业级常用的一种Git分支设计规范:GitFlow模型

但要说的是,该模型并不是适用于所有的团队、所有的环境和所有的文化。

如果你采用了持续交付(CD),你会想要一些能够尽可能简化交付过程的东西。有些人喜欢基于主干的开发模式,喜欢使用特性标志。然而,从测试的角度来看,这些反而会吓一跳。

关键在于站在你的团队或项目的角度思考:这种分支模型可以帮助你们解决哪些问题?它会带来哪些问题?这种模式为哪种开发提供更好的支持?你们想要鼓励这种行为吗?你选择的分支模型最终都是为了让人们更容易地进行软件协作开发。因此,分支模型需要考虑到使用者的需求,而不是盲目听信某些所谓的“成功的分支模型”。

所以对于不同公司,规范是会有些许差异,但万变不离其宗,一切的目的是为了效率与稳定

4.企业级项目管理实战

准备工作

DevOps研发平台

Gitee企业版免费版

企业名称可随意填写一个测试名称,只要能通过即可。注意,多人协作开发,需要将多人账号拉入一个企业下才行。如何添加成员后面会跟大家讲解。

创建项目

创建仓库

注:

  • 创建的仓库可以关联到某个项目中被管理

添加成员

1、添加企业成员

申请后,需要负责人审批通过。

2、添加项目成员

3、添加仓库开发人员

开发场景-基于gitflow模型的实践

新需求加入

现有一个订单管理的新需求需要开发,首先可以基于 develop 分支创建一个:feature/xxxxx_20231012分支。

1、需求在 feature/xxxxx_20231012 分支开发完毕,这时研发人员可以将代码合并到develop 分支,将其部署在开发环境的服务器中,方便开发人员进行测试和调试。

  1. 开发者在 feature 分支下发起请求评审。

  2. 审查员审查代码。

  3. 审查通过,合并分支。

  4. 合并成功,查看结果

2、在develop下开发人员自测通过后,先确定下develop不存在未测试完毕的需求,然后研发人员可基于develop分支创建一个release/xxx分支出来,可交由测试人员进行测试。

3、测试人员测试 release 通过后(包含测试环境和预发布环境的测试),就可将代码合并入master

4、测试人员在 master (正式环境) 测试通过后,便可删除 feature/xxx 分支。

修复测试环境Bug

在develop测试出现了Bug,建议大家直接在feature分支上进行修复。修复后的提测上线流程与新需求加入的流程一致。

修改预发布环境Bug

在测试出现了Bug,首先要回归下分支是否同样存在这个问题。如果存在,修复流程与修复测试环境Bug流程一致;如果不存在,这种可能性比较少,大部分是数据兼容问题,环境配置问题等。

修改正式环境Bug

在 master 测试出现了Bug,首先要回归下 release 和 develop 分支是否同样存在这个问题。如果存在,修复流程 与 修复测试环境 Bug流程一致。如果不存在,这种可能性也比较少,大部分是数据兼容问题,环境配置问题等。

如果存在,修复流程 与 修复测试环境 Bug流程一致;如果不存在,这种可能性也比较少,大部分是数据兼容问题,环境配置问题等。

紧急修复正式环境Bug

需求在测试环节未测试出 Bug,上线运行一段时候后出现了 Bug,需要紧急修复的。有的企业面对紧急修复时,支持不进行测试环境的验证,但还是建议验证下预发布环境。可基于 master 创建 hotfix/xxx 分支,修复完毕后发布到 master 验证,验证完毕后,将master 代码合并到 develop 分支,同时删掉 hotfix/xxx 分支。

拓展阅读

其他的DevOps研发平台

腾讯coding

阿里云效

拓展实践

阿里飞流flow分支模型,及项目版本管理实践:项目版本管理的最佳实践:飞流Flow(阿里AoneFlow)篇-CSDN博客

**小结

本篇涉及的部分git命令:

git tag [name] [commit ID]:给commit打标签 

git tag:查看当前所有存在的标签

git tag -a [name] -m "XXX" [commit_id]:给新创建的标签添加标签说明

git show [tag名称]:查看这个tag对应的详细信息

git tag -d <tag名称>:删除标签

git push origin <tagname>:推送某个标签到远程

git push origin --tags:一次性把本地所有标签全部推送到远端

git push origin :<tag名称>:本地已删除某标签,要让远程也删除该标签

git branch -vv:查看本地仓库分支和远程仓库分支对应关系

git branch --set-upstream-to=origin/<远程分支名> <本地分支名>:手动给远程分支和本地分支建立关联

git branch -a:查看最近一次pull时本地和远程所有分支的情况

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;