isomorphic-git 1.38.4 → 1.38.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.cjs +144 -22
- package/index.js +144 -22
- package/index.umd.min.js +1 -1
- package/index.umd.min.js.map +1 -1
- package/managers/index.cjs +112 -20
- package/managers/index.d.cts +1 -1
- package/managers/index.d.ts +1 -1
- package/managers/index.js +112 -20
- package/managers/index.umd.min.js +1 -1
- package/managers/index.umd.min.js.LICENSE.txt +12 -0
- package/managers/index.umd.min.js.map +1 -1
- package/models/index.cjs +60 -10
- package/models/index.js +60 -10
- package/models/index.umd.min.js +1 -1
- package/models/index.umd.min.js.LICENSE.txt +12 -0
- package/models/index.umd.min.js.map +1 -1
- package/package.json +2 -2
package/index.cjs
CHANGED
|
@@ -1511,6 +1511,18 @@ function compareRefNames(a, b) {
|
|
|
1511
1511
|
* This code for `path.join` is directly copied from @zenfs/core/path for bundle size improvements.
|
|
1512
1512
|
* SPDX-License-Identifier: LGPL-3.0-or-later
|
|
1513
1513
|
* Copyright (c) James Prevett and other ZenFS contributors.
|
|
1514
|
+
*
|
|
1515
|
+
* Windows support added:
|
|
1516
|
+
* - Backslashes are normalised to forward slashes before processing.
|
|
1517
|
+
* - Drive-letter prefixes (e.g. "C:") are detected and preserved through
|
|
1518
|
+
* normalisation, so absolute Windows paths are handled correctly.
|
|
1519
|
+
* - An absolute argument passed to join() resets the accumulated path,
|
|
1520
|
+
* matching Node behaviour and handling worktree gitdir paths properly.
|
|
1521
|
+
*
|
|
1522
|
+
* Limitation: UNC paths (e.g. \\server\share) are not supported. The leading
|
|
1523
|
+
* backslashes are normalised to forward slashes and then collapsed by
|
|
1524
|
+
* normalizeString, losing the UNC root. Git on Windows works with
|
|
1525
|
+
* drive-letter paths, so this is not expected to be a practical issue.
|
|
1514
1526
|
*/
|
|
1515
1527
|
|
|
1516
1528
|
function normalizeString(path, aar) {
|
|
@@ -1574,29 +1586,67 @@ function normalizeString(path, aar) {
|
|
|
1574
1586
|
return res
|
|
1575
1587
|
}
|
|
1576
1588
|
|
|
1589
|
+
// Returns the Windows drive prefix ("C:") if present, otherwise null.
|
|
1590
|
+
function getWindowsDrivePrefix(path) {
|
|
1591
|
+
if (path.length >= 2 && /^[a-zA-Z]:/.test(path)) {
|
|
1592
|
+
return path.slice(0, 2) // e.g. "C:"
|
|
1593
|
+
}
|
|
1594
|
+
return null
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1577
1597
|
function normalize(path) {
|
|
1578
1598
|
if (!path.length) return '.'
|
|
1579
1599
|
|
|
1580
|
-
|
|
1600
|
+
// Normalise backslashes to forward slashes before any other processing.
|
|
1601
|
+
path = path.replace(/\\/g, '/');
|
|
1602
|
+
|
|
1603
|
+
const drivePrefix = getWindowsDrivePrefix(path);
|
|
1604
|
+
// isAbsolute: Unix root ('/foo') OR Windows drive+slash ('C:/foo').
|
|
1605
|
+
const isAbsolute =
|
|
1606
|
+
path[0] === '/' || (drivePrefix !== null && path[2] === '/');
|
|
1581
1607
|
const trailingSeparator = path.at(-1) === '/';
|
|
1582
1608
|
|
|
1583
|
-
|
|
1609
|
+
// Strip the drive prefix before feeding into normalizeString so that the
|
|
1610
|
+
// core algorithm only ever sees a plain POSIX-style string.
|
|
1611
|
+
const pathBody = drivePrefix ? path.slice(2) : path;
|
|
1612
|
+
|
|
1613
|
+
let normalized = normalizeString(pathBody, !isAbsolute);
|
|
1584
1614
|
|
|
1585
|
-
if (!
|
|
1586
|
-
|
|
1587
|
-
|
|
1615
|
+
if (!normalized.length) {
|
|
1616
|
+
const root = drivePrefix
|
|
1617
|
+
? isAbsolute
|
|
1618
|
+
? drivePrefix + '/'
|
|
1619
|
+
: drivePrefix
|
|
1620
|
+
: isAbsolute
|
|
1621
|
+
? '/'
|
|
1622
|
+
: '.';
|
|
1623
|
+
return trailingSeparator && !isAbsolute ? root + '/' : root
|
|
1588
1624
|
}
|
|
1589
|
-
if (trailingSeparator)
|
|
1625
|
+
if (trailingSeparator) normalized += '/';
|
|
1590
1626
|
|
|
1591
|
-
|
|
1627
|
+
if (drivePrefix) {
|
|
1628
|
+
return isAbsolute
|
|
1629
|
+
? `${drivePrefix}/${normalized}`
|
|
1630
|
+
: `${drivePrefix}${normalized}`
|
|
1631
|
+
}
|
|
1632
|
+
return isAbsolute ? `/${normalized}` : normalized
|
|
1592
1633
|
}
|
|
1593
1634
|
|
|
1594
1635
|
function join(...args) {
|
|
1595
1636
|
if (args.length === 0) return '.'
|
|
1596
1637
|
let joined;
|
|
1597
1638
|
for (let i = 0; i < args.length; ++i) {
|
|
1598
|
-
|
|
1599
|
-
|
|
1639
|
+
// Normalise separators before processing.
|
|
1640
|
+
const arg = args[i].replace(/\\/g, '/');
|
|
1641
|
+
if (arg.length === 0) continue
|
|
1642
|
+
|
|
1643
|
+
// A Windows drive-letter path (e.g. "C:/worktrees/foo") cannot be
|
|
1644
|
+
// meaningfully appended to any base, so it resets the accumulator.
|
|
1645
|
+
// Unix absolute paths (leading '/') are NOT reset here — that would be
|
|
1646
|
+
// path.resolve() semantics; path.join('foo', '/bar') must yield 'foo/bar'.
|
|
1647
|
+
if (/^[a-zA-Z]:\//.test(arg)) {
|
|
1648
|
+
joined = arg;
|
|
1649
|
+
} else {
|
|
1600
1650
|
if (joined === undefined) joined = arg;
|
|
1601
1651
|
else joined += '/' + arg;
|
|
1602
1652
|
}
|
|
@@ -2181,7 +2231,13 @@ class GitRefManager {
|
|
|
2181
2231
|
* @param {number} [args.depth = undefined] - The maximum depth to resolve symbolic refs.
|
|
2182
2232
|
* @returns {Promise<string>} - The resolved object ID.
|
|
2183
2233
|
*/
|
|
2184
|
-
static async resolve({
|
|
2234
|
+
static async resolve({
|
|
2235
|
+
fs,
|
|
2236
|
+
gitdir,
|
|
2237
|
+
ref,
|
|
2238
|
+
depth = undefined,
|
|
2239
|
+
visited = new Set(),
|
|
2240
|
+
}) {
|
|
2185
2241
|
if (depth !== undefined) {
|
|
2186
2242
|
depth--;
|
|
2187
2243
|
if (depth === -1) {
|
|
@@ -2192,7 +2248,7 @@ class GitRefManager {
|
|
|
2192
2248
|
// Is it a ref pointer?
|
|
2193
2249
|
if (ref.startsWith('ref: ')) {
|
|
2194
2250
|
ref = ref.slice('ref: '.length);
|
|
2195
|
-
return GitRefManager.resolve({ fs, gitdir, ref, depth })
|
|
2251
|
+
return GitRefManager.resolve({ fs, gitdir, ref, depth, visited })
|
|
2196
2252
|
}
|
|
2197
2253
|
// Is it a complete and valid SHA?
|
|
2198
2254
|
if (ref.length === 40 && /[0-9a-f]{40}/.test(ref)) {
|
|
@@ -2211,7 +2267,21 @@ class GitRefManager {
|
|
|
2211
2267
|
packedMap.get(ref)
|
|
2212
2268
|
);
|
|
2213
2269
|
if (sha) {
|
|
2214
|
-
|
|
2270
|
+
// Guard against circular symbolic references (e.g. a -> b -> a). Without
|
|
2271
|
+
// tracking visited refs this recursion would not terminate.
|
|
2272
|
+
if (visited.has(ref)) {
|
|
2273
|
+
throw new InternalError(
|
|
2274
|
+
`Circular reference detected while resolving ref "${ref}"`
|
|
2275
|
+
)
|
|
2276
|
+
}
|
|
2277
|
+
visited.add(ref);
|
|
2278
|
+
return GitRefManager.resolve({
|
|
2279
|
+
fs,
|
|
2280
|
+
gitdir,
|
|
2281
|
+
ref: sha.trim(),
|
|
2282
|
+
depth,
|
|
2283
|
+
visited,
|
|
2284
|
+
})
|
|
2215
2285
|
}
|
|
2216
2286
|
}
|
|
2217
2287
|
// Do we give up?
|
|
@@ -2467,8 +2537,25 @@ function parseBuffer(buffer) {
|
|
|
2467
2537
|
const type = mode2type$1(mode);
|
|
2468
2538
|
const path = buffer.slice(space + 1, nullchar).toString('utf8');
|
|
2469
2539
|
|
|
2470
|
-
//
|
|
2471
|
-
|
|
2540
|
+
// Reject reserved entry names, matching git's verify_path(): path separators,
|
|
2541
|
+
// "." and ".." (which resolve outside the working directory), and ".git" (which git
|
|
2542
|
+
// disallows as a path component). Checks mirror git's is_ntfs_dotgit() and
|
|
2543
|
+
// is_hfs_dotgit(): case-insensitive, trailing dots/spaces stripped (NTFS),
|
|
2544
|
+
// NTFS 8.3 short name aliases (git~1..git~9), and HFS+ ignorable Unicode
|
|
2545
|
+
// characters stripped (zero-width joiners, directional marks, BOM, etc.).
|
|
2546
|
+
const hfsClean = path.replace(
|
|
2547
|
+
/[\u200C-\u200F\u202A-\u202E\u206A-\u206F\uFEFF]/g,
|
|
2548
|
+
''
|
|
2549
|
+
);
|
|
2550
|
+
const normalized = hfsClean.toLowerCase().replace(/[. ]+$/, '');
|
|
2551
|
+
if (
|
|
2552
|
+
path.includes('\\') ||
|
|
2553
|
+
path.includes('/') ||
|
|
2554
|
+
hfsClean === '.' ||
|
|
2555
|
+
hfsClean === '..' ||
|
|
2556
|
+
normalized === '.git' ||
|
|
2557
|
+
/^\.?git~[1-9]$/.test(normalized)
|
|
2558
|
+
) {
|
|
2472
2559
|
throw new UnsafeFilepathError(path)
|
|
2473
2560
|
}
|
|
2474
2561
|
|
|
@@ -2642,21 +2729,26 @@ function applyDelta(delta, source) {
|
|
|
2642
2729
|
if (firstOp.byteLength === targetSize) {
|
|
2643
2730
|
target = firstOp;
|
|
2644
2731
|
} else {
|
|
2645
|
-
//
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2732
|
+
// Build the result from the delta ops instead of pre-allocating `targetSize`.
|
|
2733
|
+
// `targetSize` comes from the delta header and may not match the actual op output,
|
|
2734
|
+
// so allocating it up front can reserve far more memory than the ops produce.
|
|
2735
|
+
// Building from the ops bounds memory to the real output size; the size check below
|
|
2736
|
+
// still validates the total before concatenating.
|
|
2737
|
+
const chunks = [firstOp];
|
|
2738
|
+
let tell = firstOp.byteLength;
|
|
2649
2739
|
|
|
2650
2740
|
while (!reader.eof()) {
|
|
2651
|
-
|
|
2741
|
+
const op = readOp(reader, source);
|
|
2742
|
+
chunks.push(op);
|
|
2743
|
+
tell += op.byteLength;
|
|
2652
2744
|
}
|
|
2653
2745
|
|
|
2654
|
-
const tell = writer.tell();
|
|
2655
2746
|
if (targetSize !== tell) {
|
|
2656
2747
|
throw new InternalError(
|
|
2657
2748
|
`applyDelta expected target buffer to be ${targetSize} bytes but the resulting buffer was ${tell} bytes`
|
|
2658
2749
|
)
|
|
2659
2750
|
}
|
|
2751
|
+
target = Buffer.concat(chunks, targetSize);
|
|
2660
2752
|
}
|
|
2661
2753
|
return target
|
|
2662
2754
|
}
|
|
@@ -6805,6 +6897,30 @@ const worthWalking = (filepath, root) => {
|
|
|
6805
6897
|
|
|
6806
6898
|
// @ts-check
|
|
6807
6899
|
|
|
6900
|
+
/**
|
|
6901
|
+
* Throw if any leading directory component of `fullpath` (relative to `dir`) is a symbolic
|
|
6902
|
+
* link. git does not follow symlinks in the leading path when writing working-tree files;
|
|
6903
|
+
* matching that avoids writing through a symlinked parent into a location outside `dir`.
|
|
6904
|
+
*
|
|
6905
|
+
* @param {import('../models/FileSystem.js').FileSystem} fs
|
|
6906
|
+
* @param {string} dir
|
|
6907
|
+
* @param {string} fullpath
|
|
6908
|
+
*/
|
|
6909
|
+
async function assertNoSymlinkInLeadingPath(fs, dir, fullpath) {
|
|
6910
|
+
const parts = fullpath.split('/');
|
|
6911
|
+
parts.pop(); // the final segment is the entry being written, not a leading dir
|
|
6912
|
+
let current = dir;
|
|
6913
|
+
for (const part of parts) {
|
|
6914
|
+
if (part === '' || part === '.') continue
|
|
6915
|
+
current = `${current}/${part}`;
|
|
6916
|
+
const stats = await fs.lstat(current);
|
|
6917
|
+
// lstat returns null when the path doesn't exist yet (nothing to traverse).
|
|
6918
|
+
if (stats && stats.isSymbolicLink()) {
|
|
6919
|
+
throw new UnsafeFilepathError(fullpath)
|
|
6920
|
+
}
|
|
6921
|
+
}
|
|
6922
|
+
}
|
|
6923
|
+
|
|
6808
6924
|
/**
|
|
6809
6925
|
* @param {object} args
|
|
6810
6926
|
* @param {import('../models/FileSystem.js').FileSystem} args.fs
|
|
@@ -7011,6 +7127,7 @@ async function _checkout({
|
|
|
7011
7127
|
.filter(([method]) => method === 'mkdir' || method === 'mkdir-index')
|
|
7012
7128
|
.map(async function ([_, fullpath]) {
|
|
7013
7129
|
const filepath = `${dir}/${fullpath}`;
|
|
7130
|
+
await assertNoSymlinkInLeadingPath(fs, dir, fullpath);
|
|
7014
7131
|
await fs.mkdir(filepath);
|
|
7015
7132
|
if (onProgress) {
|
|
7016
7133
|
await onProgress({
|
|
@@ -7080,6 +7197,10 @@ async function _checkout({
|
|
|
7080
7197
|
.map(async function ([method, fullpath, oid, mode, chmod]) {
|
|
7081
7198
|
const filepath = `${dir}/${fullpath}`;
|
|
7082
7199
|
if (method !== 'create-index' && method !== 'mkdir-index') {
|
|
7200
|
+
// Don't write through a symlinked leading path: a parent component that
|
|
7201
|
+
// is a symlink could otherwise redirect the write outside the working
|
|
7202
|
+
// tree. git applies the same check (it does not follow symlinks here).
|
|
7203
|
+
await assertNoSymlinkInLeadingPath(fs, dir, fullpath);
|
|
7083
7204
|
const { object } = await _readObject({
|
|
7084
7205
|
fs,
|
|
7085
7206
|
cache,
|
|
@@ -7486,6 +7607,7 @@ async function updateWorkingDir(
|
|
|
7486
7607
|
) {
|
|
7487
7608
|
const filepath = `${dir}/${fullpath}`;
|
|
7488
7609
|
if (method !== 'create-index' && method !== 'mkdir-index') {
|
|
7610
|
+
await assertNoSymlinkInLeadingPath(fs, dir, fullpath);
|
|
7489
7611
|
const { object } = await _readObject({ fs, cache, gitdir, oid });
|
|
7490
7612
|
if (chmod) {
|
|
7491
7613
|
await fs.rm(filepath);
|
|
@@ -9360,8 +9482,8 @@ function filterCapabilities(server, client) {
|
|
|
9360
9482
|
|
|
9361
9483
|
const pkg = {
|
|
9362
9484
|
name: 'isomorphic-git',
|
|
9363
|
-
version: '1.38.
|
|
9364
|
-
agent: 'git/isomorphic-git@1.38.
|
|
9485
|
+
version: '1.38.6',
|
|
9486
|
+
agent: 'git/isomorphic-git@1.38.6',
|
|
9365
9487
|
};
|
|
9366
9488
|
|
|
9367
9489
|
class FIFO {
|
package/index.js
CHANGED
|
@@ -1504,6 +1504,18 @@ function compareRefNames(a, b) {
|
|
|
1504
1504
|
* This code for `path.join` is directly copied from @zenfs/core/path for bundle size improvements.
|
|
1505
1505
|
* SPDX-License-Identifier: LGPL-3.0-or-later
|
|
1506
1506
|
* Copyright (c) James Prevett and other ZenFS contributors.
|
|
1507
|
+
*
|
|
1508
|
+
* Windows support added:
|
|
1509
|
+
* - Backslashes are normalised to forward slashes before processing.
|
|
1510
|
+
* - Drive-letter prefixes (e.g. "C:") are detected and preserved through
|
|
1511
|
+
* normalisation, so absolute Windows paths are handled correctly.
|
|
1512
|
+
* - An absolute argument passed to join() resets the accumulated path,
|
|
1513
|
+
* matching Node behaviour and handling worktree gitdir paths properly.
|
|
1514
|
+
*
|
|
1515
|
+
* Limitation: UNC paths (e.g. \\server\share) are not supported. The leading
|
|
1516
|
+
* backslashes are normalised to forward slashes and then collapsed by
|
|
1517
|
+
* normalizeString, losing the UNC root. Git on Windows works with
|
|
1518
|
+
* drive-letter paths, so this is not expected to be a practical issue.
|
|
1507
1519
|
*/
|
|
1508
1520
|
|
|
1509
1521
|
function normalizeString(path, aar) {
|
|
@@ -1567,29 +1579,67 @@ function normalizeString(path, aar) {
|
|
|
1567
1579
|
return res
|
|
1568
1580
|
}
|
|
1569
1581
|
|
|
1582
|
+
// Returns the Windows drive prefix ("C:") if present, otherwise null.
|
|
1583
|
+
function getWindowsDrivePrefix(path) {
|
|
1584
|
+
if (path.length >= 2 && /^[a-zA-Z]:/.test(path)) {
|
|
1585
|
+
return path.slice(0, 2) // e.g. "C:"
|
|
1586
|
+
}
|
|
1587
|
+
return null
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1570
1590
|
function normalize(path) {
|
|
1571
1591
|
if (!path.length) return '.'
|
|
1572
1592
|
|
|
1573
|
-
|
|
1593
|
+
// Normalise backslashes to forward slashes before any other processing.
|
|
1594
|
+
path = path.replace(/\\/g, '/');
|
|
1595
|
+
|
|
1596
|
+
const drivePrefix = getWindowsDrivePrefix(path);
|
|
1597
|
+
// isAbsolute: Unix root ('/foo') OR Windows drive+slash ('C:/foo').
|
|
1598
|
+
const isAbsolute =
|
|
1599
|
+
path[0] === '/' || (drivePrefix !== null && path[2] === '/');
|
|
1574
1600
|
const trailingSeparator = path.at(-1) === '/';
|
|
1575
1601
|
|
|
1576
|
-
|
|
1602
|
+
// Strip the drive prefix before feeding into normalizeString so that the
|
|
1603
|
+
// core algorithm only ever sees a plain POSIX-style string.
|
|
1604
|
+
const pathBody = drivePrefix ? path.slice(2) : path;
|
|
1605
|
+
|
|
1606
|
+
let normalized = normalizeString(pathBody, !isAbsolute);
|
|
1577
1607
|
|
|
1578
|
-
if (!
|
|
1579
|
-
|
|
1580
|
-
|
|
1608
|
+
if (!normalized.length) {
|
|
1609
|
+
const root = drivePrefix
|
|
1610
|
+
? isAbsolute
|
|
1611
|
+
? drivePrefix + '/'
|
|
1612
|
+
: drivePrefix
|
|
1613
|
+
: isAbsolute
|
|
1614
|
+
? '/'
|
|
1615
|
+
: '.';
|
|
1616
|
+
return trailingSeparator && !isAbsolute ? root + '/' : root
|
|
1581
1617
|
}
|
|
1582
|
-
if (trailingSeparator)
|
|
1618
|
+
if (trailingSeparator) normalized += '/';
|
|
1583
1619
|
|
|
1584
|
-
|
|
1620
|
+
if (drivePrefix) {
|
|
1621
|
+
return isAbsolute
|
|
1622
|
+
? `${drivePrefix}/${normalized}`
|
|
1623
|
+
: `${drivePrefix}${normalized}`
|
|
1624
|
+
}
|
|
1625
|
+
return isAbsolute ? `/${normalized}` : normalized
|
|
1585
1626
|
}
|
|
1586
1627
|
|
|
1587
1628
|
function join(...args) {
|
|
1588
1629
|
if (args.length === 0) return '.'
|
|
1589
1630
|
let joined;
|
|
1590
1631
|
for (let i = 0; i < args.length; ++i) {
|
|
1591
|
-
|
|
1592
|
-
|
|
1632
|
+
// Normalise separators before processing.
|
|
1633
|
+
const arg = args[i].replace(/\\/g, '/');
|
|
1634
|
+
if (arg.length === 0) continue
|
|
1635
|
+
|
|
1636
|
+
// A Windows drive-letter path (e.g. "C:/worktrees/foo") cannot be
|
|
1637
|
+
// meaningfully appended to any base, so it resets the accumulator.
|
|
1638
|
+
// Unix absolute paths (leading '/') are NOT reset here — that would be
|
|
1639
|
+
// path.resolve() semantics; path.join('foo', '/bar') must yield 'foo/bar'.
|
|
1640
|
+
if (/^[a-zA-Z]:\//.test(arg)) {
|
|
1641
|
+
joined = arg;
|
|
1642
|
+
} else {
|
|
1593
1643
|
if (joined === undefined) joined = arg;
|
|
1594
1644
|
else joined += '/' + arg;
|
|
1595
1645
|
}
|
|
@@ -2174,7 +2224,13 @@ class GitRefManager {
|
|
|
2174
2224
|
* @param {number} [args.depth = undefined] - The maximum depth to resolve symbolic refs.
|
|
2175
2225
|
* @returns {Promise<string>} - The resolved object ID.
|
|
2176
2226
|
*/
|
|
2177
|
-
static async resolve({
|
|
2227
|
+
static async resolve({
|
|
2228
|
+
fs,
|
|
2229
|
+
gitdir,
|
|
2230
|
+
ref,
|
|
2231
|
+
depth = undefined,
|
|
2232
|
+
visited = new Set(),
|
|
2233
|
+
}) {
|
|
2178
2234
|
if (depth !== undefined) {
|
|
2179
2235
|
depth--;
|
|
2180
2236
|
if (depth === -1) {
|
|
@@ -2185,7 +2241,7 @@ class GitRefManager {
|
|
|
2185
2241
|
// Is it a ref pointer?
|
|
2186
2242
|
if (ref.startsWith('ref: ')) {
|
|
2187
2243
|
ref = ref.slice('ref: '.length);
|
|
2188
|
-
return GitRefManager.resolve({ fs, gitdir, ref, depth })
|
|
2244
|
+
return GitRefManager.resolve({ fs, gitdir, ref, depth, visited })
|
|
2189
2245
|
}
|
|
2190
2246
|
// Is it a complete and valid SHA?
|
|
2191
2247
|
if (ref.length === 40 && /[0-9a-f]{40}/.test(ref)) {
|
|
@@ -2204,7 +2260,21 @@ class GitRefManager {
|
|
|
2204
2260
|
packedMap.get(ref)
|
|
2205
2261
|
);
|
|
2206
2262
|
if (sha) {
|
|
2207
|
-
|
|
2263
|
+
// Guard against circular symbolic references (e.g. a -> b -> a). Without
|
|
2264
|
+
// tracking visited refs this recursion would not terminate.
|
|
2265
|
+
if (visited.has(ref)) {
|
|
2266
|
+
throw new InternalError(
|
|
2267
|
+
`Circular reference detected while resolving ref "${ref}"`
|
|
2268
|
+
)
|
|
2269
|
+
}
|
|
2270
|
+
visited.add(ref);
|
|
2271
|
+
return GitRefManager.resolve({
|
|
2272
|
+
fs,
|
|
2273
|
+
gitdir,
|
|
2274
|
+
ref: sha.trim(),
|
|
2275
|
+
depth,
|
|
2276
|
+
visited,
|
|
2277
|
+
})
|
|
2208
2278
|
}
|
|
2209
2279
|
}
|
|
2210
2280
|
// Do we give up?
|
|
@@ -2460,8 +2530,25 @@ function parseBuffer(buffer) {
|
|
|
2460
2530
|
const type = mode2type$1(mode);
|
|
2461
2531
|
const path = buffer.slice(space + 1, nullchar).toString('utf8');
|
|
2462
2532
|
|
|
2463
|
-
//
|
|
2464
|
-
|
|
2533
|
+
// Reject reserved entry names, matching git's verify_path(): path separators,
|
|
2534
|
+
// "." and ".." (which resolve outside the working directory), and ".git" (which git
|
|
2535
|
+
// disallows as a path component). Checks mirror git's is_ntfs_dotgit() and
|
|
2536
|
+
// is_hfs_dotgit(): case-insensitive, trailing dots/spaces stripped (NTFS),
|
|
2537
|
+
// NTFS 8.3 short name aliases (git~1..git~9), and HFS+ ignorable Unicode
|
|
2538
|
+
// characters stripped (zero-width joiners, directional marks, BOM, etc.).
|
|
2539
|
+
const hfsClean = path.replace(
|
|
2540
|
+
/[\u200C-\u200F\u202A-\u202E\u206A-\u206F\uFEFF]/g,
|
|
2541
|
+
''
|
|
2542
|
+
);
|
|
2543
|
+
const normalized = hfsClean.toLowerCase().replace(/[. ]+$/, '');
|
|
2544
|
+
if (
|
|
2545
|
+
path.includes('\\') ||
|
|
2546
|
+
path.includes('/') ||
|
|
2547
|
+
hfsClean === '.' ||
|
|
2548
|
+
hfsClean === '..' ||
|
|
2549
|
+
normalized === '.git' ||
|
|
2550
|
+
/^\.?git~[1-9]$/.test(normalized)
|
|
2551
|
+
) {
|
|
2465
2552
|
throw new UnsafeFilepathError(path)
|
|
2466
2553
|
}
|
|
2467
2554
|
|
|
@@ -2635,21 +2722,26 @@ function applyDelta(delta, source) {
|
|
|
2635
2722
|
if (firstOp.byteLength === targetSize) {
|
|
2636
2723
|
target = firstOp;
|
|
2637
2724
|
} else {
|
|
2638
|
-
//
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2725
|
+
// Build the result from the delta ops instead of pre-allocating `targetSize`.
|
|
2726
|
+
// `targetSize` comes from the delta header and may not match the actual op output,
|
|
2727
|
+
// so allocating it up front can reserve far more memory than the ops produce.
|
|
2728
|
+
// Building from the ops bounds memory to the real output size; the size check below
|
|
2729
|
+
// still validates the total before concatenating.
|
|
2730
|
+
const chunks = [firstOp];
|
|
2731
|
+
let tell = firstOp.byteLength;
|
|
2642
2732
|
|
|
2643
2733
|
while (!reader.eof()) {
|
|
2644
|
-
|
|
2734
|
+
const op = readOp(reader, source);
|
|
2735
|
+
chunks.push(op);
|
|
2736
|
+
tell += op.byteLength;
|
|
2645
2737
|
}
|
|
2646
2738
|
|
|
2647
|
-
const tell = writer.tell();
|
|
2648
2739
|
if (targetSize !== tell) {
|
|
2649
2740
|
throw new InternalError(
|
|
2650
2741
|
`applyDelta expected target buffer to be ${targetSize} bytes but the resulting buffer was ${tell} bytes`
|
|
2651
2742
|
)
|
|
2652
2743
|
}
|
|
2744
|
+
target = Buffer.concat(chunks, targetSize);
|
|
2653
2745
|
}
|
|
2654
2746
|
return target
|
|
2655
2747
|
}
|
|
@@ -6792,6 +6884,30 @@ const worthWalking = (filepath, root) => {
|
|
|
6792
6884
|
|
|
6793
6885
|
// @ts-check
|
|
6794
6886
|
|
|
6887
|
+
/**
|
|
6888
|
+
* Throw if any leading directory component of `fullpath` (relative to `dir`) is a symbolic
|
|
6889
|
+
* link. git does not follow symlinks in the leading path when writing working-tree files;
|
|
6890
|
+
* matching that avoids writing through a symlinked parent into a location outside `dir`.
|
|
6891
|
+
*
|
|
6892
|
+
* @param {import('../models/FileSystem.js').FileSystem} fs
|
|
6893
|
+
* @param {string} dir
|
|
6894
|
+
* @param {string} fullpath
|
|
6895
|
+
*/
|
|
6896
|
+
async function assertNoSymlinkInLeadingPath(fs, dir, fullpath) {
|
|
6897
|
+
const parts = fullpath.split('/');
|
|
6898
|
+
parts.pop(); // the final segment is the entry being written, not a leading dir
|
|
6899
|
+
let current = dir;
|
|
6900
|
+
for (const part of parts) {
|
|
6901
|
+
if (part === '' || part === '.') continue
|
|
6902
|
+
current = `${current}/${part}`;
|
|
6903
|
+
const stats = await fs.lstat(current);
|
|
6904
|
+
// lstat returns null when the path doesn't exist yet (nothing to traverse).
|
|
6905
|
+
if (stats && stats.isSymbolicLink()) {
|
|
6906
|
+
throw new UnsafeFilepathError(fullpath)
|
|
6907
|
+
}
|
|
6908
|
+
}
|
|
6909
|
+
}
|
|
6910
|
+
|
|
6795
6911
|
/**
|
|
6796
6912
|
* @param {object} args
|
|
6797
6913
|
* @param {import('../models/FileSystem.js').FileSystem} args.fs
|
|
@@ -6998,6 +7114,7 @@ async function _checkout({
|
|
|
6998
7114
|
.filter(([method]) => method === 'mkdir' || method === 'mkdir-index')
|
|
6999
7115
|
.map(async function ([_, fullpath]) {
|
|
7000
7116
|
const filepath = `${dir}/${fullpath}`;
|
|
7117
|
+
await assertNoSymlinkInLeadingPath(fs, dir, fullpath);
|
|
7001
7118
|
await fs.mkdir(filepath);
|
|
7002
7119
|
if (onProgress) {
|
|
7003
7120
|
await onProgress({
|
|
@@ -7067,6 +7184,10 @@ async function _checkout({
|
|
|
7067
7184
|
.map(async function ([method, fullpath, oid, mode, chmod]) {
|
|
7068
7185
|
const filepath = `${dir}/${fullpath}`;
|
|
7069
7186
|
if (method !== 'create-index' && method !== 'mkdir-index') {
|
|
7187
|
+
// Don't write through a symlinked leading path: a parent component that
|
|
7188
|
+
// is a symlink could otherwise redirect the write outside the working
|
|
7189
|
+
// tree. git applies the same check (it does not follow symlinks here).
|
|
7190
|
+
await assertNoSymlinkInLeadingPath(fs, dir, fullpath);
|
|
7070
7191
|
const { object } = await _readObject({
|
|
7071
7192
|
fs,
|
|
7072
7193
|
cache,
|
|
@@ -7473,6 +7594,7 @@ async function updateWorkingDir(
|
|
|
7473
7594
|
) {
|
|
7474
7595
|
const filepath = `${dir}/${fullpath}`;
|
|
7475
7596
|
if (method !== 'create-index' && method !== 'mkdir-index') {
|
|
7597
|
+
await assertNoSymlinkInLeadingPath(fs, dir, fullpath);
|
|
7476
7598
|
const { object } = await _readObject({ fs, cache, gitdir, oid });
|
|
7477
7599
|
if (chmod) {
|
|
7478
7600
|
await fs.rm(filepath);
|
|
@@ -9347,8 +9469,8 @@ function filterCapabilities(server, client) {
|
|
|
9347
9469
|
|
|
9348
9470
|
const pkg = {
|
|
9349
9471
|
name: 'isomorphic-git',
|
|
9350
|
-
version: '1.38.
|
|
9351
|
-
agent: 'git/isomorphic-git@1.38.
|
|
9472
|
+
version: '1.38.6',
|
|
9473
|
+
agent: 'git/isomorphic-git@1.38.6',
|
|
9352
9474
|
};
|
|
9353
9475
|
|
|
9354
9476
|
class FIFO {
|