Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 325e059

Browse files
Fix different behavior in status check pattern matching with double stars (#35474)
Drop the minimatch dependency, use our own glob compiler. Fix #35473
1 parent 866c636 commit 325e059

File tree

6 files changed

+325
-6
lines changed

6 files changed

+325
-6
lines changed

‎package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
"jquery": "3.7.1",
3838
"katex": "0.16.22",
3939
"mermaid": "11.11.0",
40-
"minimatch": "10.0.3",
4140
"monaco-editor": "0.53.0",
4241
"monaco-editor-webpack-plugin": "7.1.0",
4342
"online-3d-viewer": "0.16.0",

‎pnpm-lock.yaml

Lines changed: 23 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎web_src/js/features/repo-settings.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import {minimatch} from 'minimatch';
21
import {createMonaco} from './codeeditor.ts';
32
import {onInputDebounce, queryElems, toggleElem} from '../utils/dom.ts';
43
import {POST} from '../modules/fetch.ts';
54
import {initRepoSettingsBranchesDrag} from './repo-settings-branches.ts';
65
import {fomanticQuery} from '../modules/fomantic/base.ts';
6+
import {globMatch} from '../utils/glob.ts';
77

88
const {appSubUrl, csrfToken} = window.config;
99

@@ -108,7 +108,7 @@ function initRepoSettingsBranches() {
108108
let matched = false;
109109
const statusCheck = el.getAttribute('data-status-check');
110110
for (const pattern of validPatterns) {
111-
if (minimatch(statusCheck, pattern, {noext: true})) {// https://github.com/go-gitea/gitea/issues/33121 disable extended glob syntax
111+
if (globMatch(statusCheck, pattern, '/')) {
112112
matched = true;
113113
break;
114114
}

‎web_src/js/utils/glob.test.ts

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import {readFile} from 'node:fs/promises';
2+
import * as path from 'node:path';
3+
import {globCompile} from './glob.ts';
4+
5+
async function loadGlobTestData(): Promise<{caseNames: string[], caseDataMap: Record<string, string>}> {
6+
const fileContent = await readFile(path.join(import.meta.dirname, 'glob.test.txt'), 'utf8');
7+
const fileLines = fileContent.split('\n');
8+
const caseDataMap: Record<string, string> = {};
9+
const caseNameMap: Record<string, boolean> = {};
10+
for (let line of fileLines) {
11+
line = line.trim();
12+
if (!line || line.startsWith('#')) continue;
13+
const parts = line.split('=', 2);
14+
if (parts.length !== 2) throw new Error(`Invalid test case line: ${line}`);
15+
16+
const key = parts[0].trim();
17+
let value = parts[1].trim();
18+
value = value.substring(1, value.length - 1); // remove quotes
19+
value = value.replace(/\\\\/g, '\\').replaceAll(/\\\//g, '/');
20+
caseDataMap[key] = value;
21+
if (key.startsWith('pattern_')) caseNameMap[key.substring('pattern_'.length)] = true;
22+
}
23+
return {caseNames: Object.keys(caseNameMap), caseDataMap};
24+
}
25+
26+
function loadGlobGolangCases() {
27+
// https://github.com/gobwas/glob/blob/master/glob_test.go
28+
function glob(matched: boolean, pattern: string, input: string, separators: string = '') {
29+
return {matched, pattern, input, separators};
30+
}
31+
return [
32+
glob(true, '* ?at * eyes', 'my cat has very bright eyes'),
33+
34+
glob(true, '', ''),
35+
glob(false, '', 'b'),
36+
37+
glob(true, '*ä', 'åä'),
38+
glob(true, 'abc', 'abc'),
39+
glob(true, 'a*c', 'abc'),
40+
glob(true, 'a*c', 'a12345c'),
41+
glob(true, 'a?c', 'a1c'),
42+
glob(true, 'a.b', 'a.b', '.'),
43+
glob(true, 'a.*', 'a.b', '.'),
44+
glob(true, 'a.**', 'a.b.c', '.'),
45+
glob(true, 'a.?.c', 'a.b.c', '.'),
46+
glob(true, 'a.?.?', 'a.b.c', '.'),
47+
glob(true, '?at', 'cat'),
48+
glob(true, '?at', 'fat'),
49+
glob(true, '*', 'abc'),
50+
glob(true, `\\*`, '*'),
51+
glob(true, '**', 'a.b.c', '.'),
52+
53+
glob(false, '?at', 'at'),
54+
glob(false, '?at', 'fat', 'f'),
55+
glob(false, 'a.*', 'a.b.c', '.'),
56+
glob(false, 'a.?.c', 'a.bb.c', '.'),
57+
glob(false, '*', 'a.b.c', '.'),
58+
59+
glob(true, '*test', 'this is a test'),
60+
glob(true, 'this*', 'this is a test'),
61+
glob(true, '*is *', 'this is a test'),
62+
glob(true, '*is*a*', 'this is a test'),
63+
glob(true, '**test**', 'this is a test'),
64+
glob(true, '**is**a***test*', 'this is a test'),
65+
66+
glob(false, '*is', 'this is a test'),
67+
glob(false, '*no*', 'this is a test'),
68+
glob(true, '[!a]*', 'this is a test3'),
69+
70+
glob(true, '*abc', 'abcabc'),
71+
glob(true, '**abc', 'abcabc'),
72+
glob(true, '???', 'abc'),
73+
glob(true, '?*?', 'abc'),
74+
glob(true, '?*?', 'ac'),
75+
glob(false, 'sta', 'stagnation'),
76+
glob(true, 'sta*', 'stagnation'),
77+
glob(false, 'sta?', 'stagnation'),
78+
glob(false, 'sta?n', 'stagnation'),
79+
80+
glob(true, '{abc,def}ghi', 'defghi'),
81+
glob(true, '{abc,abcd}a', 'abcda'),
82+
glob(true, '{a,ab}{bc,f}', 'abc'),
83+
glob(true, '{*,**}{a,b}', 'ab'),
84+
glob(false, '{*,**}{a,b}', 'ac'),
85+
86+
glob(true, '/{rate,[a-z][a-z][a-z]}*', '/rate'),
87+
glob(true, '/{rate,[0-9][0-9][0-9]}*', '/rate'),
88+
glob(true, '/{rate,[a-z][a-z][a-z]}*', '/usd'),
89+
90+
glob(true, '{*.google.*,*.yandex.*}', 'www.google.com', '.'),
91+
glob(true, '{*.google.*,*.yandex.*}', 'www.yandex.com', '.'),
92+
glob(false, '{*.google.*,*.yandex.*}', 'yandex.com', '.'),
93+
glob(false, '{*.google.*,*.yandex.*}', 'google.com', '.'),
94+
95+
glob(true, '{*.google.*,yandex.*}', 'www.google.com', '.'),
96+
glob(true, '{*.google.*,yandex.*}', 'yandex.com', '.'),
97+
glob(false, '{*.google.*,yandex.*}', 'www.yandex.com', '.'),
98+
glob(false, '{*.google.*,yandex.*}', 'google.com', '.'),
99+
100+
glob(true, '*//{,*.}example.com', 'https://www.example.com'),
101+
glob(true, '*//{,*.}example.com', 'http://example.com'),
102+
glob(false, '*//{,*.}example.com', 'http://example.com.net'),
103+
];
104+
}
105+
106+
test('GlobCompiler', async () => {
107+
const {caseNames, caseDataMap} = await loadGlobTestData();
108+
expect(caseNames.length).toBe(10); // should have 10 test cases
109+
for (const caseName of caseNames) {
110+
const pattern = caseDataMap[`pattern_${caseName}`];
111+
const regexp = caseDataMap[`regexp_${caseName}`];
112+
expect(globCompile(pattern).regexpPattern).toBe(regexp);
113+
}
114+
115+
const golangCases = loadGlobGolangCases();
116+
expect(golangCases.length).toBe(60);
117+
for (const c of golangCases) {
118+
const compiled = globCompile(c.pattern, c.separators);
119+
const msg = `pattern: ${c.pattern}, input: ${c.input}, separators: ${c.separators || '(none)'}, compiled: ${compiled.regexpPattern}`;
120+
// eslint-disable-next-line @vitest/valid-expect -- Unlike Jest, Vitest supports a message as the second argument
121+
expect(compiled.regexp.test(c.input), msg).toBe(c.matched);
122+
}
123+
124+
// then our cases
125+
expect(globCompile('*/**/x').regexpPattern).toBe('^.*/.*/x$');
126+
expect(globCompile('*/**/x', '/').regexpPattern).toBe('^[^/]*/.*/x$');
127+
expect(globCompile('[a-b][^-\\]]', '/').regexpPattern).toBe('^[a-b][^-\\]]$');
128+
expect(globCompile('.+^$()|', '/').regexpPattern).toBe('^\\.\\+\\^\\$\\(\\)\\|$');
129+
});

‎web_src/js/utils/glob.test.txt

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# test cases are from https://github.com/gobwas/glob/blob/master/glob_test.go
2+
3+
pattern_all = "[a-z][!a-x]*cat*[h][!b]*eyes*"
4+
regexp_all = `^[a-z][^a-x].*cat.*[h][^b].*eyes.*$`
5+
fixture_all_match = "my cat has very bright eyes"
6+
fixture_all_mismatch = "my dog has very bright eyes"
7+
8+
pattern_plain = "google.com"
9+
regexp_plain = `^google\.com$`
10+
fixture_plain_match = "google.com"
11+
fixture_plain_mismatch = "gobwas.com"
12+
13+
pattern_multiple = "https://*.google.*"
14+
regexp_multiple = `^https:\/\/.*\.google\..*$`
15+
fixture_multiple_match = "https://account.google.com"
16+
fixture_multiple_mismatch = "https://google.com"
17+
18+
pattern_alternatives = "{https://*.google.*,*yandex.*,*yahoo.*,*mail.ru}"
19+
regexp_alternatives = `^(https:\/\/.*\.google\..*|.*yandex\..*|.*yahoo\..*|.*mail\.ru)$`
20+
fixture_alternatives_match = "http://yahoo.com"
21+
fixture_alternatives_mismatch = "http://google.com"
22+
23+
pattern_alternatives_suffix = "{https://*gobwas.com,http://exclude.gobwas.com}"
24+
regexp_alternatives_suffix = `^(https:\/\/.*gobwas\.com|http://exclude\.gobwas\.com)$`
25+
fixture_alternatives_suffix_first_match = "https://safe.gobwas.com"
26+
fixture_alternatives_suffix_first_mismatch = "http://safe.gobwas.com"
27+
fixture_alternatives_suffix_second = "http://exclude.gobwas.com"
28+
29+
pattern_prefix = "abc*"
30+
regexp_prefix = `^abc.*$`
31+
pattern_suffix = "*def"
32+
regexp_suffix = `^.*def$`
33+
pattern_prefix_suffix = "ab*ef"
34+
regexp_prefix_suffix = `^ab.*ef$`
35+
fixture_prefix_suffix_match = "abcdef"
36+
fixture_prefix_suffix_mismatch = "af"
37+
38+
pattern_alternatives_combine_lite = "{abc*def,abc?def,abc[zte]def}"
39+
regexp_alternatives_combine_lite = `^(abc.*def|abc.def|abc[zte]def)$`
40+
fixture_alternatives_combine_lite = "abczdef"
41+
42+
pattern_alternatives_combine_hard = "{abc*[a-c]def,abc?[d-g]def,abc[zte]?def}"
43+
regexp_alternatives_combine_hard = `^(abc.*[a-c]def|abc.[d-g]def|abc[zte].def)$`
44+
fixture_alternatives_combine_hard = "abczqdef"

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /