dsh-ide-git 0.17.0 → 0.18.0

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/src/index.js CHANGED
@@ -948,17 +948,34 @@ async function branchRename(payload) {
948
948
  async function branchDelete(payload) {
949
949
  const cwd = cwdOf(payload)
950
950
  const root = await repoRootOf(cwd, payload)
951
- const name = requireRef(payload.name, 'name')
952
951
  const force = payload.force === true
953
952
  if (force && payload.confirm !== true) throw badRequest('force delete requires confirm: true')
953
+ /* Batch form (multi-select): `names` wins when present; the single `name`
954
+ keeps working exactly as before. Every name goes through the same ref
955
+ guard — an array never bypasses validation. */
956
+ const names = Array.isArray(payload.names)
957
+ ? payload.names.map((entry) => requireRef(entry, 'name'))
958
+ : [requireRef(payload.name, 'name')]
954
959
  const current = await currentBranchOf(root)
955
- if (name === current) throw new PanelError('protected-branch', 'refusing to delete the checked-out branch "' + name + '"', 409)
956
- if (PROTECTED_BRANCHES.has(name) && payload.confirm !== true) throw badRequest('deleting "' + name + '" requires confirm: true')
957
- const head = (await git(root, ['rev-parse', '--verify', 'refs/heads/' + name])).trim()
960
+ for (const name of names) {
961
+ if (name === current) throw new PanelError('protected-branch', 'refusing to delete the checked-out branch "' + name + '"', 409)
962
+ if (PROTECTED_BRANCHES.has(name) && payload.confirm !== true) throw badRequest('deleting "' + name + '" requires confirm: true')
963
+ }
964
+ const branches = []
965
+ for (const name of names) {
966
+ const head = (await git(root, ['rev-parse', '--verify', 'refs/heads/' + name])).trim()
967
+ branches.push({ name: name, hash: head })
968
+ }
958
969
  // -d (the default) refuses to drop an unmerged branch: git is the guard here.
959
- await git(root, ['branch', force ? '-D' : '-d', name])
960
- const undo = pushUndo(root, { kind: 'branch-delete', label: name, name: name, hash: head })
961
- return { branch: name, force, hash: head, undo: undo }
970
+ await git(root, ['branch', force ? '-D' : '-d', ...names])
971
+ const label = names.length === 1 ? names[0] : names.length + ' branches'
972
+ const undo = pushUndo(root, {
973
+ kind: 'branch-delete', label: label,
974
+ branches: branches,
975
+ // Legacy single-branch fields stay so an in-flight old handle keeps working.
976
+ name: names[0], hash: branches[0].hash,
977
+ })
978
+ return { branch: names[0], hash: branches[0].hash, names: names, branches: branches, force: force, undo: undo }
962
979
  }
963
980
 
964
981
  async function merge(payload) {
@@ -1030,10 +1047,14 @@ async function branchDeleteRemote(payload) {
1030
1047
  const cwd = cwdOf(payload)
1031
1048
  const root = await repoRootOf(cwd, payload)
1032
1049
  const remote = requireRef(payload.remote, 'remote')
1033
- const branch = requireRef(payload.branch, 'branch')
1050
+ /* Batch form (multi-select): `branches` wins; the single `branch` keeps
1051
+ working. `git push --delete` accepts several names in one go. */
1052
+ const branches = Array.isArray(payload.branches)
1053
+ ? payload.branches.map((entry) => requireRef(entry, 'branch'))
1054
+ : [requireRef(payload.branch, 'branch')]
1034
1055
  if (payload.confirm !== true) throw badRequest('deleting a remote branch requires confirm: true')
1035
- const out = await git(root, ['push', remote, '--delete', branch], { timeoutMs: NETWORK_TIMEOUT_MS })
1036
- return { remote, branch, output: out.trim() }
1056
+ const out = await git(root, ['push', remote, '--delete', ...branches], { timeoutMs: NETWORK_TIMEOUT_MS })
1057
+ return { remote: remote, branch: branches[0], branches: branches, output: out.trim() }
1037
1058
  }
1038
1059
 
1039
1060
  /* SCM ▸ Tags ▸ Delete Remote Tag... / Push Tags. */
@@ -1070,12 +1091,17 @@ async function stashClear(payload) {
1070
1091
  async function cherryPick(payload) {
1071
1092
  const cwd = cwdOf(payload)
1072
1093
  const root = await repoRootOf(cwd, payload)
1073
- const hash = requireRef(payload.hash, 'hash')
1074
- const result = await runGit(root, ['cherry-pick', hash])
1094
+ /* Batch form (multi-select): `hashes` are applied in the order given — the
1095
+ client sends them oldest-first so the sequence replays topologically. The
1096
+ single `hash` keeps working. */
1097
+ const hashes = Array.isArray(payload.hashes)
1098
+ ? payload.hashes.map((entry) => requireRef(entry, 'hash'))
1099
+ : [requireRef(payload.hash, 'hash')]
1100
+ const result = await runGit(root, ['cherry-pick', ...hashes])
1075
1101
  const combined = (result.stdout + '\n' + result.stderr).trim()
1076
1102
  const conflicted = /CONFLICT/.test(combined)
1077
1103
  if (result.code !== 0 && !conflicted) throw new PanelError('git-failed', combined === '' ? 'cherry-pick failed' : combined, 409)
1078
- return { hash, conflicted, output: combined }
1104
+ return { hash: hashes[0], hashes: hashes, conflicted: conflicted, output: combined }
1079
1105
  }
1080
1106
 
1081
1107
  async function revert(payload) {
@@ -1201,9 +1227,12 @@ async function tagCreate(payload) {
1201
1227
  async function tagDelete(payload) {
1202
1228
  const cwd = cwdOf(payload)
1203
1229
  const root = await repoRootOf(cwd, payload)
1204
- const tag = requireRef(payload.name, 'name')
1205
- await git(root, ['tag', '-d', tag])
1206
- return { tag }
1230
+ /* Batch form (multi-select): `names` wins; the single `name` keeps working. */
1231
+ const names = Array.isArray(payload.names)
1232
+ ? payload.names.map((entry) => requireRef(entry, 'name'))
1233
+ : [requireRef(payload.name, 'name')]
1234
+ await git(root, ['tag', '-d', ...names])
1235
+ return { tag: names[0], names: names }
1207
1236
  }
1208
1237
 
1209
1238
  /* VS Code's SCM title "Clone..." — clone a URL into an absolute local path.
@@ -1257,9 +1286,14 @@ async function undoApply(payload) {
1257
1286
  if (index < 0) throw new PanelError('undo-gone', 'this action can no longer be undone', 409)
1258
1287
  const entry = list[index]
1259
1288
  if (entry.kind === 'branch-delete') {
1260
- const exists = await runGit(root, ['show-ref', '--verify', '--quiet', 'refs/heads/' + entry.name])
1261
- if (exists.code === 0) throw new PanelError('undo-conflict', 'branch "' + entry.name + '" already exists again', 409)
1262
- await git(root, ['branch', entry.name, entry.hash])
1289
+ /* Batch handles carry `branches: [{name, hash}]`; older single-branch
1290
+ handles only have `name`/`hash` — normalise both to one list. */
1291
+ const restores = Array.isArray(entry.branches) ? entry.branches : [{ name: entry.name, hash: entry.hash }]
1292
+ for (const item of restores) {
1293
+ const exists = await runGit(root, ['show-ref', '--verify', '--quiet', 'refs/heads/' + item.name])
1294
+ if (exists.code === 0) throw new PanelError('undo-conflict', 'branch "' + item.name + '" already exists again', 409)
1295
+ await git(root, ['branch', item.name, item.hash])
1296
+ }
1263
1297
  } else if (entry.kind === 'stash-drop') {
1264
1298
  await git(root, ['stash', 'store', '-m', 'dsh-ide-git: restored ' + entry.label, entry.hash])
1265
1299
  } else if (entry.kind === 'discard') {