-
Notifications
You must be signed in to change notification settings - Fork 1
/
git-changed-files.js
107 lines (101 loc) · 3.74 KB
/
git-changed-files.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
// @flow
const execProm = require('./exec-prom');
const path = require('path');
const fs = require('fs');
const minimatch = require('minimatch'); // eslint-disable-line flowtype-errors/uncovered
// ok
const getIgnoredPatterns = (fileContents /*: string*/) => {
return fileContents
.split('\n')
.map(line => {
if (line.startsWith('#')) {
return null;
}
if (line.startsWith('"')) {
throw new Error('Quoted patterns not yet supported, sorry');
}
if (!line.trim()) {
return null;
}
const [pattern, ...attributes] = line.trim().split(' ');
if (attributes.includes('binary') || attributes.includes('linguist-generated=true')) {
return pattern;
}
return null;
})
.filter(Boolean);
};
const ignoredPatternsByDirectory /*: {[key: string]: Array<string>}*/ = {};
const isFileIgnored = (workingDirectory /*: string*/, file /*: string*/) => {
// If it's outside of the "working directory", we ignore it
if (!file.startsWith(workingDirectory)) {
return true;
}
let dir = path.dirname(file);
let name = path.basename(file);
while (dir.startsWith(workingDirectory)) {
if (!ignoredPatternsByDirectory[dir]) {
const attributes = path.join(dir, '.gitattributes');
if (fs.existsSync(attributes)) {
ignoredPatternsByDirectory[dir] = getIgnoredPatterns(
fs.readFileSync(attributes, 'utf8'),
);
} else {
ignoredPatternsByDirectory[dir] = [];
}
}
for (const pattern of ignoredPatternsByDirectory[dir]) {
// eslint-disable-next-line flowtype-errors/uncovered
if (minimatch(name, pattern)) {
return true;
}
}
name = path.join(path.basename(dir), name);
dir = path.dirname(dir);
}
return false;
};
/**
* This lists the files that have changed when compared to `base` (a git ref),
* limited to the files that are a descendent of `cwd`.
* It also respects '.gitattributes', filtering out files that have been marked
* as "binary" or "linguist-generated=true".
*/
const gitChangedFiles = async (base /*:string*/, cwd /*:string*/) /*: Promise<Array<string>>*/ => {
cwd = path.resolve(cwd);
// Github actions jobs can run the following steps to get a fully accurate
// changed files list. Otherwise, we fallback to a simple diff between the
// current and base branch, which might give false positives if the base
// is ahead of the current branch.
//
// - name: Get All Changed Files
// uses: jaredly/get-changed-files@absolute
// id: changed
// with:
// format: 'json'
// absolute: true
//
// - uses: allenevans/set-env@v2.0.0
// with:
// ALL_CHANGED_FILES: '${{ steps.changed.outputs.added_modified }}'
//
if (process.env.ALL_CHANGED_FILES) {
const files /*: Array<string> */ = JSON.parse(process.env.ALL_CHANGED_FILES); // eslint-disable-line flowtype-errors/uncovered
return files.filter(path => !isFileIgnored(cwd, path));
}
const {stdout} = await execProm(`git diff --name-only ${base} --relative`, {
cwd,
encoding: 'utf8',
rejectOnError: true,
});
return (
stdout
.split('\n')
.filter(Boolean)
.map(name => path.join(cwd, name))
// Filter out paths that were deleted
.filter((path /*: string*/) => fs.existsSync(path))
.filter((path /*: string*/) => !isFileIgnored(cwd, path))
);
};
module.exports = gitChangedFiles;