• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

discoveryjs / scan-git / 6498585099

12 Oct 2023 04:50PM UTC coverage: 84.293% (+1.6%) from 82.729%
6498585099

push

github

lahmatiy
1.1.2

335 of 379 branches covered (0.0%)

Branch coverage included in aggregate %.

2139 of 2556 relevant lines covered (83.69%)

291.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.16
/src/files-methods.ts
1
import { ReadObjectByHash, ReadObjectByOid, ResolveRef, Tree, TreeEntry } from './types';
1✔
2
import { EMPTY_TREE_OID } from './const.js';
1✔
3
import { parseAnnotatedTag, parseCommit, parseTree } from './parse-object.js';
1✔
4
import {
1✔
5
    ADDED,
1✔
6
    REMOVED,
1✔
7
    MODIFIED,
1✔
8
    diffTrees,
1✔
9
    findTreeEntry,
1✔
10
    findTreeEntries
1✔
11
} from './tree-utils.js';
1✔
12

1✔
13
type ReadTree = (hash: Buffer) => Promise<Tree>;
1✔
14
type FileEntry = { path: string; hash: string };
1✔
15
type FileListEntry = string | FileEntry;
1✔
16
type FileDeltaEntry = { path: string; hash: string };
1✔
17
type FileDelta = {
1✔
18
    add: FileDeltaEntry[];
1✔
19
    modify: (FileDeltaEntry & { prevHash: string })[];
1✔
20
    remove: FileDeltaEntry[];
1✔
21
};
1✔
22
type Path = { path: string; segments: string[] };
1✔
23
type FilenameEntry<T extends boolean> = T extends true ? FileEntry : string;
1✔
24

1✔
25
async function collectTreeFiles(
39✔
26
    hash: Buffer,
39✔
27
    readTree: ReadTree,
39✔
28
    prefix: string,
39✔
29
    filenames: FileListEntry[],
39✔
30
    filesWithHash = false
39✔
31
): Promise<any> {
39✔
32
    const tree = await readTree(hash);
39✔
33
    const taskCount = 10;
39✔
34
    const tasks: (Promise<any> | null)[] = Array.from({ length: taskCount }, () => null);
390✔
35
    let taskNum = 0;
39✔
36

39✔
37
    for (const entry of tree) {
262✔
38
        if (entry.isTree) {
30✔
39
            if (taskNum++ % taskCount === 0) {
14✔
40
                await Promise.all(tasks);
14✔
41
            }
14✔
42

30✔
43
            tasks[taskNum] = collectTreeFiles(
30✔
44
                entry.hash,
30✔
45
                readTree,
30✔
46
                `${prefix}${entry.path}/`,
30✔
47
                filenames,
30✔
48
                filesWithHash
30✔
49
            );
30✔
50
        } else {
232✔
51
            const path = `${prefix}${entry.path}`;
232✔
52

232✔
53
            if (filesWithHash) {
38✔
54
                filenames.push({ path, hash: entry.hash.toString('hex') });
38✔
55
            } else {
194✔
56
                filenames.push(path);
194✔
57
            }
194✔
58
        }
232✔
59
    }
262✔
60

39✔
61
    return Promise.all(tasks);
39✔
62
}
39✔
63

1✔
64
function addSubtreeToDelta(
5✔
65
    target: FileDeltaEntry[],
5✔
66
    pathPrefix: string,
5✔
67
    entry: TreeEntry,
5✔
68
    readTree: ReadTree
5✔
69
) {
5✔
70
    return collectTreeFiles(entry.hash, readTree, `${pathPrefix}${entry.path}/`, target, true);
5✔
71
}
5✔
72

