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.
package/index.cjs CHANGED
@@ -66,6 +66,7 @@ var diff3Merge = _interopDefault(require('diff3'));
66
66
  * @property {string} message Commit message
67
67
  * @property {string} tree SHA-1 object id of corresponding file tree
68
68
  * @property {string[]} parent an array of zero or more SHA-1 object ids
69
+ * @property {Array.<Array.<string|null>>} [changes] Changed files as `[newOid, oldOid, filepath]`; present when `log` is called with `includeChanges: true`.
69
70
  * @property {Object} author
70
71
  * @property {string} author.name The author's name
71
72
  * @property {string} author.email The author's email
@@ -1331,6 +1332,23 @@ class InvalidOidError extends BaseError {
1331
1332
  /** @type {'InvalidOidError'} */
1332
1333
  InvalidOidError.code = 'InvalidOidError';
1333
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
+
1334
1352
  class NoRefspecError extends BaseError {
1335
1353
  /**
1336
1354
  * @param {string} remote
@@ -2002,6 +2020,16 @@ const refpaths = ref => [
2002
2020
  // @see https://git-scm.com/docs/gitrepository-layout
2003
2021
  const GIT_FILES = ['config', 'description', 'index', 'shallow', 'commondir'];
2004
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
+
2005
2033
  let lock$1;
2006
2034
 
2007
2035
  async function acquireLock(ref, callback) {
@@ -2056,6 +2084,31 @@ class GitRefManager {
2056
2084
  }
2057
2085
  const refspec = GitRefSpecSet.from(refspecs);
2058
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
+ }
2059
2112
  // Delete all current tags if the pruneTags argument is true.
2060
2113
  if (pruneTags) {
2061
2114
  const tags = await GitRefManager.listRefs({
@@ -2083,18 +2136,12 @@ class GitRefManager {
2083
2136
  }
2084
2137
  }
2085
2138
  // Combine refs and symrefs giving symrefs priority
2086
- const refTranslations = refspec.translate([...refs.keys()]);
2087
2139
  for (const [serverRef, translatedRef] of refTranslations) {
2088
2140
  const value = refs.get(serverRef);
2089
2141
  actualRefsToWrite.set(translatedRef, value);
2090
2142
  }
2091
- const symrefTranslations = refspec.translate([...symrefs.keys()]);
2092
- for (const [serverRef, translatedRef] of symrefTranslations) {
2093
- const value = symrefs.get(serverRef);
2094
- const symtarget = refspec.translateOne(value);
2095
- if (symtarget) {
2096
- actualRefsToWrite.set(translatedRef, `ref: ${symtarget}`);
2097
- }
2143
+ for (const [translatedRef, value] of symrefTranslations) {
2144
+ actualRefsToWrite.set(translatedRef, value);
2098
2145
  }
2099
2146
  // If `prune` argument is true, clear out the existing local refspec roots
2100
2147
  const pruned = [];
@@ -2153,6 +2200,7 @@ class GitRefManager {
2153
2200
  // TODO: make this less crude?
2154
2201
  static async writeRef({ fs, gitdir, ref, value }) {
2155
2202
  // Validate input
2203
+ assertWritableRef(ref);
2156
2204
  if (!value.match(/[0-9a-f]{40}/)) {
2157
2205
  throw new InvalidOidError(value)
2158
2206
  }
@@ -2172,6 +2220,7 @@ class GitRefManager {
2172
2220
  * @returns {Promise<void>}
2173
2221
  */
2174
2222
  static async writeSymbolicRef({ fs, gitdir, ref, value }) {
2223
+ assertWritableRef(ref);
2175
2224
  await acquireLock(ref, async () =>
2176
2225
  fs.write(join(gitdir, ref), 'ref: ' + `${value.trim()}\n`, 'utf8')
2177
2226
  );
@@ -2200,6 +2249,8 @@ class GitRefManager {
2200
2249
  * @returns {Promise<void>}
2201
2250
  */
2202
2251
  static async deleteRefs({ fs, gitdir, refs }) {
2252
+ // Validate input
2253
+ refs.forEach(assertWritableRef);
2203
2254
  // Delete regular ref
2204
2255
  await Promise.all(refs.map(ref => fs.rm(join(gitdir, ref))));
2205
2256
  // Delete any packed ref
@@ -2323,7 +2374,7 @@ class GitRefManager {
2323
2374
  // We need to alternate between the file system and the packed-refs
2324
2375
  const packedMap = await GitRefManager.packedRefs({ fs, gitdir });
2325
2376
  // Look in all the proper paths, in this order
2326
- const allpaths = refpaths(ref);
2377
+ const allpaths = refpaths(ref).filter(p => !GIT_FILES.includes(p)); // exclude git system files (#709)
2327
2378
  for (const ref of allpaths) {
2328
2379
  const refExists = await acquireLock(ref, async () =>
2329
2380
  fs.exists(`${gitdir}/${ref}`)
@@ -3823,23 +3874,6 @@ class InvalidFilepathError extends BaseError {
3823
3874
  /** @type {'InvalidFilepathError'} */
3824
3875
  InvalidFilepathError.code = 'InvalidFilepathError';
3825
3876
 
3826
- class InvalidRefNameError extends BaseError {
3827
- /**
3828
- * @param {string} ref
3829
- * @param {string} suggestion
3830
- * @param {boolean} canForce
3831
- */
3832
- constructor(ref, suggestion) {
3833
- super(
3834
- `"${ref}" would be an invalid git reference. (Hint: a valid alternative would be "${suggestion}".)`
3835
- );
3836
- this.code = this.name = InvalidRefNameError.code;
3837
- this.data = { ref, suggestion };
3838
- }
3839
- }
3840
- /** @type {'InvalidRefNameError'} */
3841
- InvalidRefNameError.code = 'InvalidRefNameError';
3842
-
3843
3877
  class MaxDepthError extends BaseError {
3844
3878
  /**
3845
3879
  * @param {number} depth
@@ -9523,8 +9557,8 @@ function filterCapabilities(server, client) {
9523
9557
 
9524
9558
  const pkg = {
9525
9559
  name: 'isomorphic-git',
9526
- version: '1.39.0',
9527
- agent: 'git/isomorphic-git@1.39.0',
9560
+ version: '1.40.1',
9561
+ agent: 'git/isomorphic-git@1.40.1',
9528
9562
  };
9529
9563
 
9530
9564
  class FIFO {
@@ -13097,7 +13131,7 @@ async function _resolveFileId({
13097
13131
  * @param {number|void} args.depth
13098
13132
  * @param {boolean=} [args.force=false] do not throw error if filepath is not exist (works only for a single file). defaults to false
13099
13133
  * @param {boolean=} [args.follow=false] Continue listing the history of a file beyond renames (works only for a single file). defaults to false
13100
- * @param {boolean=} args.follow Continue listing the history of a file beyond renames (works only for a single file). defaults to false
13134
+ * @param {boolean=} [args.includeChanges=false] Include changed file object ids for each commit.
13101
13135
  *
13102
13136
  * @returns {Promise<Array<ReadCommitResult>>} Resolves to an array of ReadCommitResult objects
13103
13137
  * @see ReadCommitResult
@@ -13118,6 +13152,7 @@ async function _log({
13118
13152
  since,
13119
13153
  force,
13120
13154
  follow,
13155
+ includeChanges,
13121
13156
  }) {
13122
13157
  const sinceTimestamp =
13123
13158
  typeof since === 'undefined'
@@ -13245,9 +13280,57 @@ async function _log({
13245
13280
  // Process tips in order by age
13246
13281
  tips.sort((a, b) => compareAge(a.commit, b.commit));
13247
13282
  }
13283
+ if (includeChanges) {
13284
+ for (const commit of commits) {
13285
+ commit.commit.changes = await getChanges({
13286
+ fs,
13287
+ cache,
13288
+ gitdir,
13289
+ commit,
13290
+ shallow: shallowCommits.has(commit.oid),
13291
+ });
13292
+ }
13293
+ }
13248
13294
  return commits
13249
13295
  }
13250
13296
 
13297
+ async function getChanges({ fs, cache, gitdir, commit, shallow }) {
13298
+ const parent =
13299
+ shallow || !commit.commit.parent[0]
13300
+ ? '4b825dc642cb6eb9a060e54bf8d69288fbee4904'
13301
+ : commit.commit.parent[0];
13302
+ return _walk({
13303
+ fs,
13304
+ cache,
13305
+ gitdir,
13306
+ trees: [TREE({ ref: commit.oid }), TREE({ ref: parent })],
13307
+ map: async (filepath, [current, previous]) => {
13308
+ const [currentType, previousType] = await Promise.all([
13309
+ current && current.type(),
13310
+ previous && previous.type(),
13311
+ ]);
13312
+ if (currentType === 'tree') {
13313
+ if (previousType && previousType !== 'tree') {
13314
+ return [null, await previous.oid(), filepath]
13315
+ }
13316
+ return
13317
+ }
13318
+ if (previousType === 'tree') {
13319
+ if (currentType) {
13320
+ return [await current.oid(), null, filepath]
13321
+ }
13322
+ return
13323
+ }
13324
+ const [newOid, oldOid] = await Promise.all([
13325
+ current ? current.oid() : null,
13326
+ previous ? previous.oid() : null,
13327
+ ]);
13328
+ if (newOid === oldOid) return
13329
+ return [newOid, oldOid, filepath]
13330
+ },
13331
+ })
13332
+ }
13333
+
13251
13334
  // @ts-check
13252
13335
 
13253
13336
  /**
@@ -13263,6 +13346,7 @@ async function _log({
13263
13346
  * @param {Date} [args.since] - Return history newer than the given date. Can be combined with `depth` to get whichever is shorter.
13264
13347
  * @param {boolean=} [args.force=false] do not throw error if filepath is not exist (works only for a single file). defaults to false
13265
13348
  * @param {boolean=} [args.follow=false] Continue listing the history of a file beyond renames (works only for a single file). defaults to false
13349
+ * @param {boolean=} [args.includeChanges=false] Include changed file object ids for each commit. Each tuple is `[newOid, oldOid, filepath]`; an added or removed file has a null old or new oid, respectively.
13266
13350
  * @param {object} [args.cache] - a [cache](cache.md) object
13267
13351
  *
13268
13352
  * @returns {Promise<Array<ReadCommitResult>>} Resolves to an array of ReadCommitResult objects
@@ -13289,6 +13373,7 @@ async function log({
13289
13373
  since, // Date
13290
13374
  force,
13291
13375
  follow,
13376
+ includeChanges = false,
13292
13377
  cache = {},
13293
13378
  }) {
13294
13379
  try {
@@ -13308,6 +13393,7 @@ async function log({
13308
13393
  since,
13309
13394
  force,
13310
13395
  follow,
13396
+ includeChanges,
13311
13397
  })
13312
13398
  } catch (err) {
13313
13399
  err.caller = 'git.log';
package/index.d.cts CHANGED
@@ -354,6 +354,10 @@ export type CommitObject = {
354
354
  * an array of zero or more SHA-1 object ids
355
355
  */
356
356
  parent: string[];
357
+ /**
358
+ * Changed files as `[newOid, oldOid, filepath]`; present when `log` is called with `includeChanges: true`.
359
+ */
360
+ changes?: (string | null)[][] | undefined;
357
361
  author: {
358
362
  name: string;
359
363
  email: string;
@@ -2181,6 +2185,7 @@ export function listTags({ fs, dir, gitdir }: {
2181
2185
  * @param {Date} [args.since] - Return history newer than the given date. Can be combined with `depth` to get whichever is shorter.
2182
2186
  * @param {boolean=} [args.force=false] do not throw error if filepath is not exist (works only for a single file). defaults to false
2183
2187
  * @param {boolean=} [args.follow=false] Continue listing the history of a file beyond renames (works only for a single file). defaults to false
2188
+ * @param {boolean=} [args.includeChanges=false] Include changed file object ids for each commit. Each tuple is `[newOid, oldOid, filepath]`; an added or removed file has a null old or new oid, respectively.
2184
2189
  * @param {object} [args.cache] - a [cache](cache.md) object
2185
2190
  *
2186
2191
  * @returns {Promise<Array<ReadCommitResult>>} Resolves to an array of ReadCommitResult objects
@@ -2197,7 +2202,7 @@ export function listTags({ fs, dir, gitdir }: {
2197
2202
  * console.log(commits)
2198
2203
  *
2199
2204
  */
2200
- export function log({ fs, dir, gitdir, filepath, ref, depth, since, force, follow, cache, }: {
2205
+ export function log({ fs, dir, gitdir, filepath, ref, depth, since, force, follow, includeChanges, cache, }: {
2201
2206
  fs: FsClient;
2202
2207
  dir?: string | undefined;
2203
2208
  gitdir?: string | undefined;
@@ -2207,6 +2212,7 @@ export function log({ fs, dir, gitdir, filepath, ref, depth, since, force, follo
2207
2212
  since?: Date | undefined;
2208
2213
  force?: boolean | undefined;
2209
2214
  follow?: boolean | undefined;
2215
+ includeChanges?: boolean | undefined;
2210
2216
  cache?: object;
2211
2217
  }): Promise<Array<ReadCommitResult>>;
2212
2218
  /**
@@ -4521,6 +4527,7 @@ declare namespace NoCommitError {
4521
4527
  * @property {string} message Commit message
4522
4528
  * @property {string} tree SHA-1 object id of corresponding file tree
4523
4529
  * @property {string[]} parent an array of zero or more SHA-1 object ids
4530
+ * @property {Array.<Array.<string|null>>} [changes] Changed files as `[newOid, oldOid, filepath]`; present when `log` is called with `includeChanges: true`.
4524
4531
  * @property {Object} author
4525
4532
  * @property {string} author.name The author's name
4526
4533
  * @property {string} author.email The author's email
package/index.d.ts CHANGED
@@ -353,6 +353,10 @@ export type CommitObject = {
353
353
  * an array of zero or more SHA-1 object ids
354
354
  */
355
355
  parent: string[];
356
+ /**
357
+ * Changed files as `[newOid, oldOid, filepath]`; present when `log` is called with `includeChanges: true`.
358
+ */
359
+ changes?: (string | null)[][] | undefined;
356
360
  author: {
357
361
  name: string;
358
362
  email: string;
@@ -2180,6 +2184,7 @@ export function listTags({ fs, dir, gitdir }: {
2180
2184
  * @param {Date} [args.since] - Return history newer than the given date. Can be combined with `depth` to get whichever is shorter.
2181
2185
  * @param {boolean=} [args.force=false] do not throw error if filepath is not exist (works only for a single file). defaults to false
2182
2186
  * @param {boolean=} [args.follow=false] Continue listing the history of a file beyond renames (works only for a single file). defaults to false
2187
+ * @param {boolean=} [args.includeChanges=false] Include changed file object ids for each commit. Each tuple is `[newOid, oldOid, filepath]`; an added or removed file has a null old or new oid, respectively.
2183
2188
  * @param {object} [args.cache] - a [cache](cache.md) object
2184
2189
  *
2185
2190
  * @returns {Promise<Array<ReadCommitResult>>} Resolves to an array of ReadCommitResult objects
@@ -2196,7 +2201,7 @@ export function listTags({ fs, dir, gitdir }: {
2196
2201
  * console.log(commits)
2197
2202
  *
2198
2203
  */
2199
- export function log({ fs, dir, gitdir, filepath, ref, depth, since, force, follow, cache, }: {
2204
+ export function log({ fs, dir, gitdir, filepath, ref, depth, since, force, follow, includeChanges, cache, }: {
2200
2205
  fs: FsClient;
2201
2206
  dir?: string | undefined;
2202
2207
  gitdir?: string | undefined;
@@ -2206,6 +2211,7 @@ export function log({ fs, dir, gitdir, filepath, ref, depth, since, force, follo
2206
2211
  since?: Date | undefined;
2207
2212
  force?: boolean | undefined;
2208
2213
  follow?: boolean | undefined;
2214
+ includeChanges?: boolean | undefined;
2209
2215
  cache?: object;
2210
2216
  }): Promise<Array<ReadCommitResult>>;
2211
2217
  /**
@@ -4520,6 +4526,7 @@ declare namespace NoCommitError {
4520
4526
  * @property {string} message Commit message
4521
4527
  * @property {string} tree SHA-1 object id of corresponding file tree
4522
4528
  * @property {string[]} parent an array of zero or more SHA-1 object ids
4529
+ * @property {Array.<Array.<string|null>>} [changes] Changed files as `[newOid, oldOid, filepath]`; present when `log` is called with `includeChanges: true`.
4523
4530
  * @property {Object} author
4524
4531
  * @property {string} author.name The author's name
4525
4532
  * @property {string} author.email The author's email
package/index.js CHANGED
@@ -59,6 +59,7 @@ import diff3Merge from 'diff3';
59
59
  * @property {string} message Commit message
60
60
  * @property {string} tree SHA-1 object id of corresponding file tree
61
61
  * @property {string[]} parent an array of zero or more SHA-1 object ids
62
+ * @property {Array.<Array.<string|null>>} [changes] Changed files as `[newOid, oldOid, filepath]`; present when `log` is called with `includeChanges: true`.
62
63
  * @property {Object} author
63
64
  * @property {string} author.name The author's name
64
65
  * @property {string} author.email The author's email
@@ -1324,6 +1325,23 @@ class InvalidOidError extends BaseError {
1324
1325
  /** @type {'InvalidOidError'} */
1325
1326
  InvalidOidError.code = 'InvalidOidError';
1326
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
+
1327
1345
  class NoRefspecError extends BaseError {
1328
1346
  /**
1329
1347
  * @param {string} remote
@@ -1995,6 +2013,16 @@ const refpaths = ref => [
1995
2013
  // @see https://git-scm.com/docs/gitrepository-layout
1996
2014
  const GIT_FILES = ['config', 'description', 'index', 'shallow', 'commondir'];
1997
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
+
1998
2026
  let lock$1;
1999
2027
 
2000
2028
  async function acquireLock(ref, callback) {
@@ -2049,6 +2077,31 @@ class GitRefManager {
2049
2077
  }
2050
2078
  const refspec = GitRefSpecSet.from(refspecs);
2051
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
+ }
2052
2105
  // Delete all current tags if the pruneTags argument is true.
2053
2106
  if (pruneTags) {
2054
2107
  const tags = await GitRefManager.listRefs({
@@ -2076,18 +2129,12 @@ class GitRefManager {
2076
2129
  }
2077
2130
  }
2078
2131
  // Combine refs and symrefs giving symrefs priority
2079
- const refTranslations = refspec.translate([...refs.keys()]);
2080
2132
  for (const [serverRef, translatedRef] of refTranslations) {
2081
2133
  const value = refs.get(serverRef);
2082
2134
  actualRefsToWrite.set(translatedRef, value);
2083
2135
  }
2084
- const symrefTranslations = refspec.translate([...symrefs.keys()]);
2085
- for (const [serverRef, translatedRef] of symrefTranslations) {
2086
- const value = symrefs.get(serverRef);
2087
- const symtarget = refspec.translateOne(value);
2088
- if (symtarget) {
2089
- actualRefsToWrite.set(translatedRef, `ref: ${symtarget}`);
2090
- }
2136
+ for (const [translatedRef, value] of symrefTranslations) {
2137
+ actualRefsToWrite.set(translatedRef, value);
2091
2138
  }
2092
2139
  // If `prune` argument is true, clear out the existing local refspec roots
2093
2140
  const pruned = [];
@@ -2146,6 +2193,7 @@ class GitRefManager {
2146
2193
  // TODO: make this less crude?
2147
2194
  static async writeRef({ fs, gitdir, ref, value }) {
2148
2195
  // Validate input
2196
+ assertWritableRef(ref);
2149
2197
  if (!value.match(/[0-9a-f]{40}/)) {
2150
2198
  throw new InvalidOidError(value)
2151
2199
  }
@@ -2165,6 +2213,7 @@ class GitRefManager {
2165
2213
  * @returns {Promise<void>}
2166
2214
  */
2167
2215
  static async writeSymbolicRef({ fs, gitdir, ref, value }) {
2216
+ assertWritableRef(ref);
2168
2217
  await acquireLock(ref, async () =>
2169
2218
  fs.write(join(gitdir, ref), 'ref: ' + `${value.trim()}\n`, 'utf8')
2170
2219
  );
@@ -2193,6 +2242,8 @@ class GitRefManager {
2193
2242
  * @returns {Promise<void>}
2194
2243
  */
2195
2244
  static async deleteRefs({ fs, gitdir, refs }) {
2245
+ // Validate input
2246
+ refs.forEach(assertWritableRef);
2196
2247
  // Delete regular ref
2197
2248
  await Promise.all(refs.map(ref => fs.rm(join(gitdir, ref))));
2198
2249
  // Delete any packed ref
@@ -2316,7 +2367,7 @@ class GitRefManager {
2316
2367
  // We need to alternate between the file system and the packed-refs
2317
2368
  const packedMap = await GitRefManager.packedRefs({ fs, gitdir });
2318
2369
  // Look in all the proper paths, in this order
2319
- const allpaths = refpaths(ref);
2370
+ const allpaths = refpaths(ref).filter(p => !GIT_FILES.includes(p)); // exclude git system files (#709)
2320
2371
  for (const ref of allpaths) {
2321
2372
  const refExists = await acquireLock(ref, async () =>
2322
2373
  fs.exists(`${gitdir}/${ref}`)
@@ -3810,23 +3861,6 @@ class InvalidFilepathError extends BaseError {
3810
3861
  /** @type {'InvalidFilepathError'} */
3811
3862
  InvalidFilepathError.code = 'InvalidFilepathError';
3812
3863
 
3813
- class InvalidRefNameError extends BaseError {
3814
- /**
3815
- * @param {string} ref
3816
- * @param {string} suggestion
3817
- * @param {boolean} canForce
3818
- */
3819
- constructor(ref, suggestion) {
3820
- super(
3821
- `"${ref}" would be an invalid git reference. (Hint: a valid alternative would be "${suggestion}".)`
3822
- );
3823
- this.code = this.name = InvalidRefNameError.code;
3824
- this.data = { ref, suggestion };
3825
- }
3826
- }
3827
- /** @type {'InvalidRefNameError'} */
3828
- InvalidRefNameError.code = 'InvalidRefNameError';
3829
-
3830
3864
  class MaxDepthError extends BaseError {
3831
3865
  /**
3832
3866
  * @param {number} depth
@@ -9510,8 +9544,8 @@ function filterCapabilities(server, client) {
9510
9544
 
9511
9545
  const pkg = {
9512
9546
  name: 'isomorphic-git',
9513
- version: '1.39.0',
9514
- agent: 'git/isomorphic-git@1.39.0',
9547
+ version: '1.40.1',
9548
+ agent: 'git/isomorphic-git@1.40.1',
9515
9549
  };
9516
9550
 
9517
9551
  class FIFO {
@@ -13084,7 +13118,7 @@ async function _resolveFileId({
13084
13118
  * @param {number|void} args.depth
13085
13119
  * @param {boolean=} [args.force=false] do not throw error if filepath is not exist (works only for a single file). defaults to false
13086
13120
  * @param {boolean=} [args.follow=false] Continue listing the history of a file beyond renames (works only for a single file). defaults to false
13087
- * @param {boolean=} args.follow Continue listing the history of a file beyond renames (works only for a single file). defaults to false
13121
+ * @param {boolean=} [args.includeChanges=false] Include changed file object ids for each commit.
13088
13122
  *
13089
13123
  * @returns {Promise<Array<ReadCommitResult>>} Resolves to an array of ReadCommitResult objects
13090
13124
  * @see ReadCommitResult
@@ -13105,6 +13139,7 @@ async function _log({
13105
13139
  since,
13106
13140
  force,
13107
13141
  follow,
13142
+ includeChanges,
13108
13143
  }) {
13109
13144
  const sinceTimestamp =
13110
13145
  typeof since === 'undefined'
@@ -13232,9 +13267,57 @@ async function _log({
13232
13267
  // Process tips in order by age
13233
13268
  tips.sort((a, b) => compareAge(a.commit, b.commit));
13234
13269
  }
13270
+ if (includeChanges) {
13271
+ for (const commit of commits) {
13272
+ commit.commit.changes = await getChanges({
13273
+ fs,
13274
+ cache,
13275
+ gitdir,
13276
+ commit,
13277
+ shallow: shallowCommits.has(commit.oid),
13278
+ });
13279
+ }
13280
+ }
13235
13281
  return commits
13236
13282
  }
13237
13283
 
13284
+ async function getChanges({ fs, cache, gitdir, commit, shallow }) {
13285
+ const parent =
13286
+ shallow || !commit.commit.parent[0]
13287
+ ? '4b825dc642cb6eb9a060e54bf8d69288fbee4904'
13288
+ : commit.commit.parent[0];
13289
+ return _walk({
13290
+ fs,
13291
+ cache,
13292
+ gitdir,
13293
+ trees: [TREE({ ref: commit.oid }), TREE({ ref: parent })],
13294
+ map: async (filepath, [current, previous]) => {
13295
+ const [currentType, previousType] = await Promise.all([
13296
+ current && current.type(),
13297
+ previous && previous.type(),
13298
+ ]);
13299
+ if (currentType === 'tree') {
13300
+ if (previousType && previousType !== 'tree') {
13301
+ return [null, await previous.oid(), filepath]
13302
+ }
13303
+ return
13304
+ }
13305
+ if (previousType === 'tree') {
13306
+ if (currentType) {
13307
+ return [await current.oid(), null, filepath]
13308
+ }
13309
+ return
13310
+ }
13311
+ const [newOid, oldOid] = await Promise.all([
13312
+ current ? current.oid() : null,
13313
+ previous ? previous.oid() : null,
13314
+ ]);
13315
+ if (newOid === oldOid) return
13316
+ return [newOid, oldOid, filepath]
13317
+ },
13318
+ })
13319
+ }
13320
+
13238
13321
  // @ts-check
13239
13322
 
13240
13323
  /**
@@ -13250,6 +13333,7 @@ async function _log({
13250
13333
  * @param {Date} [args.since] - Return history newer than the given date. Can be combined with `depth` to get whichever is shorter.
13251
13334
  * @param {boolean=} [args.force=false] do not throw error if filepath is not exist (works only for a single file). defaults to false
13252
13335
  * @param {boolean=} [args.follow=false] Continue listing the history of a file beyond renames (works only for a single file). defaults to false
13336
+ * @param {boolean=} [args.includeChanges=false] Include changed file object ids for each commit. Each tuple is `[newOid, oldOid, filepath]`; an added or removed file has a null old or new oid, respectively.
13253
13337
  * @param {object} [args.cache] - a [cache](cache.md) object
13254
13338
  *
13255
13339
  * @returns {Promise<Array<ReadCommitResult>>} Resolves to an array of ReadCommitResult objects
@@ -13276,6 +13360,7 @@ async function log({
13276
13360
  since, // Date
13277
13361
  force,
13278
13362
  follow,
13363
+ includeChanges = false,
13279
13364
  cache = {},
13280
13365
  }) {
13281
13366
  try {
@@ -13295,6 +13380,7 @@ async function log({
13295
13380
  since,
13296
13381
  force,
13297
13382
  follow,
13383
+ includeChanges,
13298
13384
  })
13299
13385
  } catch (err) {
13300
13386
  err.caller = 'git.log';
@@ -353,6 +353,10 @@ export type CommitObject = {
353
353
  * an array of zero or more SHA-1 object ids
354
354
  */
355
355
  parent: string[];
356
+ /**
357
+ * Changed files as `[newOid, oldOid, filepath]`; present when `log` is called with `includeChanges: true`.
358
+ */
359
+ changes?: (string | null)[][] | undefined;
356
360
  author: {
357
361
  name: string;
358
362
  email: string;
@@ -2180,6 +2184,7 @@ export function listTags({ fs, dir, gitdir }: {
2180
2184
  * @param {Date} [args.since] - Return history newer than the given date. Can be combined with `depth` to get whichever is shorter.
2181
2185
  * @param {boolean=} [args.force=false] do not throw error if filepath is not exist (works only for a single file). defaults to false
2182
2186
  * @param {boolean=} [args.follow=false] Continue listing the history of a file beyond renames (works only for a single file). defaults to false
2187
+ * @param {boolean=} [args.includeChanges=false] Include changed file object ids for each commit. Each tuple is `[newOid, oldOid, filepath]`; an added or removed file has a null old or new oid, respectively.
2183
2188
  * @param {object} [args.cache] - a [cache](cache.md) object
2184
2189
  *
2185
2190
  * @returns {Promise<Array<ReadCommitResult>>} Resolves to an array of ReadCommitResult objects
@@ -2196,7 +2201,7 @@ export function listTags({ fs, dir, gitdir }: {
2196
2201
  * console.log(commits)
2197
2202
  *
2198
2203
  */
2199
- export function log({ fs, dir, gitdir, filepath, ref, depth, since, force, follow, cache, }: {
2204
+ export function log({ fs, dir, gitdir, filepath, ref, depth, since, force, follow, includeChanges, cache, }: {
2200
2205
  fs: FsClient;
2201
2206
  dir?: string | undefined;
2202
2207
  gitdir?: string | undefined;
@@ -2206,6 +2211,7 @@ export function log({ fs, dir, gitdir, filepath, ref, depth, since, force, follo
2206
2211
  since?: Date | undefined;
2207
2212
  force?: boolean | undefined;
2208
2213
  follow?: boolean | undefined;
2214
+ includeChanges?: boolean | undefined;
2209
2215
  cache?: object;
2210
2216
  }): Promise<Array<ReadCommitResult>>;
2211
2217
  /**
@@ -4520,6 +4526,7 @@ declare namespace NoCommitError {
4520
4526
  * @property {string} message Commit message
4521
4527
  * @property {string} tree SHA-1 object id of corresponding file tree
4522
4528
  * @property {string[]} parent an array of zero or more SHA-1 object ids
4529
+ * @property {Array.<Array.<string|null>>} [changes] Changed files as `[newOid, oldOid, filepath]`; present when `log` is called with `includeChanges: true`.
4523
4530
  * @property {Object} author
4524
4531
  * @property {string} author.name The author's name
4525
4532
  * @property {string} author.email The author's email