Skip to content

Commit ec36b41

Browse files
mournertmcw
authored andcommitted
feat: Add git submodules support to github linking (#1270)
* add git submodules support in github linking * more reliable submodule origin parsing
1 parent a1a2310 commit ec36b41

File tree

8 files changed

+103
-46
lines changed

8 files changed

+103
-46
lines changed

__tests__/lib/git/find_git.js

+15-5
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,22 @@ const findGit = require('../../../src/git/find_git');
55

66
test('findGit', function() {
77
mock(mockRepo.master);
8+
const root =
9+
path.parse(__dirname).root + path.join('my', 'repository', 'path');
10+
const masterPaths = findGit(path.join(root, 'index.js'));
11+
mock.restore();
812

9-
const root = path.parse(__dirname).root;
10-
11-
expect(
12-
findGit(root + path.join('my', 'repository', 'path', 'index.js'))
13-
).toBe(root + path.join('my', 'repository', 'path', '.git'));
13+
expect(masterPaths).toEqual({
14+
git: path.join(root, '.git'),
15+
root
16+
});
1417

18+
mock(mockRepo.submodule);
19+
const submodulePaths = findGit(path.join(root, 'index.js'));
1520
mock.restore();
21+
22+
expect(submodulePaths).toEqual({
23+
git: path.join(path.dirname(root), '.git', 'modules', 'path'),
24+
root
25+
});
1626
});

__tests__/lib/git/url_prefix.js

+19-6
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,33 @@ const parsePackedRefs = getGithubURLPrefix.parsePackedRefs;
55

66
test('getGithubURLPrefix', function() {
77
mock(mockRepo.master);
8-
9-
expect(getGithubURLPrefix('/my/repository/path/')).toBe(
10-
'https://github.com/foo/bar/blob/this_is_the_sha/'
11-
);
12-
8+
const masterUrl = getGithubURLPrefix({
9+
git: '/my/repository/path/.git',
10+
root: '/my/repository/path'
11+
});
1312
mock.restore();
1413

14+
expect(masterUrl).toBe('https://github.com/foo/bar/blob/this_is_the_sha/');
15+
1516
mock(mockRepo.detached);
17+
const detachedUrl = getGithubURLPrefix({
18+
git: '/my/repository/path/.git',
19+
root: '/my/repository/path'
20+
});
21+
mock.restore();
1622

17-
expect(getGithubURLPrefix('/my/repository/path/')).toBe(
23+
expect(detachedUrl).toBe(
1824
'https://github.com/foo/bar/blob/e4cb2ffe677571d0503e659e4e64e01f45639c62/'
1925
);
2026

27+
mock(mockRepo.submodule);
28+
const submoduleUrl = getGithubURLPrefix({
29+
git: '/my/repository/.git/modules/path',
30+
root: '/my/repository/path'
31+
});
2132
mock.restore();
33+
34+
expect(submoduleUrl).toBe('https://github.com/foo/bar/blob/this_is_the_sha/');
2235
});
2336

2437
test('parsePackedRefs', function() {

__tests__/utils.js

+26
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,32 @@ module.exports.mockRepo = {
7474
}
7575
}
7676
},
77+
submodule: {
78+
'/my': {
79+
repository: {
80+
path: {
81+
'.git': 'gitdir: ../.git/modules/path',
82+
'index.js': 'module.exports = 42;'
83+
},
84+
'.git': {
85+
config:
86+
'[submodule "path"]\n' +
87+
'url = https://github.com/foo/bar\n' +
88+
'active = true',
89+
modules: {
90+
path: {
91+
HEAD: 'ref: refs/heads/master',
92+
refs: {
93+
heads: {
94+
master: 'this_is_the_sha'
95+
}
96+
}
97+
}
98+
}
99+
}
100+
}
101+
}
102+
},
77103
malformed: {
78104
'/my': {
79105
repository: {

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"glob": "^7.1.2",
4646
"globals-docs": "^2.4.0",
4747
"highlight.js": "^9.15.5",
48+
"ini": "^1.3.5",
4849
"js-yaml": "^3.10.0",
4950
"lodash": "^4.17.10",
5051
"mdast-util-inject": "^1.1.0",
@@ -58,7 +59,6 @@
5859
"remark-html": "^8.0.0",
5960
"remark-reference-links": "^4.0.1",
6061
"remark-toc": "^5.0.0",
61-
"remote-origin-url": "0.4.0",
6262
"resolve": "^1.8.1",
6363
"stream-array": "^1.1.2",
6464
"strip-json-comments": "^2.0.1",

src/git/find_git.js

+15-8
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,25 @@ const fs = require('fs');
55
* Given a full path to a single file, iterate upwards through the filesystem
66
* to find a directory with a .git file indicating that it is a git repository
77
* @param filename any file within a repository
8-
* @returns repository path
8+
* @returns repository root & its .git folder paths
99
*/
1010
function findGit(filename) {
11-
const paths = filename.split(path.sep);
12-
for (let i = paths.length; i > 0; i--) {
13-
const p = path.resolve(
14-
paths.slice(0, i).join(path.sep) + path.sep + '.git'
15-
);
16-
if (fs.existsSync(p)) {
17-
return p;
11+
let root = path.resolve(filename);
12+
while (root) {
13+
root = path.dirname(root);
14+
let git = path.join(root, '.git');
15+
if (!fs.existsSync(git)) continue;
16+
17+
if (fs.statSync(git).isFile()) {
18+
// git submodule
19+
const matches = fs.readFileSync(git, 'utf8').match(/gitdir: (.*)/);
20+
if (!matches) return null;
21+
git = path.join(root, matches[1]);
1822
}
23+
24+
return { root, git };
1925
}
26+
return null;
2027
}
2128

2229
module.exports = findGit;

src/git/url_prefix.js

+18-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const fs = require('fs');
22
const path = require('path');
33
const gitUrlParse = require('git-url-parse');
4-
const getRemoteOrigin = require('remote-origin-url');
4+
const ini = require('ini');
55

66
/**
77
* Sometimes git will [pack refs](https://git-scm.com/docs/git-pack-refs)
@@ -32,15 +32,15 @@ function parsePackedRefs(packedRefs, branchName) {
3232
* @returns {string} base HTTPS url of the GitHub repository
3333
* @throws {Error} if the root is not a git repo
3434
*/
35-
function getGithubURLPrefix(root) {
35+
function getGithubURLPrefix({ git, root }) {
3636
let sha;
3737
try {
38-
const head = fs.readFileSync(path.join(root, '.git', 'HEAD'), 'utf8');
38+
const head = fs.readFileSync(path.join(git, 'HEAD'), 'utf8');
3939
const branch = head.match(/ref: (.*)/);
4040
if (branch) {
4141
const branchName = branch[1];
42-
const branchFileName = path.join(root, '.git', branchName);
43-
const packedRefsName = path.join(root, '.git', 'packed-refs');
42+
const branchFileName = path.join(git, branchName);
43+
const packedRefsName = path.join(git, 'packed-refs');
4444
if (fs.existsSync(branchFileName)) {
4545
sha = fs.readFileSync(branchFileName, 'utf8');
4646
} else if (fs.existsSync(packedRefsName)) {
@@ -57,7 +57,19 @@ function getGithubURLPrefix(root) {
5757
sha = head;
5858
}
5959
if (sha) {
60-
const parsed = gitUrlParse(getRemoteOrigin.sync(root));
60+
let origin;
61+
if (git.indexOf(root) === 0) {
62+
const config = ini.parse(
63+
fs.readFileSync(path.join(git, 'config'), 'utf8')
64+
);
65+
origin = config['remote "origin"'].url;
66+
} else {
67+
const config = ini.parse(
68+
fs.readFileSync(path.join(git, '..', '..', 'config'), 'utf8')
69+
);
70+
origin = config[`submodule "${path.basename(git)}"`].url;
71+
}
72+
const parsed = gitUrlParse(origin);
6173
parsed.git_suffix = false; // eslint-disable-line
6274
return parsed.toString('https') + '/blob/' + sha.trim() + '/';
6375
}

src/github.js

+8-7
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,16 @@ const getGithubURLPrefix = require('./git/url_prefix');
1010
* @returns {Object} comment with github inferred
1111
*/
1212
module.exports = function(comment) {
13-
const repoPath = findGit(comment.context.file);
14-
const root = repoPath ? path.dirname(repoPath) : '.';
15-
const urlPrefix = getGithubURLPrefix(root);
16-
const fileRelativePath = comment.context.file
17-
.replace(root + path.sep, '')
18-
.split(path.sep)
19-
.join('/');
13+
const paths = findGit(comment.context.file);
14+
15+
const urlPrefix = paths && getGithubURLPrefix(paths);
2016

2117
if (urlPrefix) {
18+
const fileRelativePath = comment.context.file
19+
.replace(paths.root + path.sep, '')
20+
.split(path.sep)
21+
.join('/');
22+
2223
let startLine;
2324
let endLine;
2425

yarn.lock

+1-13
Original file line numberDiff line numberDiff line change
@@ -3076,7 +3076,7 @@ inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
30763076
version "2.0.3"
30773077
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
30783078

3079-
ini@^1.3.2, ini@^1.3.3, ini@~1.3.0:
3079+
ini@^1.3.2, ini@^1.3.5, ini@~1.3.0:
30803080
version "1.3.5"
30813081
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
30823082

@@ -4868,12 +4868,6 @@ parse-filepath@^1.0.2:
48684868
map-cache "^0.2.0"
48694869
path-root "^0.1.1"
48704870

4871-
parse-git-config@^0.2.0:
4872-
version "0.2.0"
4873-
resolved "https://registry.yarnpkg.com/parse-git-config/-/parse-git-config-0.2.0.tgz#272833fdd15fea146fb75d336d236b963b6ff706"
4874-
dependencies:
4875-
ini "^1.3.3"
4876-
48774871
parse-github-repo-url@^1.3.0:
48784872
version "1.4.1"
48794873
resolved "https://registry.yarnpkg.com/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz#9e7d8bb252a6cb6ba42595060b7bf6df3dbc1f50"
@@ -5420,12 +5414,6 @@ remark@^9.0.0:
54205414
remark-stringify "^5.0.0"
54215415
unified "^6.0.0"
54225416

5423-
5424-
version "0.4.0"
5425-
resolved "https://registry.yarnpkg.com/remote-origin-url/-/remote-origin-url-0.4.0.tgz#4d3e2902f34e2d37d1c263d87710b77eb4086a30"
5426-
dependencies:
5427-
parse-git-config "^0.2.0"
5428-
54295417
remove-bom-buffer@^3.0.0:
54305418
version "3.0.0"
54315419
resolved "https://registry.yarnpkg.com/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz#c2bf1e377520d324f623892e33c10cac2c252b53"

0 commit comments

Comments
 (0)