isomorphic-git 1.39.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.
@@ -63,6 +63,7 @@ var crypto$1 = require('crypto');
63
63
  * @property {string} message Commit message
64
64
  * @property {string} tree SHA-1 object id of corresponding file tree
65
65
  * @property {string[]} parent an array of zero or more SHA-1 object ids
66
+ * @property {Array.<Array.<string|null>>} [changes] Changed files as `[newOid, oldOid, filepath]`; present when `log` is called with `includeChanges: true`.
66
67
  * @property {Object} author
67
68
  * @property {string} author.name The author's name
68
69
  * @property {string} author.email The author's email
@@ -1643,6 +1644,23 @@ class InvalidOidError extends BaseError {
1643
1644
  /** @type {'InvalidOidError'} */
1644
1645
  InvalidOidError.code = 'InvalidOidError';
1645
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
+
1646
1664
  class NoRefspecError extends BaseError {
1647
1665
  /**
1648
1666
  * @param {string} remote
@@ -1845,6 +1863,16 @@ const refpaths = ref => [
1845
1863
  // @see https://git-scm.com/docs/gitrepository-layout
1846
1864
  const GIT_FILES = ['config', 'description', 'index', 'shallow', 'commondir'];
1847
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
+
1848
1876
  let lock$1;
1849
1877
 
1850
1878
  async function acquireLock(ref, callback) {
@@ -1899,6 +1927,31 @@ class GitRefManager {
1899
1927
  }
1900
1928
  const refspec = GitRefSpecSet.from(refspecs);
1901
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
+ }
1902
1955
  // Delete all current tags if the pruneTags argument is true.
1903
1956
  if (pruneTags) {
1904
1957
  const tags = await GitRefManager.listRefs({
@@ -1926,18 +1979,12 @@ class GitRefManager {
1926
1979
  }
1927
1980
  }
1928
1981
  // Combine refs and symrefs giving symrefs priority
1929
- const refTranslations = refspec.translate([...refs.keys()]);
1930
1982
  for (const [serverRef, translatedRef] of refTranslations) {
1931
1983
  const value = refs.get(serverRef);
1932
1984
  actualRefsToWrite.set(translatedRef, value);
1933
1985
  }
1934
- const symrefTranslations = refspec.translate([...symrefs.keys()]);
1935
- for (const [serverRef, translatedRef] of symrefTranslations) {
1936
- const value = symrefs.get(serverRef);
1937
- const symtarget = refspec.translateOne(value);
1938
- if (symtarget) {
1939
- actualRefsToWrite.set(translatedRef, `ref: ${symtarget}`);
1940
- }
1986
+ for (const [translatedRef, value] of symrefTranslations) {
1987
+ actualRefsToWrite.set(translatedRef, value);
1941
1988
  }
1942
1989
  // If `prune` argument is true, clear out the existing local refspec roots
1943
1990
  const pruned = [];
@@ -1996,6 +2043,7 @@ class GitRefManager {
1996
2043
  // TODO: make this less crude?
1997
2044
  static async writeRef({ fs, gitdir, ref, value }) {
1998
2045
  // Validate input
2046
+ assertWritableRef(ref);
1999
2047
  if (!value.match(/[0-9a-f]{40}/)) {
2000
2048
  throw new InvalidOidError(value)
2001
2049
  }
@@ -2015,6 +2063,7 @@ class GitRefManager {
2015
2063
  * @returns {Promise<void>}
2016
2064
  */
2017
2065
  static async writeSymbolicRef({ fs, gitdir, ref, value }) {
2066
+ assertWritableRef(ref);
2018
2067
  await acquireLock(ref, async () =>
2019
2068
  fs.write(join(gitdir, ref), 'ref: ' + `${value.trim()}\n`, 'utf8')
2020
2069
  );
@@ -2043,6 +2092,8 @@ class GitRefManager {
2043
2092
  * @returns {Promise<void>}
2044
2093
  */
2045
2094
  static async deleteRefs({ fs, gitdir, refs }) {
2095
+ // Validate input
2096
+ refs.forEach(assertWritableRef);
2046
2097
  // Delete regular ref
2047
2098
  await Promise.all(refs.map(ref => fs.rm(join(gitdir, ref))));
2048
2099
  // Delete any packed ref
@@ -2166,7 +2217,7 @@ class GitRefManager {
2166
2217
  // We need to alternate between the file system and the packed-refs
2167
2218
  const packedMap = await GitRefManager.packedRefs({ fs, gitdir });
2168
2219
  // Look in all the proper paths, in this order
2169
- const allpaths = refpaths(ref);
2220
+ const allpaths = refpaths(ref).filter(p => !GIT_FILES.includes(p)); // exclude git system files (#709)
2170
2221
  for (const ref of allpaths) {
2171
2222
  const refExists = await acquireLock(ref, async () =>
2172
2223
  fs.exists(`${gitdir}/${ref}`)
@@ -4517,23 +4568,6 @@ async function _writeCommit({ fs, gitdir, commit }) {
4517
4568
  return oid
4518
4569
  }
4519
4570
 
4520
- class InvalidRefNameError extends BaseError {
4521
- /**
4522
- * @param {string} ref
4523
- * @param {string} suggestion
4524
- * @param {boolean} canForce
4525
- */
4526
- constructor(ref, suggestion) {
4527
- super(
4528
- `"${ref}" would be an invalid git reference. (Hint: a valid alternative would be "${suggestion}".)`
4529
- );
4530
- this.code = this.name = InvalidRefNameError.code;
4531
- this.data = { ref, suggestion };
4532
- }
4533
- }
4534
- /** @type {'InvalidRefNameError'} */
4535
- InvalidRefNameError.code = 'InvalidRefNameError';
4536
-
4537
4571
  class MissingNameError extends BaseError {
4538
4572
  /**
4539
4573
  * @param {'author'|'committer'|'tagger'} role
@@ -103,6 +103,10 @@ export type CommitObject = {
103
103
  * an array of zero or more SHA-1 object ids
104
104
  */
105
105
  parent: string[];
106
+ /**
107
+ * Changed files as `[newOid, oldOid, filepath]`; present when `log` is called with `includeChanges: true`.
108
+ */
109
+ changes?: (string | null)[][] | undefined;
106
110
  author: {
107
111
  name: string;
108
112
  email: string;
@@ -102,6 +102,10 @@ export type CommitObject = {
102
102
  * an array of zero or more SHA-1 object ids
103
103
  */
104
104
  parent: string[];
105
+ /**
106
+ * Changed files as `[newOid, oldOid, filepath]`; present when `log` is called with `includeChanges: true`.
107
+ */
108
+ changes?: (string | null)[][] | undefined;
105
109
  author: {
106
110
  name: string;
107
111
  email: string;
package/managers/index.js CHANGED
@@ -56,6 +56,7 @@ import pako from 'pako';
56
56
  * @property {string} message Commit message
57
57
  * @property {string} tree SHA-1 object id of corresponding file tree
58
58
  * @property {string[]} parent an array of zero or more SHA-1 object ids
59
+ * @property {Array.<Array.<string|null>>} [changes] Changed files as `[newOid, oldOid, filepath]`; present when `log` is called with `includeChanges: true`.
59
60
  * @property {Object} author
60
61
  * @property {string} author.name The author's name
61
62
  * @property {string} author.email The author's email
@@ -1636,6 +1637,23 @@ class InvalidOidError extends BaseError {
1636
1637
  /** @type {'InvalidOidError'} */
1637
1638
  InvalidOidError.code = 'InvalidOidError';
1638
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
+
1639
1657
  class NoRefspecError extends BaseError {
1640
1658
  /**
1641
1659
  * @param {string} remote
@@ -1838,6 +1856,16 @@ const refpaths = ref => [
1838
1856
  // @see https://git-scm.com/docs/gitrepository-layout
1839
1857
  const GIT_FILES = ['config', 'description', 'index', 'shallow', 'commondir'];
1840
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
+
1841
1869
  let lock$1;
1842
1870
 
1843
1871
  async function acquireLock(ref, callback) {
@@ -1892,6 +1920,31 @@ class GitRefManager {
1892
1920
  }
1893
1921
  const refspec = GitRefSpecSet.from(refspecs);
1894
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
+ }
1895
1948
  // Delete all current tags if the pruneTags argument is true.
1896
1949
  if (pruneTags) {
1897
1950
  const tags = await GitRefManager.listRefs({
@@ -1919,18 +1972,12 @@ class GitRefManager {
1919
1972
  }
1920
1973
  }
1921
1974
  // Combine refs and symrefs giving symrefs priority
1922
- const refTranslations = refspec.translate([...refs.keys()]);
1923
1975
  for (const [serverRef, translatedRef] of refTranslations) {
1924
1976
  const value = refs.get(serverRef);
1925
1977
  actualRefsToWrite.set(translatedRef, value);
1926
1978
  }
1927
- const symrefTranslations = refspec.translate([...symrefs.keys()]);
1928
- for (const [serverRef, translatedRef] of symrefTranslations) {
1929
- const value = symrefs.get(serverRef);
1930
- const symtarget = refspec.translateOne(value);
1931
- if (symtarget) {
1932
- actualRefsToWrite.set(translatedRef, `ref: ${symtarget}`);
1933
- }
1979
+ for (const [translatedRef, value] of symrefTranslations) {
1980
+ actualRefsToWrite.set(translatedRef, value);
1934
1981
  }
1935
1982
  // If `prune` argument is true, clear out the existing local refspec roots
1936
1983
  const pruned = [];
@@ -1989,6 +2036,7 @@ class GitRefManager {
1989
2036
  // TODO: make this less crude?
1990
2037
  static async writeRef({ fs, gitdir, ref, value }) {
1991
2038
  // Validate input
2039
+ assertWritableRef(ref);
1992
2040
  if (!value.match(/[0-9a-f]{40}/)) {
1993
2041
  throw new InvalidOidError(value)
1994
2042
  }
@@ -2008,6 +2056,7 @@ class GitRefManager {
2008
2056
  * @returns {Promise<void>}
2009
2057
  */
2010
2058
  static async writeSymbolicRef({ fs, gitdir, ref, value }) {
2059
+ assertWritableRef(ref);
2011
2060
  await acquireLock(ref, async () =>
2012
2061
  fs.write(join(gitdir, ref), 'ref: ' + `${value.trim()}\n`, 'utf8')
2013
2062
  );
@@ -2036,6 +2085,8 @@ class GitRefManager {
2036
2085
  * @returns {Promise<void>}
2037
2086
  */
2038
2087
  static async deleteRefs({ fs, gitdir, refs }) {
2088
+ // Validate input
2089
+ refs.forEach(assertWritableRef);
2039
2090
  // Delete regular ref
2040
2091
  await Promise.all(refs.map(ref => fs.rm(join(gitdir, ref))));
2041
2092
  // Delete any packed ref
@@ -2159,7 +2210,7 @@ class GitRefManager {
2159
2210
  // We need to alternate between the file system and the packed-refs
2160
2211
  const packedMap = await GitRefManager.packedRefs({ fs, gitdir });
2161
2212
  // Look in all the proper paths, in this order
2162
- const allpaths = refpaths(ref);
2213
+ const allpaths = refpaths(ref).filter(p => !GIT_FILES.includes(p)); // exclude git system files (#709)
2163
2214
  for (const ref of allpaths) {
2164
2215
  const refExists = await acquireLock(ref, async () =>
2165
2216
  fs.exists(`${gitdir}/${ref}`)
@@ -4504,23 +4555,6 @@ async function _writeCommit({ fs, gitdir, commit }) {
4504
4555
  return oid
4505
4556
  }
4506
4557
 
4507
- class InvalidRefNameError extends BaseError {
4508
- /**
4509
- * @param {string} ref
4510
- * @param {string} suggestion
4511
- * @param {boolean} canForce
4512
- */
4513
- constructor(ref, suggestion) {
4514
- super(
4515
- `"${ref}" would be an invalid git reference. (Hint: a valid alternative would be "${suggestion}".)`
4516
- );
4517
- this.code = this.name = InvalidRefNameError.code;
4518
- this.data = { ref, suggestion };
4519
- }
4520
- }
4521
- /** @type {'InvalidRefNameError'} */
4522
- InvalidRefNameError.code = 'InvalidRefNameError';
4523
-
4524
4558
  class MissingNameError extends BaseError {
4525
4559
  /**
4526
4560
  * @param {'author'|'committer'|'tagger'} role