isomorphic-git 1.17.2 → 1.18.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/README.md CHANGED
@@ -345,6 +345,7 @@ Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds
345
345
  </tr>
346
346
  <tr>
347
347
  <td align="center"><a href="https://github.com/seanpoulter"><img src="https://avatars.githubusercontent.com/u/2585460?v=4?s=60" width="60px;" alt=""/><br /><sub><b>Sean Poulter</b></sub></a><br /><a href="#maintenance-seanpoulter" title="Maintenance">🚧</a></td>
348
+ <td align="center"><a href="https://github.com/araknast"><img src="https://avatars.githubusercontent.com/u/84164531?v=4?s=60" width="60px;" alt=""/><br /><sub><b>araknast</b></sub></a><br /><a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=araknast" title="Code">💻</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=araknast" title="Tests">⚠️</a> <a href="https://github.com/isomorphic-git/isomorphic-git/commits?author=araknast" title="Documentation">📖</a></td>
348
349
  </tr>
349
350
  </table>
350
351
 
@@ -1,8 +1,8 @@
1
1
  [
2
2
  "Chrome Headless 79.0.3945.0 (Linux x86_64)",
3
- "Firefox 99.0 (Ubuntu 0.0.0)",
4
- "Safari 13.1 (Mac OS 10.15.4)",
5
- "Mobile Safari 13.0 (iOS 13.0)",
3
+ "Firefox 100.0 (Ubuntu 0.0.0)",
4
+ "Chrome 100.0.4896.127 (Android 10)",
6
5
  "Edge 79.0.309.65 (Windows 10)",
7
- "Chrome 96.0.4664.104 (Android 10)"
6
+ "Mobile Safari 13.0 (iOS 13.0)",
7
+ "Safari 13.1 (Mac OS 10.15.4)"
8
8
  ]
package/index.cjs CHANGED
@@ -239,6 +239,19 @@ var diff3Merge = _interopDefault(require('diff3'));
239
239
  * @return {{signature: string} | Promise<{signature: string}>} - an 'ASCII armor' encoded "detached" signature
240
240
  */
241
241
 
242
+ /**
243
+ * @typedef {Object} MergeDriverParams
244
+ * @property {Array<string>} branches
245
+ * @property {Array<string>} contents
246
+ * @property {string} path
247
+ */
248
+
249
+ /**
250
+ * @callback MergeDriverCallback
251
+ * @param {MergeDriverParams} args
252
+ * @return {{cleanMerge: boolean, mergedText: string} | Promise<{cleanMerge: boolean, mergedText: string}>}
253
+ */
254
+
242
255
  /**
243
256
  * @callback WalkerMap
244
257
  * @param {string} filename
@@ -3202,6 +3215,21 @@ class MergeNotSupportedError extends BaseError {
3202
3215
  /** @type {'MergeNotSupportedError'} */
3203
3216
  MergeNotSupportedError.code = 'MergeNotSupportedError';
3204
3217
 
