isomorphic-git 1.40.0 → 1.40.1

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
@@ -1332,6 +1332,23 @@ class InvalidOidError extends BaseError {
1332
1332
  /** @type {'InvalidOidError'} */
1333
1333
  InvalidOidError.code = 'InvalidOidError';
1334
1334
 
1335
+ class InvalidRefNameError extends BaseError {
1336
+ /**
1337
+ * @param {string} ref
1338
+ * @param {string} suggestion
1339
+ * @param {boolean} canForce
1340
+ */
1341
+ constructor(ref, suggestion) {
1342
+ super(
1343
+ `"${ref}" would be an invalid git reference. (Hint: a valid alternative would be "${suggestion}".)`
1344
+ );
1345
+ this.code = this.name = InvalidRefNameError.code;
1346
+ this.data = { ref, suggestion };
1347
+ }
1348
+ }
1349
+ /** @type {'InvalidRefNameError'} */
1350
+ InvalidRefNameError.code = 'InvalidRefNameError';
1351
+
1335
1352
  class NoRefspecError extends BaseError {
1336
1353
  /**
1337
1354
  * @param {string} remote
@@ -2003,6 +2020,16 @@ const refpaths = ref => [
2003
2020
  // @see https://git-scm.com/docs/gitrepository-layout
2004
2021
  const GIT_FILES = ['config', 'description', 'index', 'shallow', 'commondir'];
2005
2022
 
2023
+ // These names are legal one-level ref names, so nothing upstream of here
2024
+ // rejects them, and writing to one lands on the repository file itself.
2025
+ // Canonical git refuses too: `git update-ref index <oid>` fails with
2026
+ // "cannot lock ref 'index': reference broken" and leaves the file alone.
2027
+ function assertWritableRef(ref) {
2028
+ if (GIT_FILES.includes(ref)) {
2029
+ throw new InvalidRefNameError(ref, `refs/heads/${ref}`)
2030
+ }
2031
+ }
2032
+
2006
2033
  let lock$1;
2007
2034
 
2008
2035
  async function acquireLock(ref, callback) {
@@ -2057,6 +2084,31 @@ class GitRefManager {
2057
2084
  }
2058
2085
  const refspec = GitRefSpecSet.from(refspecs);
2059
2086
  const actualRefsToWrite = new Map();
2087
+ // Work out every destination this call will write, before touching the
2088
+ // repository. Translating is pure, so it can happen up here; the writes
2089
+ // themselves stay at the end of the function.
2090
+ const refTranslations = refspec.translate([...refs.keys()]);
2091
+ const symrefTranslations = [];
2092
+ for (const [serverRef, translatedRef] of refspec.translate([
2093
+ ...symrefs.keys(),
2094
+ ])) {
2095
+ const symtarget = refspec.translateOne(symrefs.get(serverRef));
2096
+ if (symtarget) {
2097
+ symrefTranslations.push([translatedRef, `ref: ${symtarget}`]);
2098
+ }
2099
+ }
2100
+ // The local side of a refspec is whatever `remote.<name>.fetch` says, so a
2101
+ // config like `+refs/heads/main:index` lands a write on `.git/index`.
2102
+ // Refuse it here rather than at the write loop: `pruneTags` and `prune`
2103
+ // delete refs in between, so a later throw leaves the repository pruned
2104
+ // and not updated. The tags added below are always `refs/tags/...`, which
2105
+ // is never a system file, so nothing is missed by checking this early.
2106
+ for (const [, translatedRef] of refTranslations) {
2107
+ assertWritableRef(translatedRef);
2108
+ }
2109
+ for (const [translatedRef] of symrefTranslations) {
2110
+ assertWritableRef(translatedRef);
2111
+ }
2060
2112
  // Delete all current tags if the pruneTags argument is true.
2061
2113
  if (pruneTags) {
2062
2114
  const tags = await GitRefManager.listRefs({
@@ -2084,18 +2136,12 @@ class GitRefManager {
2084
2136
  }
2085
2137
  }
2086
2138
  // Combine refs and symrefs giving symrefs priority
2087
- const refTranslations = refspec.translate([...refs.keys()]);
2088
2139
  for (const [serverRef, translatedRef] of refTranslations) {
2089
2140
  const value = refs.get(serverRef);
2090
2141
  actualRefsToWrite.set(translatedRef, value);
2091
2142
  }
2092
- const symrefTranslations = refspec.translate([...symrefs.keys()]);
2093
- for (const [serverRef, translatedRef] of symrefTranslations) {
2094
- const value = symrefs.get(serverRef);
2095
- const symtarget = refspec.translateOne(value);
2096
- if (symtarget) {
2097
- actualRefsToWrite.set(translatedRef, `ref: ${symtarget}`);
2098
- }
2143
+ for (const [translatedRef, value] of symrefTranslations) {
2144
+ actualRefsToWrite.set(translatedRef, value);
2099
2145
  }
2100
2146
  // If `prune` argument is true, clear out the existing local refspec roots
2101
2147
  const pruned = [];
@@ -2154,6 +2200,7 @@ class GitRefManager {
2154
2200
  // TODO: make this less crude?
2155
2201
  static async writeRef({ fs, gitdir, ref, value }) {
2156
2202
  // Validate input
2203
+ assertWritableRef(ref);
2157
2204
  if (!value.match(/[0-9a-f]{40}/)) {
2158
2205
  throw new InvalidOidError(value)
2159
2206
  }
@@ -2173,6 +2220,7 @@ class GitRefManager {
2173
2220
  * @returns {Promise<void>}
2174
2221
  */
2175
2222
  static async writeSymbolicRef({ fs, gitdir, ref, value }) {
2223
+ assertWritableRef(ref);
2176
2224
  await acquireLock(ref, async () =>
2177
2225
  fs.write(join(gitdir, ref), 'ref: ' + `${value.trim()}\n`, 'utf8')
2178
2226
  );
@@ -2201,6 +2249,8 @@ class GitRefManager {
2201
2249
  * @returns {Promise<void>}
2202
2250
  */
2203
2251
  static async deleteRefs({ fs, gitdir, refs }) {
2252
+ // Validate input
2253
+ refs.forEach(assertWritableRef);
2204
2254
  // Delete regular ref
2205
2255
  await Promise.all(refs.map(ref => fs.rm(join(gitdir, ref))));
2206
2256
  // Delete any packed ref
@@ -2324,7 +2374,7 @@ class GitRefManager {
2324
2374
  // We need to alternate between the file system and the packed-refs
2325
2375
  const packedMap = await GitRefManager.packedRefs({ fs, gitdir });
2326
2376
  // Look in all the proper paths, in this order
2327
- const allpaths = refpaths(ref);
2377
+ const allpaths = refpaths(ref).filter(p => !GIT_FILES.includes(p)); // exclude git system files (#709)
2328
2378
  for (const ref of allpaths) {
2329
2379
  const refExists = await acquireLock(ref, async () =>
2330
2380
  fs.exists(`${gitdir}/${ref}`)
@@ -3824,23 +3874,6 @@ class InvalidFilepathError extends BaseError {
3824
3874
  /** @type {'InvalidFilepathError'} */
3825
3875
  InvalidFilepathError.code = 'InvalidFilepathError';
3826
3876
 
3827
- class InvalidRefNameError extends BaseError {
3828
- /**
3829
- * @param {string} ref
3830
- * @param {string} suggestion
3831
- * @param {boolean} canForce
3832
- */
3833
- constructor(ref, suggestion) {
3834
- super(
3835
- `"${ref}" would be an invalid git reference. (Hint: a valid alternative would be "${suggestion}".)`
3836
- );
3837
- this.code = this.name = InvalidRefNameError.code;
3838
- this.data = { ref, suggestion };
3839
- }
3840
- }
3841
- /** @type {'InvalidRefNameError'} */
3842
- InvalidRefNameError.code = 'InvalidRefNameError';
3843
-
3844
3877
  class MaxDepthError extends BaseError {
3845
3878
  /**
3846
3879
  * @param {number} depth
@@ -9524,8 +9557,8 @@ function filterCapabilities(server, client) {
9524
9557
 
9525
9558
  const pkg = {
9526
9559
  name: 'isomorphic-git',
9527
- version: '1.40.0',
9528
- agent: 'git/isomorphic-git@1.40.0',
9560
+ version: '1.40.1',
9561
+ agent: 'git/isomorphic-git@1.40.1',
9529
9562
  };
9530
9563
 
9531
9564
  class FIFO {
package/index.js CHANGED
@@ -1325,6 +1325,23 @@ class InvalidOidError extends BaseError {
1325
1325
  /** @type {'InvalidOidError'} */
1326
1326
  InvalidOidError.code = 'InvalidOidError';
1327
1327
 
1328
+ class InvalidRefNameError extends BaseError {
1329
+ /**
1330
+ * @param {string} ref
1331
+ * @param {string} suggestion
1332
+ * @param {boolean} canForce
1333
+ */
1334
+ constructor(ref, suggestion) {
1335
+ super(
1336
+ `"${ref}" would be an invalid git reference. (Hint: a valid alternative would be "${suggestion}".)`
1337
+ );
1338
+ this.code = this.name = InvalidRefNameError.code;
1339
+ this.data = { ref, suggestion };
1340
+ }
1341
+ }
1342
+ /** @type {'InvalidRefNameError'} */
1343
+ InvalidRefNameError.code = 'InvalidRefNameError';
1344
+
1328
1345
  class NoRefspecError extends BaseError {
1329
1346
  /**
1330
1347
  * @param {string} remote
@@ -1996,6 +2013,16 @@ const refpaths = ref => [
1996
2013
  // @see https://git-scm.com/docs/gitrepository-layout
1997
2014
  const GIT_FILES = ['config', 'description', 'index', 'shallow', 'commondir'];
1998
2015
 
2016
+ // These names are legal one-level ref names, so nothing upstream of here
2017
+ // rejects them, and writing to one lands on the repository file itself.
2018
+ // Canonical git refuses too: `git update-ref index <oid>` fails with
2019
+ // "cannot lock ref 'index': reference broken" and leaves the file alone.
2020
+ function assertWritableRef(ref) {
2021
+ if (GIT_FILES.includes(ref)) {
2022
+ throw new InvalidRefNameError(ref, `refs/heads/${ref}`)
2023
+ }
2024
+ }
2025
+
1999
2026
  let lock$1;
2000
2027
 
2001
2028
  async function acquireLock(ref, callback) {
@@ -2050,6 +2077,31 @@ class GitRefManager {
2050
2077
  }
2051
2078
  const refspec = GitRefSpecSet.from(refspecs);
2052
2079
  const actualRefsToWrite = new Map();
2080
+ // Work out every destination this call will write, before touching the
2081
+ // repository. Translating is pure, so it can happen up here; the writes
2082
+ // themselves stay at the end of the function.
2083
+ const refTranslations = refspec.translate([...refs.keys()]);
2084
+ const symrefTranslations = [];
2085
+ for (const [serverRef, translatedRef] of refspec.translate([
2086
+ ...symrefs.keys(),
2087
+ ])) {
2088
+ const symtarget = refspec.translateOne(symrefs.get(serverRef));
2089
+ if (symtarget) {
2090
+ symrefTranslations.push([translatedRef, `ref: ${symtarget}`]);
2091
+ }
2092
+ }
2093
+ // The local side of a refspec is whatever `remote.<name>.fetch` says, so a
2094
+ // config like `+refs/heads/main:index` lands a write on `.git/index`.
2095
+ // Refuse it here rather than at the write loop: `pruneTags` and `prune`
2096
+ // delete refs in between, so a later throw leaves the repository pruned
2097
+ // and not updated. The tags added below are always `refs/tags/...`, which
2098
+ // is never a system file, so nothing is missed by checking this early.
2099
+ for (const [, translatedRef] of refTranslations) {
2100
+ assertWritableRef(translatedRef);
2101
+ }
2102
+ for (const [translatedRef] of symrefTranslations) {
2103
+ assertWritableRef(translatedRef);
2104
+ }
2053
2105
  // Delete all current tags if the pruneTags argument is true.
2054
2106
  if (pruneTags) {
2055
2107
  const tags = await GitRefManager.listRefs({
@@ -2077,18 +2129,12 @@ class GitRefManager {
2077
2129
  }
2078
2130
  }
2079
2131
  // Combine refs and symrefs giving symrefs priority
2080
- const refTranslations = refspec.translate([...refs.keys()]);
2081
2132
  for (const [serverRef, translatedRef] of refTranslations) {
2082
2133
  const value = refs.get(serverRef);
2083
2134
  actualRefsToWrite.set(translatedRef, value);
2084
2135
  }
2085
- const symrefTranslations = refspec.translate([...symrefs.keys()]);
2086
- for (const [serverRef, translatedRef] of symrefTranslations) {
2087
- const value = symrefs.get(serverRef);
2088
- const symtarget = refspec.translateOne(value);
2089
- if (symtarget) {
2090
- actualRefsToWrite.set(translatedRef, `ref: ${symtarget}`);
2091
- }
2136
+ for (const [translatedRef, value] of symrefTranslations) {
2137
+ actualRefsToWrite.set(translatedRef, value);
2092
2138
  }
2093
2139
  // If `prune` argument is true, clear out the existing local refspec roots
2094
2140
  const pruned = [];
@@ -2147,6 +2193,7 @@ class GitRefManager {
2147
2193
  // TODO: make this less crude?
2148
2194
  static async writeRef({ fs, gitdir, ref, value }) {
2149
2195
  // Validate input
2196
+ assertWritableRef(ref);
2150
2197
  if (!value.match(/[0-9a-f]{40}/)) {
2151
2198
  throw new InvalidOidError(value)
2152
2199
  }
@@ -2166,6 +2213,7 @@ class GitRefManager {
2166
2213
  * @returns {Promise<void>}
2167
2214
  */
2168
2215
  static async writeSymbolicRef({ fs, gitdir, ref, value }) {
2216
+ assertWritableRef(ref);
2169
2217
  await acquireLock(ref, async () =>
2170
2218
  fs.write(join(gitdir, ref), 'ref: ' + `${value.trim()}\n`, 'utf8')
2171
2219
  );
@@ -2194,6 +2242,8 @@ class GitRefManager {
2194
2242
  * @returns {Promise<void>}
2195
2243
  */
2196
2244
  static async deleteRefs({ fs, gitdir, refs }) {
2245
+ // Validate input
2246
+ refs.forEach(assertWritableRef);
2197
2247
  // Delete regular ref
2198
2248
  await Promise.all(refs.map(ref => fs.rm(join(gitdir, ref))));
2199
2249
  // Delete any packed ref
@@ -2317,7 +2367,7 @@ class GitRefManager {
2317
2367
  // We need to alternate between the file system and the packed-refs
2318
2368
  const packedMap = await GitRefManager.packedRefs({ fs, gitdir });
2319
2369
  // Look in all the proper paths, in this order
2320
- const allpaths = refpaths(ref);
2370
+ const allpaths = refpaths(ref).filter(p => !GIT_FILES.includes(p)); // exclude git system files (#709)
2321
2371
  for (const ref of allpaths) {
2322
2372
  const refExists = await acquireLock(ref, async () =>
2323
2373
  fs.exists(`${gitdir}/${ref}`)
@@ -3811,23 +3861,6 @@ class InvalidFilepathError extends BaseError {
3811
3861
  /** @type {'InvalidFilepathError'} */
3812
3862
  InvalidFilepathError.code = 'InvalidFilepathError';
3813
3863
 
3814
- class InvalidRefNameError extends BaseError {
3815
- /**
3816
- * @param {string} ref
3817
- * @param {string} suggestion
3818
- * @param {boolean} canForce
3819
- */
3820
- constructor(ref, suggestion) {
3821
- super(
3822
- `"${ref}" would be an invalid git reference. (Hint: a valid alternative would be "${suggestion}".)`
3823
- );
3824
- this.code = this.name = InvalidRefNameError.code;
3825
- this.data = { ref, suggestion };
3826
- }
3827
- }
3828
- /** @type {'InvalidRefNameError'} */
3829
- InvalidRefNameError.code = 'InvalidRefNameError';
3830
-
3831
3864
  class MaxDepthError extends BaseError {
3832
3865
  /**
3833
3866
  * @param {number} depth
@@ -9511,8 +9544,8 @@ function filterCapabilities(server, client) {
9511
9544
 
9512
9545
  const pkg = {
9513
9546
  name: 'isomorphic-git',
9514
- version: '1.40.0',
9515
- agent: 'git/isomorphic-git@1.40.0',
9547
+ version: '1.40.1',
9548
+ agent: 'git/isomorphic-git@1.40.1',
9516
9549
  };
9517
9550
 
9518
9551
  class FIFO {