阅读 remote-git-tags 源码

阅读 remote-git-tags 源码

前言

时隔很久,重新开始阅读源码的过程,因此找个相对比较简单的包remote-git-tags进行阅读,并熟悉整套阅读流程(git subtree,vscode 中 nodejs 调试)

介绍

remote-git-tags 是一个只有 22 行的相对比较简单的包,他的主要作用就是获取仓库中的所有 tags

源码

Entry

老规矩,先看入口文件,index.js,(从 package.json 中的exports字段中可以找到)

1
2
3
// package.json
...
"exports": "./index.js",

源码比较简单,原理是通过运行 bash 命令:git ls-remote --tags repoUrl 获取 tags

(可点击git ls-remote使用细节)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
➜ git ls-remote --tags https://github.com/sindresorhus/got
67316ee37f0028d660f530970ab74a571247ff10 refs/tags/v0.1.0
1e7746d8c44a75ee9ec8ed94356dd5e8ba9c3bc2 refs/tags/v0.1.0^{}
1ec67ee5906c8b8e8fb849b925479a31707a404d refs/tags/v0.1.1
98d1263b1331573dbc6c3110addb038dc5804bb8 refs/tags/v0.1.1^{}
44c54b1b66f53d58c422c68c55c024827706a4b5 refs/tags/v0.2.0
32b0c033e26594a36b16ab7305e4ec5e54972cc1 refs/tags/v0.2.0^{}
6baafc7a29aa7a6a00bc818b0d100eb11268b943 refs/tags/v0.3.0
bc6987517edb1dfe54fc495f9b84a9df4b9f1f7c refs/tags/v0.3.0^{}
4274ba19113edb51f64fe5ab278b5d823b83155c refs/tags/v1.0.0
9a593acd8412f5e412f0e7e6044a88a5ea93ca90 refs/tags/v1.0.0^{}
c0b6804510ac9105591ee338ff5ad0b0e395ac71 refs/tags/v1.0.1
37322366c7e85c898fbc9bf92bd32afa9a228aae refs/tags/v1.0.1^{}
4e736f374d7af685646b1184c06933c71c07ff99 refs/tags/v1.1.0

让我们来直接看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { promisify } from 'node:util';
import childProcess from 'node:child_process';

const execFile = promisify(childProcess.execFile);

export default async function remoteGitTags(repoUrl) {
// 把所有 tags 和对应的 hash值 存在 Map 对象中。
const { stdout } = await execFile('git', ['ls-remote', '--tags', repoUrl]);
const tags = new Map();

for (const line of stdout.trim().split('\n')) {
const [hash, tagReference] = line.split('\t');

// Strip off the indicator of dereferenced tags so we can override the
// previous entry which points at the tag hash and not the commit hash
// `refs/tags/v9.6.0^{}` → `v9.6.0`
const tagName = tagReference
.replace(/^refs\/tags\//, '')
.replace(/\^{}$/, '');

tags.set(tagName, hash);
}

return tags;
}

调试如图,我们可以看到相关代码对应的 debug 信息

1700275681824_VRng7p

Dependencies

node:util

可以使用 node: 前缀来识别核心模块,在这种情况下它会绕过 require 缓存。例如,require('node:http') 将始终返回内置的 HTTP 模块,即使有该名称的 require.cache 条目。细节可查看官网

promisify

将常见的错误优先的回调风格的函数(也就是将 (err, value) => ... 回调作为最后一个参数),并返回一个返回 promise 的版本。

1
2
3
4
5
6
7
8
9
10
11
const util = require('node:util');
const fs = require('node:fs');

const stat = util.promisify(fs.stat);
stat('.')
.then((stats) => {
// Do something with `stats`
})
.catch((error) => {
// Handle the error.
});

帮助理解,伪代码实现如下,真实源码可自行参考node 官网

1
2
3
4
5
6
7
8
9
10
11
12
function promisify(fn) {
return function (...args) {
return new Promise((resolve, reject) => {
fn(...args, (error, value) => {
if (error) {
return reject(error);
}
resolve(value);
});
});
};
}

node:child_process

execFile

  • child_process.exec(): 衍生 shell 并在该 shell 中运行命令,完成后将 stdout 和 stderr 传给回调函数。
  • child_process.execFile(): 与 child_process.exec() 类似,不同之处在于,默认情况下,它直接衍生命令,而不先衍生 shell。
  • child_process.fork(): 衍生新的 Node.js 进程并使用建立的 IPC 通信通道(其允许在父子进程之间发送消息)调用指定的模块。
  • child_process.execSync(): child_process.exec() 的同步版本,其将阻塞 Node.js 事件循环。
  • child_process.execFileSync(): child_process.execFile() 的同步版本,其将阻塞 Node.js 事件循环。

总结

多看源码,多思考 🤔~

参考链接

作者

Sea

发布于

2023-11-18

更新于

2023-11-18

许可协议

评论