3218
+ class MergeConflictError extends BaseError {
3219
+ /**
3220
+ * @param {Array<string>} filepaths
3221
+ */
3222
+ constructor(filepaths) {
3223
+ super(
3224
+ `Automatic merge failed with one or more merge conflicts in the following files: ${filepaths.toString()}. Fix conflicts then commit the result.`
3225
+ );
3226
+ this.code = this.name = MergeConflictError.code;
3227
+ this.data = { filepaths };
3228
+ }
3229
+ }
3230
+ /** @type {'MergeConflictError'} */
3231
+ MergeConflictError.code = 'MergeConflictError';
3232
+
3205
3233
  class MissingNameError extends BaseError {
3206
3234
  /**
3207
3235
  * @param {'author'|'committer'|'tagger'} role
@@ -3372,6 +3400,7 @@ var Errors = /*#__PURE__*/Object.freeze({
3372
3400
  InvalidRefNameError: InvalidRefNameError,
3373
3401
  MaxDepthError: MaxDepthError,
3374
3402
  MergeNotSupportedError: MergeNotSupportedError,
3403
+ MergeConflictError: MergeConflictError,
3375
3404
  MissingNameError: MissingNameError,
3376
3405
  MissingParameterError: MissingParameterError,
3377
3406
  MultipleGitError: MultipleGitError,
@@ -4633,7 +4662,15 @@ async function _commit({
4633
4662
  // Probably an initial commit
4634
4663
  parent = [];
4635
4664
  }
4665
+ } else {
4666
+ // ensure that the parents are oids, not refs
4667
+ parent = await Promise.all(
4668
+ parent.map(p => {
4669
+ return GitRefManager.resolve({ fs, gitdir, ref: p })
4670
+ })
4671
+ );
4636
4672
  }
4673
+
4637
4674
  let comm = GitCommit.from({
4638
4675
  tree,
4639
4676
  parent,
@@ -6946,8 +6983,8 @@ function filterCapabilities(server, client) {
6946
6983
 
6947
6984
  const pkg = {
6948
6985
  name: 'isomorphic-git',
6949
- version: '1.17.2',
6950
- agent: 'git/isomorphic-git@1.17.2',
6986
+ version: '1.18.1',
6987
+ agent: 'git/isomorphic-git@1.18.1',
6951
6988
  };
6952
6989
 
6953
6990
  class FIFO {
@@ -8423,16 +8460,14 @@ async function _findMergeBase({ fs, cache, gitdir, oids }) {
8423
8460
 
8424
8461
  const LINEBREAKS = /^.*(\r?\n|$)/gm;
8425
8462
 
8426
- function mergeFile({
8427
- ourContent,
8428
- baseContent,
8429
- theirContent,
8430
- ourName = 'ours',
8431
- baseName = 'base',
8432
- theirName = 'theirs',
8433
- format = 'diff',
8434
- markerSize = 7,
8435
- }) {
8463
+ function mergeFile({ branches, contents }) {
8464
+ const ourName = branches[1];
8465
+ const theirName = branches[2];
8466
+
8467
+ const baseContent = contents[0];
8468
+ const ourContent = contents[1];
8469
+ const theirContent = contents[2];
8470
+
8436
8471
  const ours = ourContent.match(LINEBREAKS);
8437
8472
  const base = baseContent.match(LINEBREAKS);
8438
8473
  const theirs = theirContent.match(LINEBREAKS);
@@ -8440,9 +8475,12 @@ function mergeFile({
8440
8475
  // Here we let the diff3 library do the heavy lifting.
8441
8476
  const result = diff3Merge(ours, base, theirs);
8442
8477
 
8478
+ const markerSize = 7;
8479
+
8443
8480
  // Here we note whether there are conflicts and format the results
8444
8481
  let mergedText = '';
8445
8482
  let cleanMerge = true;
8483
+
8446
8484
  for (const item of result) {
8447
8485
  if (item.ok) {
8448
8486
  mergedText += item.ok.join('');
@@ -8451,10 +8489,7 @@ function mergeFile({
8451
8489
  cleanMerge = false;
8452
8490
  mergedText += `${'<'.repeat(markerSize)} ${ourName}\n`;
8453
8491
  mergedText += item.conflict.a.join('');
8454
- if (format === 'diff3') {
8455
- mergedText += `${'|'.repeat(markerSize)} ${baseName}\n`;
8456
- mergedText += item.conflict.o.join('');
8457
- }
8492
+
8458
8493
  mergedText += `${'='.repeat(markerSize)}\n`;
8459
8494
  mergedText += item.conflict.b.join('');
8460
8495
  mergedText += `${'>'.repeat(markerSize)} ${theirName}\n`;
@@ -8480,6 +8515,8 @@ function mergeFile({
8480
8515
  * @param {string} [args.baseName='base'] - The name to use in conflicted files (in diff3 format) for the base hunks
8481
8516
  * @param {string} [args.theirName='theirs'] - The name to use in conflicted files for their hunks
8482
8517
  * @param {boolean} [args.dryRun=false]
8518
+ * @param {boolean} [args.abortOnConflict=false]
8519
+ * @param {MergeDriverCallback} [args.mergeDriver]
8483
8520
  *
8484
8521
  * @returns {Promise<string>} - The SHA-1 object id of the merged tree
8485
8522
  *
@@ -8496,11 +8533,17 @@ async function mergeTree({
8496
8533
  baseName = 'base',
8497
8534
  theirName = 'theirs',
8498
8535
  dryRun = false,
8536
+ abortOnConflict = true,
8537
+ mergeDriver,
8499
8538
  }) {
8500
8539
  const ourTree = TREE({ ref: ourOid });
8501
8540
  const baseTree = TREE({ ref: baseOid });
8502
8541
  const theirTree = TREE({ ref: theirOid });
8503
8542
 
8543
+ const unmergedFiles = [];
8544
+
8545
+ let cleanMerge = true;
8546
+
8504
8547
  const results = await _walk({
8505
8548
  fs,
8506
8549
  cache,
@@ -8561,6 +8604,11 @@ async function mergeTree({
8561
8604
  ourName,
8562
8605
  baseName,
8563
8606
  theirName,
8607
+ mergeDriver,
8608
+ }).then(r => {
8609
+ cleanMerge = r.cleanMerge;
8610
+ unmergedFiles.push(filepath);
8611
+ return r.mergeResult
8564
8612
  })
8565
8613
  }
8566
8614
  // all other types of conflicts fail
@@ -8596,6 +8644,29 @@ async function mergeTree({
8596
8644
  return parent
8597
8645
  },
8598
8646
  });
8647
+
8648
+ if (!cleanMerge) {
8649
+ if (dir && !abortOnConflict) {
8650
+ await _walk({
8651
+ fs,
8652
+ cache,
8653
+ dir,
8654
+ gitdir,
8655
+ trees: [TREE({ ref: results.oid })],
8656
+ map: async function(filepath, [entry]) {
8657
+ const path = `${dir}/${filepath}`;
8658
+ if ((await entry.type()) === 'blob') {
8659
+ const mode = await entry.mode();
8660
+ const content = new TextDecoder().decode(await entry.content());
8661
+ await fs.write(path, content, { mode });
8662
+ }
8663
+ return true
8664
+ },
8665
+ });
8666
+ }
8667
+ throw new MergeConflictError(unmergedFiles)
8668
+ }
8669
+
8599
8670
  return results.oid
8600
8671
  }
8601
8672
 
@@ -8634,9 +8705,8 @@ async function modified(entry, base) {
8634
8705
  * @param {string} [args.ourName]
8635
8706
  * @param {string} [args.baseName]
8636
8707
  * @param {string} [args.theirName]
8637
- * @param {string} [args.format]
8638
- * @param {number} [args.markerSize]
8639
8708
  * @param {boolean} [args.dryRun = false]
8709
+ * @param {MergeDriverCallback} [args.mergeDriver]
8640
8710
  *
8641
8711
  */
8642
8712
  async function mergeBlobs({
@@ -8649,9 +8719,8 @@ async function mergeBlobs({
8649
8719
  ourName,
8650
8720
  theirName,
8651
8721
  baseName,
8652
- format,
8653
- markerSize,
8654
8722
  dryRun,
8723
+ mergeDriver = mergeFile,
8655
8724
  }) {
8656
8725
  const type = 'blob';
8657
8726
  // Compute the new mode.
@@ -8662,30 +8731,33 @@ async function mergeBlobs({
8662
8731
  : await ours.mode();
8663
8732
  // The trivial case: nothing to merge except maybe mode
8664
8733
  if ((await ours.oid()) === (await theirs.oid())) {
8665
- return { mode, path, oid: await ours.oid(), type }
8734
+ return {
8735
+ cleanMerge: true,
8736
+ mergeResult: { mode, path, oid: await ours.oid(), type },
8737
+ }
8666
8738
  }
8667
8739
  // if only one side made oid changes, return that side's oid
8668
8740
  if ((await ours.oid()) === (await base.oid())) {
8669
- return { mode, path, oid: await theirs.oid(), type }
8741
+ return {
8742
+ cleanMerge: true,
8743
+ mergeResult: { mode, path, oid: await theirs.oid(), type },
8744
+ }
8670
8745
  }
8671
8746
  if ((await theirs.oid()) === (await base.oid())) {
8672
- return { mode, path, oid: await ours.oid(), type }
8747
+ return {
8748
+ cleanMerge: true,
8749
+ mergeResult: { mode, path, oid: await ours.oid(), type },
8750
+ }
8673
8751
  }
8674
8752
  // if both sides made changes do a merge
8675
- const { mergedText, cleanMerge } = mergeFile({
8676
- ourContent: Buffer.from(await ours.content()).toString('utf8'),
8677
- baseContent: Buffer.from(await base.content()).toString('utf8'),
8678
- theirContent: Buffer.from(await theirs.content()).toString('utf8'),
8679
- ourName,
8680
- theirName,
8681
- baseName,
8682
- format,
8683
- markerSize,
8753
+ const ourContent = Buffer.from(await ours.content()).toString('utf8');
8754
+ const baseContent = Buffer.from(await base.content()).toString('utf8');
8755
+ const theirContent = Buffer.from(await theirs.content()).toString('utf8');
8756
+ const { mergedText, cleanMerge } = await mergeDriver({
8757
+ branches: [baseName, ourName, theirName],
8758
+ contents: [baseContent, ourContent, theirContent],
8759
+ path,
8684
8760
  });
8685
- if (!cleanMerge) {
8686
- // all other types of conflicts fail
8687
- throw new MergeNotSupportedError()
8688
- }
8689
8761
  const oid = await _writeObject({
8690
8762
  fs,
8691
8763
  gitdir,
@@ -8693,7 +8765,8 @@ async function mergeBlobs({
8693
8765
  object: Buffer.from(mergedText, 'utf8'),
8694
8766
  dryRun,
8695
8767
  });
8696
- return { mode, path, oid, type }
8768
+
8769
+ return { cleanMerge, mergeResult: { mode, path, oid, type } }
8697
8770
  }
8698
8771
 
8699
8772
  // @ts-check
@@ -8721,6 +8794,7 @@ async function mergeBlobs({
8721
8794
  * @param {boolean} args.fastForwardOnly
8722
8795
  * @param {boolean} args.dryRun
8723
8796
  * @param {boolean} args.noUpdateBranch
8797
+ * @param {boolean} args.abortOnConflict
8724
8798
  * @param {string} [args.message]
8725
8799
  * @param {Object} args.author
8726
8800
  * @param {string} args.author.name
@@ -8734,6 +8808,7 @@ async function mergeBlobs({
8734
8808
  * @param {number} args.committer.timezoneOffset
8735
8809
  * @param {string} [args.signingKey]
8736
8810
  * @param {SignCallback} [args.onSign] - a PGP signing implementation
8811
+ * @param {MergeDriverCallback} [args.mergeDriver]
8737
8812
  *
8738
8813
  * @returns {Promise<MergeResult>} Resolves to a description of the merge operation
8739
8814
  *
@@ -8741,6 +8816,7 @@ async function mergeBlobs({
8741
8816
  async function _merge({
8742
8817
  fs,
8743
8818
  cache,
8819
+ dir,
8744
8820
  gitdir,
8745
8821
  ours,
8746
8822
  theirs,
@@ -8748,11 +8824,13 @@ async function _merge({
8748
8824
  fastForwardOnly = false,
8749
8825
  dryRun = false,
8750
8826
  noUpdateBranch = false,
8827
+ abortOnConflict = true,
8751
8828
  message,
8752
8829
  author,
8753
8830
  committer,
8754
8831
  signingKey,
8755
8832
  onSign,
8833
+ mergeDriver,
8756
8834
  }) {
8757
8835
  if (ours === undefined) {
8758
8836
  ours = await _currentBranch({ fs, gitdir, fullname: true });
@@ -8812,14 +8890,17 @@ async function _merge({
8812
8890
  const tree = await mergeTree({
8813
8891
  fs,
8814
8892
  cache,
8893
+ dir,
8815
8894
  gitdir,
8816
8895
  ourOid,
8817
8896
  theirOid,
8818
8897
  baseOid,
8819
- ourName: ours,
8898
+ ourName: abbreviateRef(ours),
8820
8899
  baseName: 'base',
8821
- theirName: theirs,
8900
+ theirName: abbreviateRef(theirs),
8822
8901
  dryRun,
8902
+ abortOnConflict,
8903
+ mergeDriver,
8823
8904
  });
8824
8905
  if (!message) {
8825
8906
  message = `Merge branch '${abbreviateRef(theirs)}' into ${abbreviateRef(
@@ -10787,11 +10868,11 @@ async function _log({
10787
10868
  }
10788
10869
  }
10789
10870
  if (!found) {
10790
- if (!force && !follow) throw e
10791
10871
  if (isOk && lastFileOid) {
10792
10872
  commits.push(lastCommit);
10793
- // break
10873
+ if (!force) break
10794
10874
  }
10875
+ if (!force && !follow) throw e
10795
10876
  }
10796
10877
  lastCommit = commit;
10797
10878
  isOk = false;
@@ -10911,15 +10992,63 @@ async function log({
10911
10992
  /**
10912
10993
  * Merge two branches
10913
10994
  *
10914
- * ## Limitations
10915
- *
10916
- * Currently it does not support incomplete merges. That is, if there are merge conflicts it cannot solve
10917
- * with the built in diff3 algorithm it will not modify the working dir, and will throw a [`MergeNotSupportedError`](./errors.md#mergenotsupportedError) error.
10918
- *
10919
10995
  * Currently it will fail if multiple candidate merge bases are found. (It doesn't yet implement the recursive merge strategy.)
10920
10996
  *
10921
10997
  * Currently it does not support selecting alternative merge strategies.
10922
10998
  *
10999
+ * Currently it is not possible to abort an incomplete merge. To restore the worktree to a clean state, you will need to checkout an earlier commit.
11000
+ *
11001
+ * Currently it does not directly support the behavior of `git merge --continue`. To complete a merge after manual conflict resolution, you will need to add and commit the files manually, and specify the appropriate parent commits.
11002
+ *
11003
+ * ## Manually resolving merge conflicts
11004
+ * By default, if isomorphic-git encounters a merge conflict it cannot resolve using the builtin diff3 algorithm or provided merge driver, it will abort and throw a `MergeNotSupportedError`.
11005
+ * This leaves the index and working tree untouched.
11006
+ *
11007
+ * When `abortOnConflict` is set to `false`, and a merge conflict cannot be automatically resolved, a `MergeConflictError` is thrown and the results of the incomplete merge will be written to the working directory.
11008
+ * This includes conflict markers in files with unresolved merge conflicts.
11009
+ *
11010
+ * To complete the merge, edit the conflicting files as you see fit, and then add and commit the resolved merge.
11011
+ *
11012
+ * For a proper merge commit, be sure to specify the branches or commits you are merging in the `parent` argument to `git.commit`.
11013
+ * For example, say we are merging the branch `feature` into the branch `main` and there is a conflict we want to resolve manually.
11014
+ * The flow would look like this:
11015
+ *
11016
+ * ```
11017
+ * await git.merge({
11018
+ * fs,
11019
+ * dir,
11020
+ * ours: 'main',
11021
+ * theirs: 'feature',
11022
+ * abortOnConflict: false,
11023
+ * }).catch(e => {
11024
+ * if (e instanceof Errors.MergeConflictError) {
11025
+ * console.log(
11026
+ * 'Automatic merge failed for the following files: '
11027
+ * + `${e.data}. `
11028
+ * + 'Resolve these conflicts and then commit your changes.'
11029
+ * )
11030
+ * } else throw e
11031
+ * })
11032
+ *
11033
+ * // This is the where we manually edit the files that have been written to the working directory
11034
+ * // ...
11035
+ * // Files have been edited and we are ready to commit
11036
+ *
11037
+ * await git.add({
11038
+ * fs,
11039
+ * dir,
11040
+ * filepath: '.',
11041
+ * })
11042
+ *
11043
+ * await git.commit({
11044
+ * fs,
11045
+ * dir,
11046
+ * ref: 'main',
11047
+ * message: "Merge branch 'feature' into main",
11048
+ * parent: ['main', 'feature'], // Be sure to specify the parents when creating a merge commit
11049
+ * })
11050
+ * ```
11051
+ *
10923
11052
  * @param {object} args
10924
11053
  * @param {FsClient} args.fs - a file system client
10925
11054
  * @param {SignCallback} [args.onSign] - a PGP signing implementation
@@ -10931,6 +11060,7 @@ async function log({
10931
11060
  * @param {boolean} [args.fastForwardOnly = false] - If true, then non-fast-forward merges will throw an Error instead of performing a merge.
10932
11061
  * @param {boolean} [args.dryRun = false] - If true, simulates a merge so you can test whether it would succeed.
10933
11062
  * @param {boolean} [args.noUpdateBranch = false] - If true, does not update the branch pointer after creating the commit.
11063
+ * @param {boolean} [args.abortOnConflict = true] - If true, merges with conflicts will not update the worktree or index.
10934
11064
  * @param {string} [args.message] - Overrides the default auto-generated merge commit message
10935
11065
  * @param {Object} [args.author] - passed to [commit](commit.md) when creating a merge commit
10936
11066
  * @param {string} [args.author.name] - Default is `user.name` config.
@@ -10944,6 +11074,7 @@ async function log({
10944
11074
  * @param {number} [args.committer.timezoneOffset] - Set the committer timezone offset field. This is the difference, in minutes, from the current timezone to UTC. Default is `(new Date()).getTimezoneOffset()`.
10945
11075
  * @param {string} [args.signingKey] - passed to [commit](commit.md) when creating a merge commit
10946
11076
  * @param {object} [args.cache] - a [cache](cache.md) object
11077
+ * @param {MergeDriverCallback} [args.mergeDriver] - a [merge driver](mergeDriver.md) implementation
10947
11078
  *
10948
11079
  * @returns {Promise<MergeResult>} Resolves to a description of the merge operation
10949
11080
  * @see MergeResult
@@ -10969,11 +11100,13 @@ async function merge({
10969
11100
  fastForwardOnly = false,
10970
11101
  dryRun = false,
10971
11102
  noUpdateBranch = false,
11103
+ abortOnConflict = true,
10972
11104
  message,
10973
11105
  author: _author,
10974
11106
  committer: _committer,
10975
11107
  signingKey,
10976
11108
  cache = {},
11109
+ mergeDriver,
10977
11110
  }) {
10978
11111
  try {
10979
11112
  assertParameter('fs', _fs);
@@ -11000,6 +11133,7 @@ async function merge({
11000
11133
  return await _merge({
11001
11134
  fs,
11002
11135
  cache,
11136
+ dir,
11003
11137
  gitdir,
11004
11138
  ours,
11005
11139
  theirs,
@@ -11007,11 +11141,13 @@ async function merge({
11007
11141
  fastForwardOnly,
11008
11142
  dryRun,
11009
11143
  noUpdateBranch,
11144
+ abortOnConflict,
11010
11145
  message,
11011
11146
  author,
11012
11147
  committer,
11013
11148
  signingKey,
11014
11149
  onSign,
11150
+ mergeDriver,
11015
11151
  })
11016
11152
  } catch (err) {
11017
11153
  err.caller = 'git.merge';