1✔
73
async function collectFilesDelta(
6✔
74
    prevHash: Buffer,
6✔
75
    nextHash: Buffer,
6✔
76
    readObjectByHash: ReadObjectByHash,
6✔
77
    readTree: ReadTree,
6✔
78
    prefix: string,
6✔
79
    delta: FileDelta
6✔
80
) {
6✔
81
    const [{ object: prevTree }, { object: nextTree }] = await Promise.all([
6✔
82
        readObjectByHash(prevHash),
6✔
83
        readObjectByHash(nextHash)
6✔
84
    ]);
6✔
85

6✔
86
    for (const entry of diffTrees(prevTree, nextTree)) {
20✔
87
        switch (entry.type) {
20✔
88
            case ADDED:
19✔
89
                if (entry.isTree) {
5✔
90
                    await addSubtreeToDelta(delta.add, prefix, entry, readTree);
5✔
91
                } else {
14✔
92
                    delta.add.push({
14✔
93
                        path: `${prefix}${entry.path}`,
14✔
94
                        hash: entry.hash.toString('hex')
14✔
95
                    });
14✔
96
                }
14✔
97
                break;
19✔
98

20✔
99
            case REMOVED:
×
100
                if (entry.isTree) {
×
101
                    await addSubtreeToDelta(delta.remove, prefix, entry, readTree);
×
102
                } else {
×
103
                    delta.remove.push({
×
104
                        path: `${prefix}${entry.path}`,
×
105
                        hash: entry.hash.toString('hex')
×
106
                    });
×
107
                }
×
108
                break;
×
109

20✔
110
            case MODIFIED:
1✔
111
                if (entry.isTree) {
×
112
                    await collectFilesDelta(
×
113
                        entry.prevHash,
×
114
                        entry.hash,
×
115
                        readObjectByHash,
×
116
                        readTree,
×
117
                        `${prefix}${entry.path}/`,
×
118
                        delta
×
119
                    );
×
120
                } else {
×
121
                    delta.modify.push({
1✔
122
                        path: `${prefix}${entry.path}`,
1✔
123
                        hash: entry.hash.toString('hex'),
1✔
124
                        prevHash: entry.prevHash.toString('hex')
1✔
125
                    });
1✔
126
                }
1✔
127
                break;
1✔
128
        }
20✔
129
    }
20✔
130
}
6✔
131

1✔
132
async function collectTreeEntries(
7✔
133
    result: TreeEntry[],
7✔
134
    pathsInfo: Path[],
7✔
135
    readObjectByHash: ReadObjectByHash,
7✔
136
    treeHash: Buffer,
7✔
137
    level: number
7✔
138
): Promise<void> {
7✔
139
    // Group segments with the same first segment
7✔
140
    const groups = new Map<string, Path[]>();
7✔
141
    for (const pathInfo of pathsInfo) {
13✔
142
        const key = pathInfo.segments[level];
13✔
143
        const group = groups.get(key);
13✔
144

13✔
145
        if (group === undefined) {
11✔
146
            groups.set(key, [pathInfo]);
11✔
147
        } else {
2✔
148
            group.push(pathInfo);
2✔
149
        }
2✔
150
    }
13✔
151

7✔
152
    // Find tree entries for the groups
7✔
153
    const { object: treeObject } = await readObjectByHash(treeHash);
7✔
154
    const groupPaths = Array.from(groups.keys());
7✔
155
    const foundEntries = findTreeEntries(treeObject, groupPaths);
7✔
156

7✔
157
    for (const entry of foundEntries) {
8✔
158
        const group = groups.get(entry.path) as Path[];
8✔
159
        const subtreePaths: Path[] = [];
8✔
160

8✔
161
        for (const pathInfo of group) {
10✔
162
            if (pathInfo.segments.length === level + 1) {
8✔
163
                entry.path = pathInfo.path;
8✔
164
                result.push(entry);
8✔
165
            } else if (entry.isTree) {
2✔
166
                subtreePaths.push(pathInfo);
2✔
167
            }
2✔
168
        }
10✔
169

8✔
170
        if (subtreePaths.length > 0) {
2✔
171
            await collectTreeEntries(result, subtreePaths, readObjectByHash, entry.hash, level + 1);
2✔
172
        }
2✔
173
    }
8✔
174
}
7✔
175

1✔
176
async function resolveRefToTree(oid: string, readObject: ReadObjectByOid): Promise<string> {
27✔
177
    const { type, object } = await readObject(oid);
27✔
178

27✔
179
    // Resolve annotated tag objects to whatever
27✔
180
    if (type === 'tag') {
×
181
        return resolveRefToTree(parseAnnotatedTag(object).object, readObject);
×
182
    }
×
183

27✔
184
    // Resolve commits to trees
27✔
185
    if (type === 'commit') {
21✔
186
        return parseCommit(object).tree;
21✔
187
    }
6✔
188

6✔
189
    if (type !== 'tree') {
×
190
        throw new Error(`Object ${oid} must be a "tree" but "${type}"`);
×
191
    }
6✔
192

6✔
193
    return oid;
6✔
194
}
6✔
195

