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.
@@ -1644,6 +1644,23 @@ class InvalidOidError extends BaseError {
1644
1644
  /** @type {'InvalidOidError'} */
1645
1645
  InvalidOidError.code = 'InvalidOidError';
1646
1646
 
1647
+ class InvalidRefNameError extends BaseError {
1648
+ /**
1649
+ * @param {string} ref
1650
+ * @param {string} suggestion
1651
+ * @param {boolean} canForce
1652
+ */
1653
+ constructor(ref, suggestion) {
1654
+ super(
1655
+ `"${ref}" would be an invalid git reference. (Hint: a valid alternative would be "${suggestion}".)`
1656
+ );
1657
+ this.code = this.name = InvalidRefNameError.code;
1658
+ this.data = { ref, suggestion };
1659
+ }
1660
+ }
1661
+ /** @type {'InvalidRefNameError'} */
1662
+ InvalidRefNameError.code = 'InvalidRefNameError';
1663
+
1647
1664
  class NoRefspecError extends BaseError {
1648
1665
  /**
1649
1666
  * @param {string} remote
@@ -1846,6 +1863,16 @@ const refpaths = ref => [
1846
1863
  // @see https://git-scm.com/docs/gitrepository-layout
1847
1864
  const GIT_FILES = ['config', 'description', 'index', 'shallow', 'commondir'];
1848
1865
 
1866
+ // These names are legal one-level ref names, so nothing upstream of here
1867
+ // rejects them, and writing to one lands on the repository file itself.
1868
+ // Canonical git refuses too: `git update-ref index <oid>` fails with
1869
+ // "cannot lock ref 'index': reference broken" and leaves the file alone.
1870
+ function assertWritableRef(ref) {
1871
+ if (GIT_FILES.includes(ref)) {
1872
+ throw new InvalidRefNameError(ref, `refs/heads/${ref}`)
1873
+ }
1874
+ }
1875
+
1849
1876
  let lock$1;
1850
1877
 
1851
1878
  async function acquireLock(ref, callback) {
@@ -1900,6 +1927,31 @@ class GitRefManager {
1900
1927
  }
1901
1928
  const refspec = GitRefSpecSet.from(refspecs);
1902
1929
  const actualRefsToWrite = new Map();
1930
+ // Work out every destination this call will write, before touching the
1931
+ // repository. Translating is pure, so it can happen up here; the writes
1932
+ // themselves stay at the end of the function.
1933
+ const refTranslations = refspec.translate([...refs.keys()]);
1934
+ const symrefTranslations = [];
1935
+ for (const [serverRef, translatedRef] of refspec.translate([
1936
+ ...symrefs.keys(),
1937
+ ])) {
1938
+ const symtarget = refspec.translateOne(symrefs.get(serverRef));
1939
+ if (symtarget) {
1940
+ symrefTranslations.push([translatedRef, `ref: ${symtarget}`]);
1941
+ }
1942
+ }
1943
+ // The local side of a refspec is whatever `remote.<name>.fetch` says, so a
1944
+ // config like `+refs/heads/main:index` lands a write on `.git/index`.
1945
+ // Refuse it here rather than at the write loop: `pruneTags` and `prune`
1946
+ // delete refs in between, so a later throw leaves the repository pruned
1947
+ // and not updated. The tags added below are always `refs/tags/...`, which
1948
+ // is never a system file, so nothing is missed by checking this early.
1949
+ for (const [, translatedRef] of refTranslations) {
1950
+ assertWritableRef(translatedRef);
1951
+ }
1952
+ for (const [translatedRef] of symrefTranslations) {
1953
+ assertWritableRef(translatedRef);
1954
+ }
1903
1955
  // Delete all current tags if the pruneTags argument is true.
1904
1956
  if (pruneTags) {
1905
1957
  const tags = await GitRefManager.listRefs({
@@ -1927,18 +1979,12 @@ class GitRefManager {
1927
1979
  }
1928
1980
  }
1929
1981
  // Combine refs and symrefs giving symrefs priority
1930
- const refTranslations = refspec.translate([...refs.keys()]);
1931
1982
  for (const [serverRef, translatedRef] of refTranslations) {
1932
1983
  const value = refs.get(serverRef);
1933
1984
  actualRefsToWrite.set(translatedRef, value);
1934
1985
  }
1935
- const symrefTranslations = refspec.translate([...symrefs.keys()]);
1936
- for (const [serverRef, translatedRef] of symrefTranslations) {
1937
- const value = symrefs.get(serverRef);
1938
- const symtarget = refspec.translateOne(value);
1939
- if (symtarget) {
1940
- actualRefsToWrite.set(translatedRef, `ref: ${symtarget}`);
1941
- }
1986
+ for (const [translatedRef, value] of symrefTranslations) {
1987
+ actualRefsToWrite.set(translatedRef, value);
1942
1988
  }
1943
1989
  // If `prune` argument is true, clear out the existing local refspec roots
1944
1990
  const pruned = [];
@@ -1997,6 +2043,7 @@ class GitRefManager {
1997
2043
  // TODO: make this less crude?
1998
2044
  static async writeRef({ fs, gitdir, ref, value }) {
1999
2045
  // Validate input
2046
+ assertWritableRef(ref);
2000
2047
  if (!value.match(/[0-9a-f]{40}/)) {
2001
2048
  throw new InvalidOidError(value)
2002
2049
  }
@@ -2016,6 +2063,7 @@ class GitRefManager {
2016
2063
  * @returns {Promise<void>}
2017
2064
  */
2018
2065
  static async writeSymbolicRef({ fs, gitdir, ref, value }) {
2066
+ assertWritableRef(ref);
2019
2067
  await acquireLock(ref, async () =>
2020
2068
  fs.write(join(gitdir, ref), 'ref: ' + `${value.trim()}\n`, 'utf8')
2021
2069
  );
@@ -2044,6 +2092,8 @@ class GitRefManager {
2044
2092
  * @returns {Promise<void>}
2045
2093
  */
2046
2094
  static async deleteRefs({ fs, gitdir, refs }) {
2095
+ // Validate input
2096
+ refs.forEach(assertWritableRef);
2047
2097
  // Delete regular ref
2048
2098
  await Promise.all(refs.map(ref => fs.rm(join(gitdir, ref))));
2049
2099
  // Delete any packed ref
@@ -2167,7 +2217,7 @@ class GitRefManager {
2167
2217
  // We need to alternate between the file system and the packed-refs
2168
2218
  const packedMap = await GitRefManager.packedRefs({ fs, gitdir });
2169
2219
  // Look in all the proper paths, in this order
2170
- const allpaths = refpaths(ref);
2220
+ const allpaths = refpaths(ref).filter(p => !GIT_FILES.includes(p)); // exclude git system files (#709)
2171
2221
  for (const ref of allpaths) {
2172
2222
  const refExists = await acquireLock(ref, async () =>
2173
2223
  fs.exists(`${gitdir}/${ref}`)
@@ -4518,23 +4568,6 @@ async function _writeCommit({ fs, gitdir, commit }) {
4518
4568
  return oid
4519
4569
  }
4520
4570
 
4521
- class InvalidRefNameError extends BaseError {
4522
- /**
4523
- * @param {string} ref
4524
- * @param {string} suggestion
4525
- * @param {boolean} canForce
4526
- */
4527
- constructor(ref, suggestion) {
4528
- super(
4529
- `"${ref}" would be an invalid git reference. (Hint: a valid alternative would be "${suggestion}".)`
4530
- );
4531
- this.code = this.name = InvalidRefNameError.code;
4532
- this.data = { ref, suggestion };
4533
- }
4534
- }
4535
- /** @type {'InvalidRefNameError'} */
4536
- InvalidRefNameError.code = 'InvalidRefNameError';
4537
-
4538
4571
  class MissingNameError extends BaseError {
4539
4572
  /**
4540
4573
  * @param {'author'|'committer'|'tagger'} role
package/managers/index.js CHANGED
@@ -1637,6 +1637,23 @@ class InvalidOidError extends BaseError {
1637
1637
  /** @type {'InvalidOidError'} */
1638
1638
  InvalidOidError.code = 'InvalidOidError';
1639
1639
 
1640
+ class InvalidRefNameError extends BaseError {
1641
+ /**
1642
+ * @param {string} ref
1643
+ * @param {string} suggestion
1644
+ * @param {boolean} canForce
1645
+ */
1646
+ constructor(ref, suggestion) {
1647
+ super(
1648
+ `"${ref}" would be an invalid git reference. (Hint: a valid alternative would be "${suggestion}".)`
1649
+ );
1650
+ this.code = this.name = InvalidRefNameError.code;
1651
+ this.data = { ref, suggestion };
1652
+ }
1653
+ }
1654
+ /** @type {'InvalidRefNameError'} */
1655
+ InvalidRefNameError.code = 'InvalidRefNameError';
1656
+
1640
1657
  class NoRefspecError extends BaseError {
1641
1658
  /**
1642
1659
  * @param {string} remote
@@ -1839,6 +1856,16 @@ const refpaths = ref => [
1839
1856
  // @see https://git-scm.com/docs/gitrepository-layout
1840
1857
  const GIT_FILES = ['config', 'description', 'index', 'shallow', 'commondir'];
1841
1858
 
1859
+ // These names are legal one-level ref names, so nothing upstream of here
1860
+ // rejects them, and writing to one lands on the repository file itself.
1861
+ // Canonical git refuses too: `git update-ref index <oid>` fails with
1862
+ // "cannot lock ref 'index': reference broken" and leaves the file alone.
1863
+ function assertWritableRef(ref) {
1864
+ if (GIT_FILES.includes(ref)) {
1865
+ throw new InvalidRefNameError(ref, `refs/heads/${ref}`)
1866
+ }
1867
+ }
1868
+
1842
1869
  let lock$1;
1843
1870
 
1844
1871
  async function acquireLock(ref, callback) {
@@ -1893,6 +1920,31 @@ class GitRefManager {
1893
1920
  }
1894
1921
  const refspec = GitRefSpecSet.from(refspecs);
1895
1922
  const actualRefsToWrite = new Map();
1923
+ // Work out every destination this call will write, before touching the
1924
+ // repository. Translating is pure, so it can happen up here; the writes
1925
+ // themselves stay at the end of the function.
1926
+ const refTranslations = refspec.translate([...refs.keys()]);
1927
+ const symrefTranslations = [];
1928
+ for (const [serverRef, translatedRef] of refspec.translate([
1929
+ ...symrefs.keys(),
1930
+ ])) {
1931
+ const symtarget = refspec.translateOne(symrefs.get(serverRef));
1932
+ if (symtarget) {
1933
+ symrefTranslations.push([translatedRef, `ref: ${symtarget}`]);
1934
+ }
1935
+ }
1936
+ // The local side of a refspec is whatever `remote.<name>.fetch` says, so a
1937
+ // config like `+refs/heads/main:index` lands a write on `.git/index`.
1938
+ // Refuse it here rather than at the write loop: `pruneTags` and `prune`
1939
+ // delete refs in between, so a later throw leaves the repository pruned
1940
+ // and not updated. The tags added below are always `refs/tags/...`, which
1941
+ // is never a system file, so nothing is missed by checking this early.
1942
+ for (const [, translatedRef] of refTranslations) {
1943
+ assertWritableRef(translatedRef);
1944
+ }
1945
+ for (const [translatedRef] of symrefTranslations) {
1946
+ assertWritableRef(translatedRef);
1947
+ }
1896
1948
  // Delete all current tags if the pruneTags argument is true.
1897
1949
  if (pruneTags) {
1898
1950
  const tags = await GitRefManager.listRefs({
@@ -1920,18 +1972,12 @@ class GitRefManager {
1920
1972
  }
1921
1973
  }
1922
1974
  // Combine refs and symrefs giving symrefs priority
1923
- const refTranslations = refspec.translate([...refs.keys()]);
1924
1975
  for (const [serverRef, translatedRef] of refTranslations) {
1925
1976
  const value = refs.get(serverRef);
1926
1977
  actualRefsToWrite.set(translatedRef, value);
1927
1978
  }
1928
- const symrefTranslations = refspec.translate([...symrefs.keys()]);
1929
- for (const [serverRef, translatedRef] of symrefTranslations) {
1930
- const value = symrefs.get(serverRef);
1931
- const symtarget = refspec.translateOne(value);
1932
- if (symtarget) {
1933
- actualRefsToWrite.set(translatedRef, `ref: ${symtarget}`);
1934
- }
1979
+ for (const [translatedRef, value] of symrefTranslations) {
1980
+ actualRefsToWrite.set(translatedRef, value);
1935
1981
  }
1936
1982
  // If `prune` argument is true, clear out the existing local refspec roots
1937
1983
  const pruned = [];
@@ -1990,6 +2036,7 @@ class GitRefManager {
1990
2036
  // TODO: make this less crude?
1991
2037
  static async writeRef({ fs, gitdir, ref, value }) {
1992
2038
  // Validate input
2039
+ assertWritableRef(ref);
1993
2040
  if (!value.match(/[0-9a-f]{40}/)) {
1994
2041
  throw new InvalidOidError(value)
1995
2042
  }
@@ -2009,6 +2056,7 @@ class GitRefManager {
2009
2056
  * @returns {Promise<void>}
2010
2057
  */
2011
2058
  static async writeSymbolicRef({ fs, gitdir, ref, value }) {
2059
+ assertWritableRef(ref);
2012
2060
  await acquireLock(ref, async () =>
2013
2061
  fs.write(join(gitdir, ref), 'ref: ' + `${value.trim()}\n`, 'utf8')
2014
2062
  );
@@ -2037,6 +2085,8 @@ class GitRefManager {
2037
2085
  * @returns {Promise<void>}
2038
2086
  */
2039
2087
  static async deleteRefs({ fs, gitdir, refs }) {
2088
+ // Validate input
2089
+ refs.forEach(assertWritableRef);
2040
2090
  // Delete regular ref
2041
2091
  await Promise.all(refs.map(ref => fs.rm(join(gitdir, ref))));
2042
2092
  // Delete any packed ref
@@ -2160,7 +2210,7 @@ class GitRefManager {
2160
2210
  // We need to alternate between the file system and the packed-refs
2161
2211
  const packedMap = await GitRefManager.packedRefs({ fs, gitdir });
2162
2212
  // Look in all the proper paths, in this order
2163
- const allpaths = refpaths(ref);
2213
+ const allpaths = refpaths(ref).filter(p => !GIT_FILES.includes(p)); // exclude git system files (#709)
2164
2214
  for (const ref of allpaths) {
2165
2215
  const refExists = await acquireLock(ref, async () =>
2166
2216
  fs.exists(`${gitdir}/${ref}`)
@@ -4505,23 +4555,6 @@ async function _writeCommit({ fs, gitdir, commit }) {
4505
4555
  return oid
4506
4556
  }
4507
4557
 
4508
- class InvalidRefNameError extends BaseError {
4509
- /**
4510
- * @param {string} ref
4511
- * @param {string} suggestion
4512
- * @param {boolean} canForce
4513
- */
4514
- constructor(ref, suggestion) {
4515
- super(
4516
- `"${ref}" would be an invalid git reference. (Hint: a valid alternative would be "${suggestion}".)`
4517
- );
4518
- this.code = this.name = InvalidRefNameError.code;
4519
- this.data = { ref, suggestion };
4520
- }
4521
- }
4522
- /** @type {'InvalidRefNameError'} */
4523
- InvalidRefNameError.code = 'InvalidRefNameError';
4524
-
4525
4558
  class MissingNameError extends BaseError {
4526
4559
  /**
4527
4560
  * @param {'author'|'committer'|'tagger'} role