Visitar URL original
handle long unc forms · isaacs/node-glob@8b16637 · GitHub
Skip to content

Commit 8b16637

Browse files
committed
handle long unc forms
1 parent 5bcdc65 commit 8b16637

4 files changed

Lines changed: 98 additions & 9 deletions

File tree

‎changelog.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
Windows.
2525
- `ignore` patterns follow the `windowsPathsNoEscape` option,
2626
the same as other patterns.
27+
- On Windows, patterns that start with a long UNC root like
28+
`//?/UNC/host/share/` walk that share, the same as
29+
`//host/share/`.
2730
- Faster `ignore` patterns (about 1.2 to 3 times faster), and
2831
less work for each entry in a walk (about 10% faster sync, and
2932
15% to 30% faster async, for large trees).

‎src/pattern.ts‎

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,19 +80,24 @@ export class Pattern {
8080
// //host/share => ['//host/share/']
8181
// //host/share/ => ['//host/share/']
8282
// //host/share/x => ['//host/share/', 'x']
83+
// //?/UNC/host/share/x => ['//?/UNC/host/share/', 'x']
8384
// /etc => ['/', 'etc']
8485
// / => ['/']
8586
if (this.isUNC()) {
86-
// '' / '' / 'host' / 'share'
87-
const [p0, p1, p2, p3, ...prest] = this.#patternList
88-
const [g0, g1, g2, g3, ...grest] = this.#globList
87+
// '' / '' / 'host' / 'share', or
88+
// '' / '' / '?' / 'UNC' / 'host' / 'share'
89+
const n = this.#isLongUNC() ? 6 : 4
90+
const proot = this.#patternList.slice(0, n)
91+
const groot = this.#globList.slice(0, n)
92+
const prest = this.#patternList.slice(n)
93+
const grest = this.#globList.slice(n)
8994
if (prest[0] === '') {
9095
// ends in /
9196
prest.shift()
9297
grest.shift()
9398
}
94-
const p = [p0, p1, p2, p3, ''].join('/')
95-
const g = [g0, g1, g2, g3, ''].join('/')
99+
const p = [...proot, ''].join('/')
100+
const g = [...groot, ''].join('/')
96101
this.#patternList = [p, ...prest]
97102
this.#globList = [g, ...grest]
98103
this.length = this.#patternList.length
@@ -181,22 +186,32 @@ export class Pattern {
181186
return this.#rest
182187
}
183188

189+
// the root is //?/UNC/host/share, the same as //host/share
190+
#isLongUNC(): boolean {
191+
const pl = this.#patternList
192+
return (
193+
pl[2] === '?' && typeof pl[3] === 'string' && /^unc$/i.test(pl[3])
194+
)
195+
}
196+
184197
/**
185198
* true if the pattern represents a //unc/path/ on windows
186199
*/
187200
isUNC(): boolean {
188201
const pl = this.#patternList
202+
// the host and share, after //?/UNC/ in a long UNC path
203+
const h = this.#isLongUNC() ? 4 : 2
189204
return this.#isUNC !== undefined ?
190205
this.#isUNC
191206
: (this.#isUNC =
192207
this.#platform === 'win32' &&
193208
this.#index === 0 &&
194209
pl[0] === '' &&
195210
pl[1] === '' &&
196-
typeof pl[2] === 'string' &&
197-
!!pl[2] &&
198-
typeof pl[3] === 'string' &&
199-
!!pl[3])
211+
typeof pl[h] === 'string' &&
212+
!!pl[h] &&
213+
typeof pl[h + 1] === 'string' &&
214+
!!pl[h + 1])
200215
}
201216

202217
// pattern like C:/...

‎test/long-unc.ts‎

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// //?/UNC/host/share/x is the same path as //host/share/x on Windows
2+
3+
import * as fs from 'fs'
4+
import { PathScurryWin32 } from 'path-scurry'
5+
import t from 'tap'
6+
import { glob, globSync } from '../dist/esm/index.js'
7+
8+
const dir = t.testdir({
9+
a: { x: '', y: '' },
10+
b: { x: '' },
11+
})
12+
13+
// serve \\HOST\SHARE\ from the test dir
14+
const real = (p: string) =>
15+
p.replace(/^\\\\HOST\\SHARE\\?/i, `${dir}/`).replace(/\\/g, '/')
16+
const winfs = {
17+
lstatSync: (p: string) => fs.lstatSync(real(p)),
18+
readdir: (
19+
p: string,
20+
o: { withFileTypes: true },
21+
cb: (er: NodeJS.ErrnoException | null, e?: fs.Dirent[]) => any,
22+
) => fs.readdir(real(p), o, cb),
23+
readdirSync: (p: string, o: { withFileTypes: true }) =>
24+
fs.readdirSync(real(p), o),
25+
readlinkSync: (p: string) => fs.readlinkSync(real(p)),
26+
realpathSync: (p: string) => fs.realpathSync(real(p)),
27+
promises: {
28+
lstat: (p: string) => fs.promises.lstat(real(p)),
29+
readdir: (p: string, o: { withFileTypes: true }) =>
30+
fs.promises.readdir(real(p), o),
31+
readlink: (p: string) => fs.promises.readlink(real(p)),
32+
realpath: (p: string) => fs.promises.realpath(real(p)),
33+
},
34+
}
35+
36+
// the share is a new root, which uses the same fs as the cwd
37+
const scurry = () => new PathScurryWin32('C:\\', { fs: winfs })
38+
39+
const expect = ['//HOST/SHARE/a/x', '//HOST/SHARE/b/x']
40+
for (const pattern of [
41+
'//host/share/*/x',
42+
'//?/UNC/host/share/*/x',
43+
'//?/unc/HOST/share/*/x',
44+
'//?/UNC/host/share/a/../*/x',
45+
]) {
46+
t.test(pattern, async t => {
47+
const opts = {
48+
platform: 'win32',
49+
posix: true,
50+
scurry: scurry(),
51+
} as const
52+
t.strictSame(globSync(pattern, opts).sort(), expect, 'sync')
53+
t.strictSame((await glob(pattern, opts)).sort(), expect, 'async')
54+
})
55+
}
56+
57+
t.test('ignore', async t => {
58+
const opts = {
59+
platform: 'win32',
60+
posix: true,
61+
scurry: scurry(),
62+
ignore: ['//?/UNC/host/share/a/**'],
63+
} as const
64+
t.strictSame(globSync('//host/share/*/x', opts), ['//HOST/SHARE/b/x'])
65+
})

‎test/pattern.ts‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ t.same(
1616
'//?/z:',
1717
'c:/x/*',
1818
'c:/',
19+
'//?/UNC/host/share/x/*',
20+
'//?/unc/host/share/',
21+
'//?/UNC/host/share',
1922
],
2023
{ platform: 'win32' },
2124
).patterns.map(p => [p.globString(), p.root()]),
@@ -28,6 +31,9 @@ t.same(
2831
['//?/z:/', '//?/z:/'],
2932
['c:/x/*', 'c:/'],
3033
['c:/', 'c:/'],
34+
['//?/UNC/host/share/x/*', '//?/UNC/host/share/'],
35+
['//?/unc/host/share/', '//?/UNC/host/share/'],
36+
['//?/UNC/host/share/', '//?/UNC/host/share/'],
3137
],
3238
)
3339
t.throws(() => {

0 commit comments

Comments
 (0)