1✔
196
export function createFilesMethods(
13✔
197
    readObjectByOid: ReadObjectByOid,
13✔
198
    readObjectByHash: ReadObjectByHash,
13✔
199
    resolveRef: ResolveRef
13✔
200
) {
13✔
201
    async function treeOidFromRef(ref: string) {
27✔
202
        if (typeof ref !== 'string') {
×
203
            return EMPTY_TREE_OID;
×
204
        }
×
205

27✔
206
        const oid = await resolveRef(ref);
27✔
207
        const treeOid = await resolveRefToTree(oid, readObjectByOid);
27✔
208

27✔
209
        return treeOid;
27✔
210
    }
27✔
211
    async function readTree(hash: Buffer) {
39✔
212
        const { object: treeObject } = await readObjectByHash(hash);
39✔
213
        const tree = parseTree(treeObject);
39✔
214

39✔
215
        return tree;
39✔
216
    }
39✔
217
    async function getPathEntry(path: string, ref = 'HEAD'): Promise<TreeEntry | null> {
6✔
218
        const treeOid = await treeOidFromRef(ref);
6✔
219
        const pathSegments = path.split('/');
6✔
220
        let treeHash = Buffer.from(treeOid, 'hex');
6✔
221

6✔
222
        for (let i = 0; i < pathSegments.length; i++) {
9✔
223
            const segment = pathSegments[i];
9✔
224
            const { object: treeObject } = await readObjectByHash(treeHash);
9✔
225
            const entry = findTreeEntry(treeObject, segment);
9✔
226

9✔
227
            if (entry === null) {
2✔
228
                break;
2✔
229
            }
7✔
230

7✔
231
            if (i === pathSegments.length - 1) {
4✔
232
                entry.path = path;
4✔
233
                return entry;
4✔
234
            }
3✔
235

3✔
236
            if (!entry.isTree) {
×
237
                break;
×
238
            }
3✔
239

3✔
240
            treeHash = entry.hash;
3✔
241
        }
2✔
242

2✔
243
        return null;
2✔
244
    }
2✔
245

13✔
246
    async function getPathsEntries(paths: string[], ref = 'HEAD'): Promise<TreeEntry[]> {
5✔
247
        const treeOid = await treeOidFromRef(ref);
5✔
248
        const entries: TreeEntry[] = [];
5✔
249
        const treeHash = Buffer.from(treeOid, 'hex');
5✔
250

5✔
251
        // Sort paths and split them into segments
5✔
252
        const sortedPaths = [...paths].sort();
5✔
253
        const pathSegments = sortedPaths.map((path) => ({
11✔
254
            path,
11✔
255
            segments: path.split('/')
11✔
256
        }));
11✔
257

5✔
258
        await collectTreeEntries(entries, pathSegments, readObjectByHash, treeHash, 0);
5✔
259

5✔
260
        return entries;
5✔
261
    }
5✔
262

13✔
263
    return {
13✔
264
        treeOidFromRef,
13✔
265
        getPathEntry,
13✔
266
        getPathsEntries,
13✔
267

13✔
268
        async listFiles<T extends boolean = false>(ref = 'HEAD', filesWithHash?: T) {
4✔
269
            const treeOid = await treeOidFromRef(ref);
4✔
270
            const filenames: FilenameEntry<T>[] = [];
4✔
271

4✔
272
            await collectTreeFiles(
4✔
273
                Buffer.from(treeOid, 'hex'),
4✔
274
                readTree,
4✔
275
                '',
4✔
276
                filenames,
4✔
277
                filesWithHash
4✔
278
            );
4✔
279

4✔
280
            return filenames;
4✔
281
        },
4✔
282

13✔
283
        async deltaFiles(nextRef = 'HEAD', prevRef?: string) {
6✔
284
            if (!prevRef) {
4✔
285
                const prevOid = await resolveRef(nextRef);
4✔
286
                const prev = await readObjectByOid(prevOid);
4✔
287

4✔
288
                prevRef =
4✔
289
                    prev.type === 'commit' ? parseCommit(prev.object).parent[0] : EMPTY_TREE_OID;
1✔
290
            }
4✔
291

6✔
292
            const prevTreeOid = await treeOidFromRef(prevRef);
6✔
293
            const nextTreeOid = await treeOidFromRef(nextRef);
6✔
294
            const delta: FileDelta = {
6✔
295
                add: [],
6✔
296
                modify: [],
6✔
297
                remove: []
6✔
298
            };
6✔
299

6✔
300
            if (prevTreeOid !== nextTreeOid) {
6✔
301
                await collectFilesDelta(
6✔
302
                    Buffer.from(prevTreeOid, 'hex'),
6✔
303
                    Buffer.from(nextTreeOid, 'hex'),
6✔
304
                    readObjectByHash,
6✔
305
                    readTree,
6✔
306
                    '',
6✔
307
                    delta
6✔
308
                );
6✔
309
            }
6✔
310

6✔
311
            return delta;
6✔
312
        }
6✔
313
    };
13✔
314
}
13✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc