-
Notifications
You must be signed in to change notification settings - Fork 29
/
gatsby-node.js
174 lines (157 loc) · 5.49 KB
/
gatsby-node.js
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
const Git = require("simple-git");
const fastGlob = require("fast-glob");
const fs = require(`fs-extra`)
const { createFileNode } = require("gatsby-source-filesystem/create-file-node");
const GitUrlParse = require("git-url-parse");
function getCachedRepoPath(name, programDir) {
return require("path").join(
programDir,
`.cache`,
`gatsby-source-git`,
name
);
}
async function isAlreadyCloned(remote, path) {
const existingRemote = await Git(path).listRemote(["--get-url"]);
return existingRemote.trim() == remote.trim();
}
async function getTargetBranch(repo, branch) {
if (typeof branch == `string`) {
return `origin/${branch}`;
} else {
return repo.raw(["symbolic-ref", "--short", "refs/remotes/origin/HEAD"]).then(result => result.trim());
}
}
async function getRepo(path, remote, branch, fetchDepth) {
// If the directory doesn't exist or is empty, clone. This will be the case if
// our config has changed because Gatsby trashes the cache dir automatically
// in that case. Note, however, that if just the branch name changes, then the directory
// will still exist and we fall into the `isAlreadyCloned` block below.
let opts = [];
const depth = fetchDepth ?? 1
if(depth > 0) {
opts.push(`--depth`, depth);
}
if (!fs.existsSync(path) || fs.readdirSync(path).length === 0) {
if (typeof branch == `string`) {
opts.push(`--branch`, branch);
}
await Git().clone(remote, path, opts);
return Git(path);
} else if (await isAlreadyCloned(remote, path)) {
const repo = await Git(path);
const target = await getTargetBranch(repo, branch);
if (typeof branch == `string`) {
// First add the remote and fetch. This is a no-op if the branch hasn't changed but
// it's necessary when the configured branch has changed. This is because, due to
// the clone options used in the block above, only one remote branch is added, i.e.,
// the git config fetch refspec looks like this after cloning with a provided branch:
//
/// [remote "origin"]
// url = [email protected]:<org>/<repo>.git
// fetch = +refs/heads/<branch>:refs/remotes/origin/<branch>
await repo
.remote(['set-branches', 'origin', branch])
.then(() => repo.fetch('origin', branch))
.then(() => repo.checkout(branch))
}
await repo
.fetch(opts)
.then(() => repo.reset([`--hard`, target]));
return repo;
} else {
throw new Error(`Can't clone to target destination: ${localPath}`);
}
}
exports.sourceNodes = async (
{
actions: { createNode },
store,
createNodeId,
createContentDigest,
reporter
},
{ name, remote, branch, patterns = `**`, local, fetchDepth}
) => {
const programDir = store.getState().program.directory;
const localPath = local || getCachedRepoPath(name, programDir);
const parsedRemote = GitUrlParse(remote);
let repo;
try {
repo = await getRepo(localPath, remote, branch, fetchDepth);
} catch (e) {
return reporter.error(e);
}
parsedRemote.git_suffix = false;
parsedRemote.webLink = parsedRemote.toString("https");
delete parsedRemote.git_suffix;
let ref = await repo.raw(["rev-parse", "--abbrev-ref", "HEAD"]);
parsedRemote.ref = ref.trim();
const repoFiles = await fastGlob(patterns, {
cwd: localPath,
absolute: true
});
const remoteId = createNodeId(`git-remote-${name}`);
// Create a single graph node for this git remote.
// Filenodes sourced from it will get a field pointing back to it.
await createNode(
Object.assign(parsedRemote, {
id: remoteId,
sourceInstanceName: name,
parent: null,
children: [],
internal: {
type: `GitRemote`,
content: JSON.stringify(parsedRemote),
contentDigest: createContentDigest(parsedRemote)
}
})
);
const createAndProcessNode = path => {
return createFileNode(path, createNodeId, {
name: name,
path: localPath
}).then(fileNode => {
const relativePath = fileNode.relativePath;
return repo.log({
file: relativePath
})
.then(log => {
const latest = log.latest;
const {date, message, author_name} = latest;
fileNode.modifiedTime = new Date(Date.parse(date)).toISOString();
fileNode.message = message;
fileNode.authorName = author_name;
return fileNode;
});
})
.then(fileNode => {
// Add a link to the git remote node
fileNode.gitRemote___NODE = remoteId;
// Then create the node, as if it were created by the gatsby-source
// filesystem plugin.
return createNode(fileNode, {
name: `gatsby-source-filesystem`
});
});
};
return Promise.all(repoFiles.map(createAndProcessNode));
};
exports.onPreInit = async ({ reporter, emitter, store }, pluginOptions) => {
emitter.on('DELETE_CACHE', async () => {
// The gatsby cache delete algorithm doesn't delete the hidden files, like
// our .git directories, causing problems for our plugin;
// So we delete our cache ourself.
const programDir = store.getState().program.directory;
const localPath = getCachedRepoPath(pluginOptions.name, programDir);
try {
// Attempt to empty dir if remove fails,
// like when directory is mount point.
await fs.remove(localPath).catch(() => fs.emptyDir(localPath))
reporter.verbose(`Removed gatsby-source-git cache directory: ${localPath}`);
} catch (e) {
reporter.error(`Failed to remove gatsby-source-git files.`, e);
}
});
}
exports.onCreateNode;