isomorphic-git 1.42.0 → 1.42.2

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 CHANGED
@@ -6,12 +6,12 @@ function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'defau
6
6
 
7
7
  var Hash = _interopDefault(require('sha.js/sha1.js'));
8
8
  var AsyncLock = _interopDefault(require('async-lock'));
9
+ var cleanGitRef = _interopDefault(require('clean-git-ref'));
9
10
  var crc32 = _interopDefault(require('crc-32'));
10
11
  var pako = _interopDefault(require('pako'));
11
12
  var crypto$1 = require('crypto');
12
13
  var pify = _interopDefault(require('pify'));
13
14
  var ignore = _interopDefault(require('ignore'));
14
- var cleanGitRef = _interopDefault(require('clean-git-ref'));
15
15
  var diff3Merge = _interopDefault(require('diff3'));
16
16
 
17
17
  /**
@@ -1558,6 +1558,22 @@ function compareRefNames(a, b) {
1558
1558
  return tmp
1559
1559
  }
1560
1560
 
1561
+ /*
1562
+ Adapted from is-git-ref-name-valid
1563
+ SPDX-License-Identifier: MIT
1564
+ Copyright © Vincent Weevers
1565
+ */
1566
+
1567
+ // eslint-disable-next-line no-control-regex
1568
+ const bad = /(^|[/.])([/.]|$)|^@$|@{|[\x00-\x20\x7f~^:?*[\\]|\.lock(\/|$)/;
1569
+
1570
+ function isValidRef(name, onelevel) {
1571
+ if (typeof name !== 'string')
1572
+ throw new TypeError('Reference name must be a string')
1573
+
1574
+ return !bad.test(name) && (!!onelevel || name.includes('/'))
1575
+ }
1576
+
1561
1577
  /*!
1562
1578
  * This code for `path.join` is directly copied from @zenfs/core/path for bundle size improvements.
1563
1579
  * SPDX-License-Identifier: LGPL-3.0-or-later
@@ -2061,6 +2077,13 @@ function assertWritableRef(ref) {
2061
2077
  if (GIT_FILES.includes(ref)) {
2062
2078
  throw new InvalidRefNameError(ref, `refs/heads/${ref}`)
2063
2079
  }
2080
+ // A ref name is joined onto gitdir with `join()`, which collapses `..`
2081
+ // exactly like `path.join`. A server-supplied ref/tag name or the wildcard
2082
+ // capture of a refspec can therefore climb out of gitdir, or anywhere
2083
+ // beneath it, entirely unvalidated up to this point. See GHSA-h3c3-jh3g-8hcc.
2084
+ if (!isValidRef(ref, true)) {
2085
+ throw new InvalidRefNameError(ref, cleanGitRef.clean(ref))
2086
+ }
2064
2087
  }
2065
2088
 
2066
2089
  /**
@@ -2120,21 +2143,39 @@ class GitRefManager {
2120
2143
  ])) {
2121
2144
  const symtarget = refspec.translateOne(symrefs.get(serverRef));
2122
2145
  if (symtarget) {
2146
+ // The destination (translatedRef) is validated below, but the target
2147
+ // a symref points at is server-supplied too (the wire parser accepts
2148
+ // `symref=HEAD:(.*)` unrestricted) and only ever passed through the
2149
+ // refspec's plain string substitution, never checked. Left alone, a
2150
+ // `..`-laden target is written into the file content as-is and later
2151
+ // drives an out-of-gitdir read the next time something resolves it.
2152
+ // See GHSA-h3c3-jh3g-8hcc.
2153
+ assertWritableRef(symtarget);
2123
2154
  symrefTranslations.push([translatedRef, `ref: ${symtarget}`]);
2124
2155
  }
2125
2156
  }
2157
+ // Tags aren't translated by a refspec, but they are still a server-supplied
2158
+ // name written straight to gitdir below, so they need the same check.
2159
+ // Computed here (pure, no I/O) so it can be validated up front and then
2160
+ // reused unchanged in the write step further down.
2161
+ const tagRefsToWrite = tags
2162
+ ? [...refs.keys()].filter(
2163
+ serverRef =>
2164
+ serverRef.startsWith('refs/tags') && !serverRef.endsWith('^{}')
2165
+ )
2166
+ : [];
2126
2167
  // The local side of a refspec is whatever `remote.<name>.fetch` says, so a
2127
2168
  // config like `+refs/heads/main:index` lands a write on `.git/index`.
2128
2169
  // Refuse it here rather than at the write loop: `pruneTags` and `prune`
2129
2170
  // delete refs in between, so a later throw leaves the repository pruned
2130
- // and not updated. The tags added below are always `refs/tags/...`, which
2131
- // is never a system file, so nothing is missed by checking this early.
2171
+ // and not updated.
2132
2172
  for (const [, translatedRef] of refTranslations) {
2133
2173
  assertWritableRef(translatedRef);
2134
2174
  }
2135
2175
  for (const [translatedRef] of symrefTranslations) {
2136
2176
  assertWritableRef(translatedRef);
2137
2177
  }
2178
+ tagRefsToWrite.forEach(assertWritableRef);
2138
2179
  // Delete all current tags if the pruneTags argument is true.
2139
2180
  if (pruneTags) {
2140
2181
  const tags = await GitRefManager.listRefs({
@@ -2149,16 +2190,12 @@ class GitRefManager {
2149
2190
  });
2150
2191
  }
2151
2192
  // Add all tags if the fetch tags argument is true.
2152
- if (tags) {
2153
- for (const serverRef of refs.keys()) {
2154
- if (serverRef.startsWith('refs/tags') && !serverRef.endsWith('^{}')) {
2155
- // Git's behavior is to only fetch tags that do not conflict with tags already present.
2156
- if (!(await GitRefManager.exists({ fs, gitdir, ref: serverRef }))) {
2157
- // Always use the object id of the tag itself, and not the peeled object id.
2158
- const oid = refs.get(serverRef);
2159
- actualRefsToWrite.set(serverRef, oid);
2160
- }
2161
- }
2193
+ for (const serverRef of tagRefsToWrite) {
2194
+ // Git's behavior is to only fetch tags that do not conflict with tags already present.
2195
+ if (!(await GitRefManager.exists({ fs, gitdir, ref: serverRef }))) {
2196
+ // Always use the object id of the tag itself, and not the peeled object id.
2197
+ const oid = refs.get(serverRef);
2198
+ actualRefsToWrite.set(serverRef, oid);
2162
2199
  }
2163
2200
  }
2164
2201
  // Combine refs and symrefs giving symrefs priority
@@ -6698,22 +6735,6 @@ async function addNote({
6698
6735
  }
6699
6736
  }
6700
6737
 
6701
- /*
6702
- Adapted from is-git-ref-name-valid
6703
- SPDX-License-Identifier: MIT
6704
- Copyright © Vincent Weevers
6705
- */
6706
-
6707
- // eslint-disable-next-line no-control-regex
6708
- const bad = /(^|[/.])([/.]|$)|^@$|@{|[\x00-\x20\x7f~^:?*[\\]|\.lock(\/|$)/;
6709
-
6710
- function isValidRef(name, onelevel) {
6711
- if (typeof name !== 'string')
6712
- throw new TypeError('Reference name must be a string')
6713
-
6714
- return !bad.test(name) && (!!onelevel || name.includes('/'))
6715
- }
6716
-
6717
6738
  // @ts-check
6718
6739
 
6719
6740
  /**
@@ -9779,8 +9800,8 @@ function filterCapabilities(server, client) {
9779
9800
 
9780
9801
  const pkg = {
9781
9802
  name: 'isomorphic-git',
9782
- version: '1.42.0',
9783
- agent: 'git/isomorphic-git@1.42.0',
9803
+ version: '1.42.2',
9804
+ agent: 'git/isomorphic-git@1.42.2',
9784
9805
  };
9785
9806
 
9786
9807
  class FIFO {
@@ -14476,13 +14497,20 @@ async function _push({
14476
14497
 
14477
14498
  // If remote branch is present, look for a common merge base.
14478
14499
  if (oldoid !== '0000000000000000000000000000000000000000') {
14479
- // trick to speed up common force push scenarios
14480
- const mergebase = await _findMergeBase({
14481
- fs,
14482
- cache,
14483
- gitdir,
14484
- oids: [oid, oldoid],
14485
- });
14500
+ // Speeds up common force-push / thin-pack scenarios. The advertised
14501
+ // remote tip may be absent locally (e.g. remote advanced since clone);
14502
+ // that is normal and must not surface as NotFoundError from merge-base.
14503
+ let mergebase = [];
14504
+ try {
14505
+ mergebase = await _findMergeBase({
14506
+ fs,
14507
+ cache,
14508
+ gitdir,
14509
+ oids: [oid, oldoid],
14510
+ });
14511
+ } catch (e) {
14512
+ mergebase = [];
14513
+ }
14486
14514
  for (const oid of mergebase) finish.push(oid);
14487
14515
  if (thinPack) {
14488
14516
  skipObjects = await listObjects({ fs, cache, gitdir, oids: mergebase });
@@ -14608,7 +14636,7 @@ async function _push({
14608
14636
  ) {
14609
14637
  // TODO: I think this should actually be using a refspec transform rather than assuming 'refs/remotes/{remote}'
14610
14638
  const ref = `refs/remotes/${remote}/${fullRemoteRef.replace(
14611
- 'refs/heads',
14639
+ 'refs/heads/',
14612
14640
  ''
14613
14641
  )}`;
14614
14642
  if (_delete) {
package/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import Hash from 'sha.js/sha1.js';
2
2
  import AsyncLock from 'async-lock';
3
+ import cleanGitRef from 'clean-git-ref';
3
4
  import crc32 from 'crc-32';
4
5
  import pako from 'pako';
5
6
  import pify from 'pify';
6
7
  import ignore from 'ignore';
7
- import cleanGitRef from 'clean-git-ref';
8
8
  import diff3Merge from 'diff3';
9
9
 
10
10
  /**
@@ -1551,6 +1551,22 @@ function compareRefNames(a, b) {
1551
1551
  return tmp
1552
1552
  }
1553
1553
 
1554
+ /*
1555
+ Adapted from is-git-ref-name-valid
1556
+ SPDX-License-Identifier: MIT
1557
+ Copyright © Vincent Weevers
1558
+ */
1559
+
1560
+ // eslint-disable-next-line no-control-regex
1561
+ const bad = /(^|[/.])([/.]|$)|^@$|@{|[\x00-\x20\x7f~^:?*[\\]|\.lock(\/|$)/;
1562
+
1563
+ function isValidRef(name, onelevel) {
1564
+ if (typeof name !== 'string')
1565
+ throw new TypeError('Reference name must be a string')
1566
+
1567
+ return !bad.test(name) && (!!onelevel || name.includes('/'))
1568
+ }
1569
+
1554
1570
  /*!
1555
1571
  * This code for `path.join` is directly copied from @zenfs/core/path for bundle size improvements.
1556
1572
  * SPDX-License-Identifier: LGPL-3.0-or-later
@@ -2054,6 +2070,13 @@ function assertWritableRef(ref) {
2054
2070
  if (GIT_FILES.includes(ref)) {
2055
2071
  throw new InvalidRefNameError(ref, `refs/heads/${ref}`)
2056
2072
  }
2073
+ // A ref name is joined onto gitdir with `join()`, which collapses `..`
2074
+ // exactly like `path.join`. A server-supplied ref/tag name or the wildcard
2075
+ // capture of a refspec can therefore climb out of gitdir, or anywhere
2076
+ // beneath it, entirely unvalidated up to this point. See GHSA-h3c3-jh3g-8hcc.
2077
+ if (!isValidRef(ref, true)) {
2078
+ throw new InvalidRefNameError(ref, cleanGitRef.clean(ref))
2079
+ }
2057
2080
  }
2058
2081
 
2059
2082
  /**
@@ -2113,21 +2136,39 @@ class GitRefManager {
2113
2136
  ])) {
2114
2137
  const symtarget = refspec.translateOne(symrefs.get(serverRef));
2115
2138
  if (symtarget) {
2139
+ // The destination (translatedRef) is validated below, but the target
2140
+ // a symref points at is server-supplied too (the wire parser accepts
2141
+ // `symref=HEAD:(.*)` unrestricted) and only ever passed through the
2142
+ // refspec's plain string substitution, never checked. Left alone, a
2143
+ // `..`-laden target is written into the file content as-is and later
2144
+ // drives an out-of-gitdir read the next time something resolves it.
2145
+ // See GHSA-h3c3-jh3g-8hcc.
2146
+ assertWritableRef(symtarget);
2116
2147
  symrefTranslations.push([translatedRef, `ref: ${symtarget}`]);
2117
2148
  }
2118
2149
  }
2150
+ // Tags aren't translated by a refspec, but they are still a server-supplied
2151
+ // name written straight to gitdir below, so they need the same check.
2152
+ // Computed here (pure, no I/O) so it can be validated up front and then
2153
+ // reused unchanged in the write step further down.
2154
+ const tagRefsToWrite = tags
2155
+ ? [...refs.keys()].filter(
2156
+ serverRef =>
2157
+ serverRef.startsWith('refs/tags') && !serverRef.endsWith('^{}')
2158
+ )
2159
+ : [];
2119
2160
  // The local side of a refspec is whatever `remote.<name>.fetch` says, so a
2120
2161
  // config like `+refs/heads/main:index` lands a write on `.git/index`.
2121
2162
  // Refuse it here rather than at the write loop: `pruneTags` and `prune`
2122
2163
  // delete refs in between, so a later throw leaves the repository pruned
2123
- // and not updated. The tags added below are always `refs/tags/...`, which
2124
- // is never a system file, so nothing is missed by checking this early.
2164
+ // and not updated.
2125
2165
  for (const [, translatedRef] of refTranslations) {
2126
2166
  assertWritableRef(translatedRef);
2127
2167
  }
2128
2168
  for (const [translatedRef] of symrefTranslations) {
2129
2169
  assertWritableRef(translatedRef);
2130
2170
  }
2171
+ tagRefsToWrite.forEach(assertWritableRef);
2131
2172
  // Delete all current tags if the pruneTags argument is true.
2132
2173
  if (pruneTags) {
2133
2174
  const tags = await GitRefManager.listRefs({
@@ -2142,16 +2183,12 @@ class GitRefManager {
2142
2183
  });
2143
2184
  }
2144
2185
  // Add all tags if the fetch tags argument is true.
2145
- if (tags) {
2146
- for (const serverRef of refs.keys()) {
2147
- if (serverRef.startsWith('refs/tags') && !serverRef.endsWith('^{}')) {
2148
- // Git's behavior is to only fetch tags that do not conflict with tags already present.
2149
- if (!(await GitRefManager.exists({ fs, gitdir, ref: serverRef }))) {
2150
- // Always use the object id of the tag itself, and not the peeled object id.
2151
- const oid = refs.get(serverRef);
2152
- actualRefsToWrite.set(serverRef, oid);
2153
- }
2154
- }
2186
+ for (const serverRef of tagRefsToWrite) {
2187
+ // Git's behavior is to only fetch tags that do not conflict with tags already present.
2188
+ if (!(await GitRefManager.exists({ fs, gitdir, ref: serverRef }))) {
2189
+ // Always use the object id of the tag itself, and not the peeled object id.
2190
+ const oid = refs.get(serverRef);
2191
+ actualRefsToWrite.set(serverRef, oid);
2155
2192
  }
2156
2193
  }
2157
2194
  // Combine refs and symrefs giving symrefs priority
@@ -6685,22 +6722,6 @@ async function addNote({
6685
6722
  }
6686
6723
  }
6687
6724
 
6688
- /*
6689
- Adapted from is-git-ref-name-valid
6690
- SPDX-License-Identifier: MIT
6691
- Copyright © Vincent Weevers
6692
- */
6693
-
6694
- // eslint-disable-next-line no-control-regex
6695
- const bad = /(^|[/.])([/.]|$)|^@$|@{|[\x00-\x20\x7f~^:?*[\\]|\.lock(\/|$)/;
6696
-
6697
- function isValidRef(name, onelevel) {
6698
- if (typeof name !== 'string')
6699
- throw new TypeError('Reference name must be a string')
6700
-
6701
- return !bad.test(name) && (!!onelevel || name.includes('/'))
6702
- }
6703
-
6704
6725
  // @ts-check
6705
6726
 
6706
6727
  /**
@@ -9766,8 +9787,8 @@ function filterCapabilities(server, client) {
9766
9787
 
9767
9788
  const pkg = {
9768
9789
  name: 'isomorphic-git',
9769
- version: '1.42.0',
9770
- agent: 'git/isomorphic-git@1.42.0',
9790
+ version: '1.42.2',
9791
+ agent: 'git/isomorphic-git@1.42.2',
9771
9792
  };
9772
9793
 
9773
9794
  class FIFO {
@@ -14463,13 +14484,20 @@ async function _push({
14463
14484
 
14464
14485
  // If remote branch is present, look for a common merge base.
14465
14486
  if (oldoid !== '0000000000000000000000000000000000000000') {
14466
- // trick to speed up common force push scenarios
14467
- const mergebase = await _findMergeBase({
14468
- fs,
14469
- cache,
14470
- gitdir,
14471
- oids: [oid, oldoid],
14472
- });
14487
+ // Speeds up common force-push / thin-pack scenarios. The advertised
14488
+ // remote tip may be absent locally (e.g. remote advanced since clone);
14489
+ // that is normal and must not surface as NotFoundError from merge-base.
14490
+ let mergebase = [];
14491
+ try {
14492
+ mergebase = await _findMergeBase({
14493
+ fs,
14494
+ cache,
14495
+ gitdir,
14496
+ oids: [oid, oldoid],
14497
+ });
14498
+ } catch (e) {
14499
+ mergebase = [];
14500
+ }
14473
14501
  for (const oid of mergebase) finish.push(oid);
14474
14502
  if (thinPack) {
14475
14503
  skipObjects = await listObjects({ fs, cache, gitdir, oids: mergebase });
@@ -14595,7 +14623,7 @@ async function _push({
14595
14623
  ) {
14596
14624
  // TODO: I think this should actually be using a refspec transform rather than assuming 'refs/remotes/{remote}'
14597
14625
  const ref = `refs/remotes/${remote}/${fullRemoteRef.replace(
14598
- 'refs/heads',
14626
+ 'refs/heads/',
14599
14627
  ''
14600
14628
  )}`;
14601
14629
  if (_delete) {