背景
近日写了一个chrome插件的starter-boilerplate。但这类boilerplate被人们使用的方式常常是整合在cli库中。由于仓库本身的模板性质和git历史存在,并不合适使用npm分发或git clone
快速搭建项目骨架。
碰巧此前学习svelte的时候接触到了degit,degit做的事很简单,复制git仓库代码。这也正是一个称职的boilerplate发挥光和热的方式。
degit使用
|
|
上面是一个degit的基本用法,类似git clone
指定仓库地址和本地目录名,默认将项目当前master
分支的代码拷贝到本地。还可以在仓库后使用#
分隔,指定分支名、tag名或commit hash。目前(2019/11/12)degit支持github、gitlab、BitBucket以及Sourcehut,暂不支持私有仓库。
在一些情况下,我们可能希望在拷贝完代码后进行一些后置操作,如拷贝关联仓库或删除不必要文件等。对此,degit设计了actions来支持,可以在当前目录的degit.json
中声明。目前actions只有clone
和remove
两种。
|
|
degit优势
如README中提到的,degit和git clone --depth 1
还是有所区别的:
git clone
后,终归还是会有个.git
目录,需要手动重置- degit在实现时增加了缓存策略,在有些情况下不需要重复下载代码,速度更快
- “更少的字数”(
degit user/repo
而不是git clone --depth 1 git@github.com:user/repo
) - 灵活度更高,如前后置操作如actions的支持
- 更好的可扩展性,未来可以在degit基础上实现交互等更复杂的设计
degit原理
那么degit快在哪里?它的思路借鉴于zel和gittar,即方便快捷地从git仓库中下载需要的源代码。原理上,利用某些git平台url的特定规则,从平台下载tar.gz包,再本地解压。
degit实现集中在src/index.js
中。src/bin.js
只用来实现cli部分的入口代码,src/utils.js
则包含了一些工具函数。
入口
在src/bin.js
中,流程分下面几步:
- 利用mri做了基本的参数处理
- 实例化Degit对象,注册logger的监听方法
- 调用
clone
方法
|
|
Degit初始化
对象实例包含下面几个成员,其中repo信息需要处理后才能拿到。
src
,string,用户输入的仓库地址cache
,boolean,是否使用缓存,来自命令行-c
或--cache
参数force
,boolean,目标文件夹有内容时,是否覆盖,来自-f
或--force
参数verbose
,boolean,是否打印详细日志,来自-v
或--verbose
参数repo
,处理src
拿到仓库的详情,包括site
,网页域名user
,用户/组织名name
,仓库名ref
,分支、tag、commit hashurl
,完整的HTTP url
directiveActions
,actions配置对应的处理函数,包含clone
,递归处理src的仓库remove
,调用remove
方法移除指定文件
repo信息来自src经过正则匹配出的详细信息。由于要利用一些git平台的url拼接规则,需要排除已知平台以外的url。
|
|
仓库下载
下载仓库流程如下:
获取缓存信息
degit的缓存放在/home
或/tmp
下的.degit
目录下,按照site/user/name
的目录组织。
|
|
目录下有一个map.json
和缓存的代码tar.gz包,包名格式为<commit-hash>.tar.gz
。在map.json保存着此前使用过的分支名/tag名/简写commit名到commit hash的最新映射关系。形如下方:
|
|
这一步会尝试使用parse好的site
、user
、name
属性找已有的缓存的map.json
。没有找到时返回{}
。
获取commit hash
这一步分两种情况;
- 使用缓存时,直接从上一步拿到的
map.json
里面找ref
对应的commit hash - 不使用缓存时,需要从远端仓库拿分支名/tag名到commit hash的对应关系(使用
git ls-remote
完成)。之后格式化为结构化数据并从中寻找ref
对应的commit hash。如果中途失败,则fallback到使用缓存的方式。
|
|
这一步若未找到hash,则无法构造下载的url,从而需要抛出错误。
构造下载地址
根据不同的git平台固定的源码tar.gz归档url规则,构造下载的url,这也是degit思路的基础。目前支持gitlab、bucket、github风格的url。
|
|
创建目录并下载
不使用缓存时,会在创建缓存目录并下载。另外,指定-f
或--force
参数,会覆盖已有文件路径。最后使用https模块下载文件。
|
|
更新缓存
下载成功会更新本地缓存,保证以后使用缓存时能使用尽量新的包。
- 当前使用包的commit hash如果和指定分支/tag/commit hash对应的hash一致,则不需要更新
- 在需要更新时,检查老的hash是否还有使用,如果没有使用,则清除hash对应的tar.gz包
- 更新map.json里的对应关系
|
|
解压tar.gz包
创建cli中输入的目标目录,并将已下载到缓存中tar.gz包解压到目标路径下。
|
|
actions
处理
如果在当前目录下获取到了degit.json
,则执行后续的clone
或remove
操作。
- clone,在目标目录下继续一遍clone流程
- remove,删除指定文件或文件夹
degit改造
degit虽好,但从上面也可以看到,支持仓库比较有限,且不支持私有仓库。在公司内部,无法从url推断git仓库类型时,degit就无法工作了。不过,借助degit本身的设计,稍微改造上面提到的“degit初始化”,“构造下载地址”部分,就可以让degit通过传参url风格的形式支持私有仓库。
- 新增
-s
或--style
命令行入参,表示git仓库url的风格,目前设计有github、gitlab、bitbucket这几个degit原始就支持的形式。 - 解析仓库地址信息时,若有style入参,则先判断是否在上述允许范围内;保留原有从域名解析style的部分,新增若未解析出style,则从入参里取;最后再抛出不支持的仓库地址错误
- 解析返回数据结构中,新增
style
字段表示url风格,原有的site
为避免歧义,直接使用域名代替原有的域名前缀 - 在构造下载地址时,直接根据style字段拼接url
|
|
可能存在的问题
绝大多数私有仓库,都会对用户身份做校验,直接访问tar.gz链接会报401错误。这需要根据不同的内部平台自己做处理了。
因为特殊原因,改造后的包和代码不提供。
–END–