isomorphic-git 1.41.5 → 1.41.8

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/README.md CHANGED
@@ -109,7 +109,7 @@ import git from 'isomorphic-git'
109
109
  // or
110
110
  import * as git from 'isomorphic-git'
111
111
  // or
112
- import {plugins, clone, commit, push} from 'isomorphic-git'
112
+ import {add, clone, commit, push} from 'isomorphic-git'
113
113
  ```
114
114
 
115
115
  View the full [Getting Started guide](https://isomorphic-git.github.io/docs/quickstart.html) on the docs website.
package/index.cjs CHANGED
@@ -2365,6 +2365,38 @@ class GitRefManager {
2365
2365
  throw new NotFoundError(ref)
2366
2366
  }
2367
2367
 
2368
+ /**
2369
+ * Checks whether a ref names the branch HEAD points at in a repository where
2370
+ * that branch has no commits yet.
2371
+ *
2372
+ * A repository created by `git init` has a HEAD pointing at a branch that
2373
+ * does not exist on disk. Resolving that branch fails the same way resolving
2374
+ * a misspelled ref does, so callers that want to treat the two differently
2375
+ * need this to tell them apart.
2376
+ *
2377
+ * @param {Object} args
2378
+ * @param {FSClient} args.fs - A file system implementation.
2379
+ * @param {string} [args.gitdir=join(dir, '.git')] - [required] The [git directory](dir-vs-gitdir.md) path
2380
+ * @param {string} args.ref - The ref to check.
2381
+ * @returns {Promise<boolean>} - True if the ref names an unborn branch.
2382
+ */
2383
+ static async isUnbornBranch({ fs, gitdir, ref }) {
2384
+ let target;
2385
+ try {
2386
+ // depth 2 stops at the symbolic target instead of following it to an oid.
2387
+ target = await GitRefManager.resolve({
2388
+ fs,
2389
+ gitdir,
2390
+ ref: 'HEAD',
2391
+ depth: 2,
2392
+ });
2393
+ } catch (_) {
2394
+ return false
2395
+ }
2396
+ if (!target.startsWith('refs/heads/')) return false
2397
+ return ref === 'HEAD' || refpaths(ref).includes(target)
2398
+ }
2399
+
2368
2400
  /**
2369
2401
  * Checks if a ref exists.
2370
2402
  *
@@ -3651,6 +3683,10 @@ async function _readObject({
3651
3683
  oid,
3652
3684
  format = 'content',
3653
3685
  }) {
3686
+ if (!['deflated', 'wrapped', 'content'].includes(format)) {
3687
+ throw new InternalError(`invalid requested format "${format}"`)
3688
+ }
3689
+
3654
3690
  // Curry the current read method so that the packfile un-deltification
3655
3691
  // process can acquire external ref-deltas.
3656
3692
  const getExternalRefDelta = oid => _readObject({ fs, cache, gitdir, oid });
@@ -3710,11 +3746,7 @@ async function _readObject({
3710
3746
  result.object = object;
3711
3747
  result.format = 'content';
3712
3748
 
3713
- if (format === 'content') {
3714
- return result
3715
- }
3716
-
3717
- throw new InternalError(`invalid requested format "${format}"`)
3749
+ return result
3718
3750
  }
3719
3751
 
3720
3752
  class AlreadyExistsError extends BaseError {
@@ -4544,9 +4576,16 @@ class GitWalkerRepo {
4544
4576
  try {
4545
4577
  oid = await GitRefManager.resolve({ fs, gitdir, ref });
4546
4578
  } catch (e) {
4547
- if (e instanceof NotFoundError) {
4548
- // Handle fresh branches with no commits
4579
+ // Only a branch that has no commits yet gets the empty tree. Anything
4580
+ // else that fails to resolve is a ref the caller got wrong, and
4581
+ // swallowing it here would walk an empty tree instead of saying so.
4582
+ if (
4583
+ e instanceof NotFoundError &&
4584
+ (await GitRefManager.isUnbornBranch({ fs, gitdir, ref }))
4585
+ ) {
4549
4586
  oid = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
4587
+ } else {
4588
+ throw e
4550
4589
  }
4551
4590
  }
4552
4591
  const tree = await resolveTree({ fs, cache: this.cache, gitdir, oid });
@@ -9728,8 +9767,8 @@ function filterCapabilities(server, client) {
9728
9767
 
9729
9768
  const pkg = {
9730
9769
  name: 'isomorphic-git',
9731
- version: '1.41.5',
9732
- agent: 'git/isomorphic-git@1.41.5',
9770
+ version: '1.41.8',
9771
+ agent: 'git/isomorphic-git@1.41.8',
9733
9772
  };
9734
9773
 
9735
9774
  class FIFO {
@@ -11225,44 +11264,70 @@ async function _findMergeBase({ fs, cache, gitdir, oids }) {
11225
11264
  // and computing virtual merge bases is just too much for me to fathom right now.
11226
11265
 
11227
11266
  // If we start N independent walkers, one at each of the given `oids`, and walk backwards
11228
- // through ancestors, eventually we'll discover a commit where each one of these N walkers
11229
- // has passed through. So we just need to keep track of which walkers have visited each commit
11230
- // until we find a commit that N distinct walkers has visited.
11267
+ // through ancestors, eventually we'll discover commits where each one of these N walkers
11268
+ // has passed through. We cannot stop at the first common commit because a better common
11269
+ // ancestor may still be present in another branch of the graph.
11231
11270
  const visits = {};
11232
11271
  const passes = oids.length;
11272
+ const common = new Set();
11273
+ const parents = new Map();
11274
+ const shallows = await GitShallowManager.read({ fs, gitdir });
11275
+ const readParents = async oid => {
11276
+ if (parents.has(oid)) return parents.get(oid)
11277
+ const { object, type } = await _readObject({ fs, cache, gitdir, oid });
11278
+ if (type !== 'commit') {
11279
+ throw new ObjectTypeError(oid, type, 'commit')
11280
+ }
11281
+ const commit = GitCommit.from(object);
11282
+ const { parent } = commit.parseHeaders();
11283
+ const result = shallows.has(oid) ? [] : parent;
11284
+ parents.set(oid, result);
11285
+ return result
11286
+ };
11233
11287
  let heads = oids.map((oid, index) => ({ index, oid }));
11234
11288
  while (heads.length) {
11235
11289
  // Count how many times we've passed each commit
11236
- const result = new Set();
11237
11290
  for (const { oid, index } of heads) {
11291
+ await readParents(oid);
11238
11292
  if (!visits[oid]) visits[oid] = new Set();
11239
11293
  visits[oid].add(index);
11240
11294
  if (visits[oid].size === passes) {
11241
- result.add(oid);
11295
+ common.add(oid);
11242
11296
  }
11243
11297
  }
11244
- if (result.size > 0) {
11245
- return [...result]
11246
- }
11247
- // We haven't found a common ancestor yet
11248
11298
  const newheads = new Map();
11249
11299
  for (const { oid, index } of heads) {
11250
- try {
11251
- const { object } = await _readObject({ fs, cache, gitdir, oid });
11252
- const commit = GitCommit.from(object);
11253
- const { parent } = commit.parseHeaders();
11254
- for (const oid of parent) {
11255
- if (!visits[oid] || !visits[oid].has(index)) {
11256
- newheads.set(oid + ':' + index, { oid, index });
11257
- }
11300
+ // Parents of a common commit cannot be better merge bases.
11301
+ if (common.has(oid)) continue
11302
+ for (const parent of await readParents(oid)) {
11303
+ if (!visits[parent] || !visits[parent].has(index)) {
11304
+ newheads.set(parent + ':' + index, { oid: parent, index });
11258
11305
  }
11259
- } catch (err) {
11260
- // do nothing
11261
11306
  }
11262
11307
  }
11263
11308
  heads = Array.from(newheads.values());
11264
11309
  }
11265
- return []
11310
+
11311
+ if (common.size < 2) return [...common]
11312
+
11313
+ // A merge base is only "best" when it is not an ancestor of another
11314
+ // common ancestor. This also keeps independent criss-cross bases.
11315
+ const redundant = new Set();
11316
+ for (const oid of common) {
11317
+ const ancestors = [...(await readParents(oid))];
11318
+ const seen = new Set();
11319
+ while (ancestors.length) {
11320
+ const ancestor = ancestors.pop();
11321
+ if (seen.has(ancestor)) continue
11322
+ seen.add(ancestor);
11323
+ if (common.has(ancestor)) {
11324
+ redundant.add(ancestor);
11325
+ } else {
11326
+ ancestors.push(...(await readParents(ancestor)));
11327
+ }
11328
+ }
11329
+ }
11330
+ return [...common].filter(oid => !redundant.has(oid))
11266
11331
  }
11267
11332
 
11268
11333
  // @ts-check
package/index.js CHANGED
@@ -2358,6 +2358,38 @@ class GitRefManager {
2358
2358
  throw new NotFoundError(ref)
2359
2359
  }
2360
2360
 
2361
+ /**
2362
+ * Checks whether a ref names the branch HEAD points at in a repository where
2363
+ * that branch has no commits yet.
2364
+ *
2365
+ * A repository created by `git init` has a HEAD pointing at a branch that
2366
+ * does not exist on disk. Resolving that branch fails the same way resolving
2367
+ * a misspelled ref does, so callers that want to treat the two differently
2368
+ * need this to tell them apart.
2369
+ *
2370
+ * @param {Object} args
2371
+ * @param {FSClient} args.fs - A file system implementation.
2372
+ * @param {string} [args.gitdir=join(dir, '.git')] - [required] The [git directory](dir-vs-gitdir.md) path
2373
+ * @param {string} args.ref - The ref to check.
2374
+ * @returns {Promise<boolean>} - True if the ref names an unborn branch.
2375
+ */
2376
+ static async isUnbornBranch({ fs, gitdir, ref }) {
2377
+ let target;
2378
+ try {
2379
+ // depth 2 stops at the symbolic target instead of following it to an oid.
2380
+ target = await GitRefManager.resolve({
2381
+ fs,
2382
+ gitdir,
2383
+ ref: 'HEAD',
2384
+ depth: 2,
2385
+ });
2386
+ } catch (_) {
2387
+ return false
2388
+ }
2389
+ if (!target.startsWith('refs/heads/')) return false
2390
+ return ref === 'HEAD' || refpaths(ref).includes(target)
2391
+ }
2392
+
2361
2393
  /**
2362
2394
  * Checks if a ref exists.
2363
2395
  *
@@ -3638,6 +3670,10 @@ async function _readObject({
3638
3670
  oid,
3639
3671
  format = 'content',
3640
3672
  }) {
3673
+ if (!['deflated', 'wrapped', 'content'].includes(format)) {
3674
+ throw new InternalError(`invalid requested format "${format}"`)
3675
+ }
3676
+
3641
3677
  // Curry the current read method so that the packfile un-deltification
3642
3678
  // process can acquire external ref-deltas.
3643
3679
  const getExternalRefDelta = oid => _readObject({ fs, cache, gitdir, oid });
@@ -3697,11 +3733,7 @@ async function _readObject({
3697
3733
  result.object = object;
3698
3734
  result.format = 'content';
3699
3735
 
3700
- if (format === 'content') {
3701
- return result
3702
- }
3703
-
3704
- throw new InternalError(`invalid requested format "${format}"`)
3736
+ return result
3705
3737
  }
3706
3738
 
3707
3739
  class AlreadyExistsError extends BaseError {
@@ -4531,9 +4563,16 @@ class GitWalkerRepo {
4531
4563
  try {
4532
4564
  oid = await GitRefManager.resolve({ fs, gitdir, ref });
4533
4565
  } catch (e) {
4534
- if (e instanceof NotFoundError) {
4535
- // Handle fresh branches with no commits
4566
+ // Only a branch that has no commits yet gets the empty tree. Anything
4567
+ // else that fails to resolve is a ref the caller got wrong, and
4568
+ // swallowing it here would walk an empty tree instead of saying so.
4569
+ if (
4570
+ e instanceof NotFoundError &&
4571
+ (await GitRefManager.isUnbornBranch({ fs, gitdir, ref }))
4572
+ ) {
4536
4573
  oid = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
4574
+ } else {
4575
+ throw e
4537
4576
  }
4538
4577
  }
4539
4578
  const tree = await resolveTree({ fs, cache: this.cache, gitdir, oid });
@@ -9715,8 +9754,8 @@ function filterCapabilities(server, client) {
9715
9754
 
9716
9755
  const pkg = {
9717
9756
  name: 'isomorphic-git',
9718
- version: '1.41.5',
9719
- agent: 'git/isomorphic-git@1.41.5',
9757
+ version: '1.41.8',
9758
+ agent: 'git/isomorphic-git@1.41.8',
9720
9759
  };
9721
9760
 
9722
9761
  class FIFO {
@@ -11212,44 +11251,70 @@ async function _findMergeBase({ fs, cache, gitdir, oids }) {
11212
11251
  // and computing virtual merge bases is just too much for me to fathom right now.
11213
11252
 
11214
11253
  // If we start N independent walkers, one at each of the given `oids`, and walk backwards
11215
- // through ancestors, eventually we'll discover a commit where each one of these N walkers
11216
- // has passed through. So we just need to keep track of which walkers have visited each commit
11217
- // until we find a commit that N distinct walkers has visited.
11254
+ // through ancestors, eventually we'll discover commits where each one of these N walkers
11255
+ // has passed through. We cannot stop at the first common commit because a better common
11256
+ // ancestor may still be present in another branch of the graph.
11218
11257
  const visits = {};
11219
11258
  const passes = oids.length;
11259
+ const common = new Set();
11260
+ const parents = new Map();
11261
+ const shallows = await GitShallowManager.read({ fs, gitdir });
11262
+ const readParents = async oid => {
11263
+ if (parents.has(oid)) return parents.get(oid)
11264
+ const { object, type } = await _readObject({ fs, cache, gitdir, oid });
11265
+ if (type !== 'commit') {
11266
+ throw new ObjectTypeError(oid, type, 'commit')
11267
+ }
11268
+ const commit = GitCommit.from(object);
11269
+ const { parent } = commit.parseHeaders();
11270
+ const result = shallows.has(oid) ? [] : parent;
11271
+ parents.set(oid, result);
11272
+ return result
11273
+ };
11220
11274
  let heads = oids.map((oid, index) => ({ index, oid }));
11221
11275
  while (heads.length) {
11222
11276
  // Count how many times we've passed each commit
11223
- const result = new Set();
11224
11277
  for (const { oid, index } of heads) {
11278
+ await readParents(oid);
11225
11279
  if (!visits[oid]) visits[oid] = new Set();
11226
11280
  visits[oid].add(index);
11227
11281
  if (visits[oid].size === passes) {
11228
- result.add(oid);
11282
+ common.add(oid);
11229
11283
  }
11230
11284
  }
11231
- if (result.size > 0) {
11232
- return [...result]
11233
- }
11234
- // We haven't found a common ancestor yet
11235
11285
  const newheads = new Map();
11236
11286
  for (const { oid, index } of heads) {
11237
- try {
11238
- const { object } = await _readObject({ fs, cache, gitdir, oid });
11239
- const commit = GitCommit.from(object);
11240
- const { parent } = commit.parseHeaders();
11241
- for (const oid of parent) {
11242
- if (!visits[oid] || !visits[oid].has(index)) {
11243
- newheads.set(oid + ':' + index, { oid, index });
11244
- }
11287
+ // Parents of a common commit cannot be better merge bases.
11288
+ if (common.has(oid)) continue
11289
+ for (const parent of await readParents(oid)) {
11290
+ if (!visits[parent] || !visits[parent].has(index)) {
11291
+ newheads.set(parent + ':' + index, { oid: parent, index });
11245
11292
  }
11246
- } catch (err) {
11247
- // do nothing
11248
11293
  }
11249
11294
  }
11250
11295
  heads = Array.from(newheads.values());
11251
11296
  }
11252
- return []
11297
+
11298
+ if (common.size < 2) return [...common]
11299
+
11300
+ // A merge base is only "best" when it is not an ancestor of another
11301
+ // common ancestor. This also keeps independent criss-cross bases.
11302
+ const redundant = new Set();
11303
+ for (const oid of common) {
11304
+ const ancestors = [...(await readParents(oid))];
11305
+ const seen = new Set();
11306
+ while (ancestors.length) {
11307
+ const ancestor = ancestors.pop();
11308
+ if (seen.has(ancestor)) continue
11309
+ seen.add(ancestor);
11310
+ if (common.has(ancestor)) {
11311
+ redundant.add(ancestor);
11312
+ } else {
11313
+ ancestors.push(...(await readParents(ancestor)));
11314
+ }
11315
+ }
11316
+ }
11317
+ return [...common].filter(oid => !redundant.has(oid))
11253
11318
  }
11254
11319
 
11255
11320
  // @ts-check