dsh-ide-git 0.14.5 → 0.16.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/client.js CHANGED
@@ -21,6 +21,7 @@ window.__ModuleLoader__.load({
21
21
  factory: (require) => {
22
22
  const React = require('react')
23
23
  const E = React.createElement
24
+ const Fragment = React.Fragment
24
25
  const useState = React.useState
25
26
  const useEffect = React.useEffect
26
27
  const useMemo = React.useMemo
@@ -186,6 +187,70 @@ window.__ModuleLoader__.load({
186
187
  'diff.copy': '复制',
187
188
  'diff.copied': '已复制',
188
189
  'toolbar.refresh': '刷新',
190
+ 'more.sort': '查看和排序',
191
+ 'more.commitGroup': '提交',
192
+ 'more.changesGroup': '更改',
193
+ 'more.pullpushGroup': '拉取,推送',
194
+ 'more.branchGroup': '分支',
195
+ 'more.remoteGroup': '远程',
196
+ 'more.stashGroup': '存储',
197
+ 'more.tagGroup': '标记',
198
+ 'more.commitStaged': '提交已暂存文件...',
199
+ 'more.commitAll': '全部提交...',
200
+ 'more.undoCommit': '撤销上次提交',
201
+ 'more.rebaseAbort': '中止变基',
202
+ 'more.commitStagedAmend': '提交已暂存文件(修改)...',
203
+ 'more.commitAllAmend': '全部提交(修改)...',
204
+ 'more.commitStagedSign': '提交已暂存文件(已签收)...',
205
+ 'more.commitAllSign': '全部提交(已签收)...',
206
+ 'prompt.commitMessage': '提交信息',
207
+ 'more.discardAll': '放弃所有更改...',
208
+ 'more.sync': '同步(拉取并推送)',
209
+ 'more.syncRebase': '同步(变基并推送)',
210
+ 'more.pullRebase': '拉取(变基)',
211
+ 'more.pullFrom': '从远程拉取',
212
+ 'more.pushForce': '推送(强制)...',
213
+ 'more.pushTo': '推送到远程',
214
+ 'more.fetchAll': '从所有远程抓取',
215
+ 'more.deleteRemoteBranch': '删除远程分支...',
216
+ 'more.publish': '发布分支...',
217
+ 'more.addRemote': '添加远程...',
218
+ 'more.removeRemote': '删除远程...',
219
+ 'more.stashTracked': '贮藏(仅已跟踪)',
220
+ 'more.stashStaged': '贮藏已暂存文件',
221
+ 'more.stashApplyPick': '应用贮藏...',
222
+ 'more.stashPopPick': '弹出贮藏...',
223
+ 'more.stashDropAny': '删除贮藏...',
224
+ 'more.stashClear': '删除全部贮藏...',
225
+ 'stash.none': '没有可用的贮藏',
226
+ 'more.tagDeleteRemote': '删除远程标签...',
227
+ 'more.pushTags': '推送标签...',
228
+ 'confirm.undoCommit': '撤销上次提交?提交的改动会保留在暂存区。',
229
+ 'confirm.discardAll': '放弃全部未暂存与未跟踪的更改?可通过撤回浮窗恢复(冲突文件除外)。',
230
+ 'confirm.pushForce': '强制推送会用本地分支覆盖远程(--force-with-lease,他人有新提交时会被拒绝)。继续?',
231
+ 'confirm.deleteRemoteBranch': '删除远程 {remote} 上的分支 {branch}?',
232
+ 'confirm.remoteRemove': '删除远程 {name}?其跟踪配置一并移除。',
233
+ 'confirm.stashClear': '删除全部贮藏?此操作不可撤回。',
234
+ 'confirm.pushTags': '推送全部标签到 {remote}?',
235
+ 'prompt.remoteName': '远程名称',
236
+ 'prompt.remoteUrl': '远程 URL',
237
+ 'prompt.deleteRemoteTag': '要删除的远程标签(将推送到 {remote})',
238
+ 'toolbar.more': '更多操作',
239
+ 'more.sortGit': 'Git 顺序',
240
+ 'more.sortPath': '按路径',
241
+ 'more.sortName': '按名称',
242
+ 'more.sortStatus': '按状态',
243
+ 'more.clone': '克隆...',
244
+ 'more.stashPop': '弹出最近贮藏',
245
+ 'more.deleteTag': '删除标签...',
246
+ 'more.gitOutput': '显示 Git 输出',
247
+ 'clone.title': '克隆仓库',
248
+ 'clone.url': '仓库 URL',
249
+ 'clone.dir': '目标目录(绝对路径)',
250
+ 'clone.run': '克隆',
251
+ 'prompt.deleteTag': '要删除的标签名',
252
+ 'gitLog.title': 'Git 输出',
253
+ 'gitLog.empty': '暂无 Git 运行记录',
189
254
  'toolbar.newBranch': '新建分支',
190
255
  'toolbar.fetch': '抓取(Fetch)',
191
256
  'toolbar.pull': '拉取(Pull)',
@@ -411,6 +476,70 @@ window.__ModuleLoader__.load({
411
476
  'diff.copy': 'Copy',
412
477
  'diff.copied': 'Copied',
413
478
  'toolbar.refresh': 'Refresh',
479
+ 'more.sort': 'View & Sort',
480
+ 'more.commitGroup': 'Commit',
481
+ 'more.changesGroup': 'Changes',
482
+ 'more.pullpushGroup': 'Pull, Push',
483
+ 'more.branchGroup': 'Branch',
484
+ 'more.remoteGroup': 'Remote',
485
+ 'more.stashGroup': 'Stash',
486
+ 'more.tagGroup': 'Tags',
487
+ 'more.commitStaged': 'Commit Staged...',
488
+ 'more.commitAll': 'Commit All...',
489
+ 'more.undoCommit': 'Undo Last Commit',
490
+ 'more.rebaseAbort': 'Abort Rebase',
491
+ 'more.commitStagedAmend': 'Commit Staged (Amend)...',
492
+ 'more.commitAllAmend': 'Commit All (Amend)...',
493
+ 'more.commitStagedSign': 'Commit Staged (Signed Off)...',
494
+ 'more.commitAllSign': 'Commit All (Signed Off)...',
495
+ 'prompt.commitMessage': 'Commit message',
496
+ 'more.discardAll': 'Discard All Changes...',
497
+ 'more.sync': 'Sync (Pull then Push)',
498
+ 'more.syncRebase': 'Sync (Rebase then Push)',
499
+ 'more.pullRebase': 'Pull (Rebase)',
500
+ 'more.pullFrom': 'Pull from Remote',
501
+ 'more.pushForce': 'Push (Force)...',
502
+ 'more.pushTo': 'Push to Remote',
503
+ 'more.fetchAll': 'Fetch from All Remotes',
504
+ 'more.deleteRemoteBranch': 'Delete Remote Branch...',
505
+ 'more.publish': 'Publish Branch...',
506
+ 'more.addRemote': 'Add Remote...',
507
+ 'more.removeRemote': 'Remove Remote...',
508
+ 'more.stashTracked': 'Stash (Tracked Only)',
509
+ 'more.stashStaged': 'Stash Staged',
510
+ 'more.stashApplyPick': 'Apply Stash...',
511
+ 'more.stashPopPick': 'Pop Stash...',
512
+ 'more.stashDropAny': 'Drop Stash...',
513
+ 'more.stashClear': 'Drop All Stashes...',
514
+ 'stash.none': 'No stashes available',
515
+ 'more.tagDeleteRemote': 'Delete Remote Tag...',
516
+ 'more.pushTags': 'Push Tags...',
517
+ 'confirm.undoCommit': 'Undo the last commit? Its changes stay staged.',
518
+ 'confirm.discardAll': 'Discard ALL unstaged and untracked changes? Recoverable via undo toasts (conflicted files excluded).',
519
+ 'confirm.pushForce': 'Force push overwrites the remote with this branch (--force-with-lease refuses when others pushed). Continue?',
520
+ 'confirm.deleteRemoteBranch': 'Delete branch {branch} on {remote}?',
521
+ 'confirm.remoteRemove': 'Remove remote {name}? Its tracking config goes with it.',
522
+ 'confirm.stashClear': 'Drop ALL stashes? This cannot be undone.',
523
+ 'confirm.pushTags': 'Push all tags to {remote}?',
524
+ 'prompt.remoteName': 'Remote name',
525
+ 'prompt.remoteUrl': 'Remote URL',
526
+ 'prompt.deleteRemoteTag': 'Remote tag to delete (pushed to {remote})',
527
+ 'toolbar.more': 'More actions',
528
+ 'more.sortGit': 'Git order',
529
+ 'more.sortPath': 'By path',
530
+ 'more.sortName': 'By name',
531
+ 'more.sortStatus': 'By status',
532
+ 'more.clone': 'Clone...',
533
+ 'more.stashPop': 'Pop latest stash',
534
+ 'more.deleteTag': 'Delete tag...',
535
+ 'more.gitOutput': 'Show Git output',
536
+ 'clone.title': 'Clone repository',
537
+ 'clone.url': 'Repository URL',
538
+ 'clone.dir': 'Target directory (absolute path)',
539
+ 'clone.run': 'Clone',
540
+ 'prompt.deleteTag': 'Tag name to delete',
541
+ 'gitLog.title': 'Git output',
542
+ 'gitLog.empty': 'No git runs yet',
414
543
  'toolbar.newBranch': 'New branch',
415
544
  'toolbar.fetch': 'Fetch',
416
545
  'toolbar.pull': 'Pull',
@@ -644,6 +773,70 @@ window.__ModuleLoader__.load({
644
773
  'diff.copy': '複製',
645
774
  'diff.copied': '已複製',
646
775
  'toolbar.refresh': '重新整理',
776
+ 'more.sort': '查看和排序',
777
+ 'more.commitGroup': '提交',
778
+ 'more.changesGroup': '更改',
779
+ 'more.pullpushGroup': '拉取,推送',
780
+ 'more.branchGroup': '分支',
781
+ 'more.remoteGroup': '遠端',
782
+ 'more.stashGroup': '儲存',
783
+ 'more.tagGroup': '標籤',
784
+ 'more.commitStaged': '提交已暫存檔案...',
785
+ 'more.commitAll': '全部提交...',
786
+ 'more.undoCommit': '撤銷上次提交',
787
+ 'more.rebaseAbort': '中止變基',
788
+ 'more.commitStagedAmend': '提交已暫存檔案(修改)...',
789
+ 'more.commitAllAmend': '全部提交(修改)...',
790
+ 'more.commitStagedSign': '提交已暫存檔案(已簽收)...',
791
+ 'more.commitAllSign': '全部提交(已簽收)...',
792
+ 'prompt.commitMessage': '提交資訊',
793
+ 'more.discardAll': '放棄所有變更...',
794
+ 'more.sync': '同步(拉取並推送)',
795
+ 'more.syncRebase': '同步(變基並推送)',
796
+ 'more.pullRebase': '拉取(變基)',
797
+ 'more.pullFrom': '從遠端拉取',
798
+ 'more.pushForce': '推送(強制)...',
799
+ 'more.pushTo': '推送到遠端',
800
+ 'more.fetchAll': '從所有遠端抓取',
801
+ 'more.deleteRemoteBranch': '刪除遠端分支...',
802
+ 'more.publish': '發布分支...',
803
+ 'more.addRemote': '新增遠端...',
804
+ 'more.removeRemote': '刪除遠端...',
805
+ 'more.stashTracked': '儲存(僅已追蹤)',
806
+ 'more.stashStaged': '儲存已暫存檔案',
807
+ 'more.stashApplyPick': '套用儲存...',
808
+ 'more.stashPopPick': '彈出儲存...',
809
+ 'more.stashDropAny': '刪除儲存...',
810
+ 'more.stashClear': '刪除全部儲存...',
811
+ 'stash.none': '沒有可用的儲存',
812
+ 'more.tagDeleteRemote': '刪除遠端標籤...',
813
+ 'more.pushTags': '推送標籤...',
814
+ 'confirm.undoCommit': '撤銷上次提交?提交的變更會保留在暫存區。',
815
+ 'confirm.discardAll': '放棄全部未暫存與未追蹤的變更?可透過復原浮窗恢復(衝突檔案除外)。',
816
+ 'confirm.pushForce': '強制推送會以本地分支覆寫遠端(--force-with-lease,他人有新提交時會被拒絕)。繼續?',
817
+ 'confirm.deleteRemoteBranch': '刪除遠端 {remote} 上的分支 {branch}?',
818
+ 'confirm.remoteRemove': '刪除遠端 {name}?其追蹤設定一併移除。',
819
+ 'confirm.stashClear': '刪除全部儲存?此操作不可復原。',
820
+ 'confirm.pushTags': '推送全部標籤到 {remote}?',
821
+ 'prompt.remoteName': '遠端名稱',
822
+ 'prompt.remoteUrl': '遠端 URL',
823
+ 'prompt.deleteRemoteTag': '要刪除的遠端標籤(將推送到 {remote})',
824
+ 'toolbar.more': '更多操作',
825
+ 'more.sortGit': 'Git 順序',
826
+ 'more.sortPath': '按路徑',
827
+ 'more.sortName': '按名稱',
828
+ 'more.sortStatus': '按狀態',
829
+ 'more.clone': '複製存放庫...',
830
+ 'more.stashPop': '彈出最近貯藏',
831
+ 'more.deleteTag': '刪除標籤...',
832
+ 'more.gitOutput': '顯示 Git 輸出',
833
+ 'clone.title': '複製存放庫',
834
+ 'clone.url': '存放庫 URL',
835
+ 'clone.dir': '目標目錄(絕對路徑)',
836
+ 'clone.run': '複製',
837
+ 'prompt.deleteTag': '要刪除的標籤名',
838
+ 'gitLog.title': 'Git 輸出',
839
+ 'gitLog.empty': '暫無 Git 執行記錄',
647
840
  'toolbar.newBranch': '新建分支',
648
841
  'toolbar.fetch': '抓取',
649
842
  'toolbar.pull': '拉取',
@@ -869,6 +1062,70 @@ window.__ModuleLoader__.load({
869
1062
  'diff.copy': '複製',
870
1063
  'diff.copied': '已複製',
871
1064
  'toolbar.refresh': '重新整理',
1065
+ 'more.sort': '查看和排序',
1066
+ 'more.commitGroup': '提交',
1067
+ 'more.changesGroup': '變更',
1068
+ 'more.pullpushGroup': '拉取,推送',
1069
+ 'more.branchGroup': '分支',
1070
+ 'more.remoteGroup': '遠端',
1071
+ 'more.stashGroup': '擱置',
1072
+ 'more.tagGroup': '標籤',
1073
+ 'more.commitStaged': '提交已暫存檔案...',
1074
+ 'more.commitAll': '全部提交...',
1075
+ 'more.undoCommit': '復原上次提交',
1076
+ 'more.rebaseAbort': '中止變基',
1077
+ 'more.commitStagedAmend': '提交已暫存檔案(修改)...',
1078
+ 'more.commitAllAmend': '全部提交(修改)...',
1079
+ 'more.commitStagedSign': '提交已暫存檔案(已簽核)...',
1080
+ 'more.commitAllSign': '全部提交(已簽核)...',
1081
+ 'prompt.commitMessage': '提交資訊',
1082
+ 'more.discardAll': '捨棄所有變更...',
1083
+ 'more.sync': '同步(拉取並推送)',
1084
+ 'more.syncRebase': '同步(變基並推送)',
1085
+ 'more.pullRebase': '拉取(變基)',
1086
+ 'more.pullFrom': '從遠端拉取',
1087
+ 'more.pushForce': '推送(強制)...',
1088
+ 'more.pushTo': '推送到遠端',
1089
+ 'more.fetchAll': '從所有遠端擷取',
1090
+ 'more.deleteRemoteBranch': '刪除遠端分支...',
1091
+ 'more.publish': '發佈分支...',
1092
+ 'more.addRemote': '新增遠端...',
1093
+ 'more.removeRemote': '移除遠端...',
1094
+ 'more.stashTracked': '擱置(僅已追蹤)',
1095
+ 'more.stashStaged': '擱置已暫存檔案',
1096
+ 'more.stashApplyPick': '套用擱置...',
1097
+ 'more.stashPopPick': '取出擱置...',
1098
+ 'more.stashDropAny': '刪除擱置...',
1099
+ 'more.stashClear': '刪除全部擱置...',
1100
+ 'stash.none': '沒有可用的擱置',
1101
+ 'more.tagDeleteRemote': '刪除遠端標籤...',
1102
+ 'more.pushTags': '推送標籤...',
1103
+ 'confirm.undoCommit': '復原上次提交?提交的變更會保留在暫存區。',
1104
+ 'confirm.discardAll': '捨棄全部未暫存與未追蹤的變更?可透過復原浮窗復原(衝突檔案除外)。',
1105
+ 'confirm.pushForce': '強制推送會以本地分支覆寫遠端(--force-with-lease,他人有新提交時會被拒絕)。繼續?',
1106
+ 'confirm.deleteRemoteBranch': '刪除遠端 {remote} 上的分支 {branch}?',
1107
+ 'confirm.remoteRemove': '移除遠端 {name}?其追蹤設定一併移除。',
1108
+ 'confirm.stashClear': '刪除全部擱置?此作業無法復原。',
1109
+ 'confirm.pushTags': '推送全部標籤到 {remote}?',
1110
+ 'prompt.remoteName': '遠端名稱',
1111
+ 'prompt.remoteUrl': '遠端 URL',
1112
+ 'prompt.deleteRemoteTag': '要刪除的遠端標籤(將推送至 {remote})',
1113
+ 'toolbar.more': '更多操作',
1114
+ 'more.sortGit': 'Git 順序',
1115
+ 'more.sortPath': '按路徑',
1116
+ 'more.sortName': '按名稱',
1117
+ 'more.sortStatus': '按狀態',
1118
+ 'more.clone': '複製存放庫...',
1119
+ 'more.stashPop': '取出最近擱置',
1120
+ 'more.deleteTag': '刪除標籤...',
1121
+ 'more.gitOutput': '顯示 Git 輸出',
1122
+ 'clone.title': '複製存放庫',
1123
+ 'clone.url': '存放庫 URL',
1124
+ 'clone.dir': '目標目錄(絕對路徑)',
1125
+ 'clone.run': '複製',
1126
+ 'prompt.deleteTag': '要刪除的標籤名',
1127
+ 'gitLog.title': 'Git 輸出',
1128
+ 'gitLog.empty': '暫無 Git 執行記錄',
872
1129
  'toolbar.newBranch': '新建分支',
873
1130
  'toolbar.fetch': '抓取',
874
1131
  'toolbar.pull': '拉取',
@@ -1094,6 +1351,70 @@ window.__ModuleLoader__.load({
1094
1351
  'diff.copy': '複製',
1095
1352
  'diff.copied': '已複製',
1096
1353
  'toolbar.refresh': '重新整理',
1354
+ 'more.sort': '查看和排序',
1355
+ 'more.commitGroup': '提交',
1356
+ 'more.changesGroup': '更改',
1357
+ 'more.pullpushGroup': '拉取,推送',
1358
+ 'more.branchGroup': '分支',
1359
+ 'more.remoteGroup': '遠端',
1360
+ 'more.stashGroup': '儲存',
1361
+ 'more.tagGroup': '標籤',
1362
+ 'more.commitStaged': '提交已暫存檔案...',
1363
+ 'more.commitAll': '全部提交...',
1364
+ 'more.undoCommit': '撤銷上次提交',
1365
+ 'more.rebaseAbort': '中止變基',
1366
+ 'more.commitStagedAmend': '提交已暫存檔案(修改)...',
1367
+ 'more.commitAllAmend': '全部提交(修改)...',
1368
+ 'more.commitStagedSign': '提交已暫存檔案(已簽收)...',
1369
+ 'more.commitAllSign': '全部提交(已簽收)...',
1370
+ 'prompt.commitMessage': '提交資訊',
1371
+ 'more.discardAll': '放棄所有變更...',
1372
+ 'more.sync': '同步(拉取並推送)',
1373
+ 'more.syncRebase': '同步(變基並推送)',
1374
+ 'more.pullRebase': '拉取(變基)',
1375
+ 'more.pullFrom': '從遠端拉取',
1376
+ 'more.pushForce': '推送(強制)...',
1377
+ 'more.pushTo': '推送到遠端',
1378
+ 'more.fetchAll': '從所有遠端抓取',
1379
+ 'more.deleteRemoteBranch': '刪除遠端分支...',
1380
+ 'more.publish': '發布分支...',
1381
+ 'more.addRemote': '新增遠端...',
1382
+ 'more.removeRemote': '刪除遠端...',
1383
+ 'more.stashTracked': '儲存(僅已追蹤)',
1384
+ 'more.stashStaged': '儲存已暫存檔案',
1385
+ 'more.stashApplyPick': '套用儲存...',
1386
+ 'more.stashPopPick': '彈出儲存...',
1387
+ 'more.stashDropAny': '刪除儲存...',
1388
+ 'more.stashClear': '刪除全部儲存...',
1389
+ 'stash.none': '沒有可用的儲存',
1390
+ 'more.tagDeleteRemote': '刪除遠端標籤...',
1391
+ 'more.pushTags': '推送標籤...',
1392
+ 'confirm.undoCommit': '撤銷上次提交?提交的變更會保留在暫存區。',
1393
+ 'confirm.discardAll': '放棄全部未暫存與未追蹤的變更?可透過復原浮窗恢復(衝突檔案除外)。',
1394
+ 'confirm.pushForce': '強制推送會以本地分支覆寫遠端(--force-with-lease,他人有新提交時會被拒絕)。繼續?',
1395
+ 'confirm.deleteRemoteBranch': '刪除遠端 {remote} 上的分支 {branch}?',
1396
+ 'confirm.remoteRemove': '刪除遠端 {name}?其追蹤設定一併移除。',
1397
+ 'confirm.stashClear': '刪除全部儲存?此操作不可復原。',
1398
+ 'confirm.pushTags': '推送全部標籤到 {remote}?',
1399
+ 'prompt.remoteName': '遠端名稱',
1400
+ 'prompt.remoteUrl': '遠端 URL',
1401
+ 'prompt.deleteRemoteTag': '要刪除的遠端標籤(將推送到 {remote})',
1402
+ 'toolbar.more': '更多操作',
1403
+ 'more.sortGit': 'Git 順序',
1404
+ 'more.sortPath': '按路徑',
1405
+ 'more.sortName': '按名稱',
1406
+ 'more.sortStatus': '按狀態',
1407
+ 'more.clone': '複製存放庫...',
1408
+ 'more.stashPop': '彈出最近貯藏',
1409
+ 'more.deleteTag': '刪除標籤...',
1410
+ 'more.gitOutput': '顯示 Git 輸出',
1411
+ 'clone.title': '複製存放庫',
1412
+ 'clone.url': '存放庫 URL',
1413
+ 'clone.dir': '目標目錄(絕對路徑)',
1414
+ 'clone.run': '複製',
1415
+ 'prompt.deleteTag': '要刪除的標籤名',
1416
+ 'gitLog.title': 'Git 輸出',
1417
+ 'gitLog.empty': '暫無 Git 執行記錄',
1097
1418
  'toolbar.newBranch': '新建分支',
1098
1419
  'toolbar.fetch': '抓取',
1099
1420
  'toolbar.pull': '拉取',
@@ -1319,6 +1640,70 @@ window.__ModuleLoader__.load({
1319
1640
  'diff.copy': 'コピー',
1320
1641
  'diff.copied': 'コピーしました',
1321
1642
  'toolbar.refresh': '更新',
1643
+ 'more.sort': '表示と並べ替え',
1644
+ 'more.commitGroup': 'コミット',
1645
+ 'more.changesGroup': '変更',
1646
+ 'more.pullpushGroup': 'プル,プッシュ',
1647
+ 'more.branchGroup': 'ブランチ',
1648
+ 'more.remoteGroup': 'リモート',
1649
+ 'more.stashGroup': 'スタッシュ',
1650
+ 'more.tagGroup': 'タグ',
1651
+ 'more.commitStaged': 'ステージ済みをコミット...',
1652
+ 'more.commitAll': 'すべてコミット...',
1653
+ 'more.undoCommit': '最後のコミットを取り消す',
1654
+ 'more.rebaseAbort': 'リベースを中止',
1655
+ 'more.commitStagedAmend': 'ステージ済みをコミット(修正)...',
1656
+ 'more.commitAllAmend': 'すべてコミット(修正)...',
1657
+ 'more.commitStagedSign': 'ステージ済みをコミット(署名)...',
1658
+ 'more.commitAllSign': 'すべてコミット(署名)...',
1659
+ 'prompt.commitMessage': 'コミットメッセージ',
1660
+ 'more.discardAll': 'すべての変更を破棄...',
1661
+ 'more.sync': '同期(プルしてプッシュ)',
1662
+ 'more.syncRebase': '同期(リベースしてプッシュ)',
1663
+ 'more.pullRebase': 'プル(リベース)',
1664
+ 'more.pullFrom': 'リモートからプル',
1665
+ 'more.pushForce': '強制プッシュ...',
1666
+ 'more.pushTo': 'リモートへプッシュ',
1667
+ 'more.fetchAll': '全リモートからフェッチ',
1668
+ 'more.deleteRemoteBranch': 'リモートブランチを削除...',
1669
+ 'more.publish': 'ブランチを公開...',
1670
+ 'more.addRemote': 'リモートを追加...',
1671
+ 'more.removeRemote': 'リモートを削除...',
1672
+ 'more.stashTracked': 'スタッシュ(追跡済みのみ)',
1673
+ 'more.stashStaged': 'ステージ済みをスタッシュ',
1674
+ 'more.stashApplyPick': 'スタッシュを適用...',
1675
+ 'more.stashPopPick': 'スタッシュをポップ...',
1676
+ 'more.stashDropAny': 'スタッシュを削除...',
1677
+ 'more.stashClear': 'すべてのスタッシュを削除...',
1678
+ 'stash.none': '利用可能なスタッシュはありません',
1679
+ 'more.tagDeleteRemote': 'リモートタグを削除...',
1680
+ 'more.pushTags': 'タグをプッシュ...',
1681
+ 'confirm.undoCommit': '最後のコミットを取り消しますか?変更はステージに残ります。',
1682
+ 'confirm.discardAll': 'ステージ外と未追跡のすべての変更を破棄しますか?元に戻すトーストで復元できます(競合ファイルを除く)。',
1683
+ 'confirm.pushForce': '強制プッシュはリモートをこのブランチで上書きします(--force-with-lease により他者の更新時は拒否)。続行しますか?',
1684
+ 'confirm.deleteRemoteBranch': '{remote} のブランチ {branch} を削除しますか?',
1685
+ 'confirm.remoteRemove': 'リモート {name} を削除しますか?追跡設定も削除されます。',
1686
+ 'confirm.stashClear': 'すべてのスタッシュを削除しますか?この操作は元に戻せません。',
1687
+ 'confirm.pushTags': 'すべてのタグを {remote} にプッシュしますか?',
1688
+ 'prompt.remoteName': 'リモート名',
1689
+ 'prompt.remoteUrl': 'リモート URL',
1690
+ 'prompt.deleteRemoteTag': '削除するリモートタグ({remote} にプッシュ)',
1691
+ 'toolbar.more': 'その他の操作',
1692
+ 'more.sortGit': 'Git の順序',
1693
+ 'more.sortPath': 'パス順',
1694
+ 'more.sortName': '名前順',
1695
+ 'more.sortStatus': '状態順',
1696
+ 'more.clone': 'リポジトリを複製...',
1697
+ 'more.stashPop': '最新のスタッシュをポップ',
1698
+ 'more.deleteTag': 'タグを削除...',
1699
+ 'more.gitOutput': 'Git 出力を表示',
1700
+ 'clone.title': 'リポジトリを複製',
1701
+ 'clone.url': 'リポジトリ URL',
1702
+ 'clone.dir': '複製先ディレクトリ(絶対パス)',
1703
+ 'clone.run': '複製',
1704
+ 'prompt.deleteTag': '削除するタグ名',
1705
+ 'gitLog.title': 'Git 出力',
1706
+ 'gitLog.empty': 'Git 実行履歴はまだありません',
1322
1707
  'toolbar.newBranch': '新しいブランチ',
1323
1708
  'toolbar.fetch': 'フェッチ',
1324
1709
  'toolbar.pull': 'プル',
@@ -1544,6 +1929,70 @@ window.__ModuleLoader__.load({
1544
1929
  'diff.copy': '복사',
1545
1930
  'diff.copied': '복사됨',
1546
1931
  'toolbar.refresh': '새로 고침',
1932
+ 'more.sort': '보기 및 정렬',
1933
+ 'more.commitGroup': '커밋',
1934
+ 'more.changesGroup': '변경',
1935
+ 'more.pullpushGroup': '풀, 푸시',
1936
+ 'more.branchGroup': '브랜치',
1937
+ 'more.remoteGroup': '원격',
1938
+ 'more.stashGroup': '스태시',
1939
+ 'more.tagGroup': '태그',
1940
+ 'more.commitStaged': '스테이지된 파일 커밋...',
1941
+ 'more.commitAll': '모두 커밋...',
1942
+ 'more.undoCommit': '마지막 커밋 취소',
1943
+ 'more.rebaseAbort': '리베이스 중단',
1944
+ 'more.commitStagedAmend': '스테이지된 파일 커밋(수정)...',
1945
+ 'more.commitAllAmend': '모두 커밋(수정)...',
1946
+ 'more.commitStagedSign': '스테이지된 파일 커밋(서명)...',
1947
+ 'more.commitAllSign': '모두 커밋(서명)...',
1948
+ 'prompt.commitMessage': '커밋 메시지',
1949
+ 'more.discardAll': '모든 변경 내용 취소...',
1950
+ 'more.sync': '동기화(풀 후 푸시)',
1951
+ 'more.syncRebase': '동기화(리베이스 후 푸시)',
1952
+ 'more.pullRebase': '풀(리베이스)',
1953
+ 'more.pullFrom': '원격에서 풀',
1954
+ 'more.pushForce': '강제 푸시...',
1955
+ 'more.pushTo': '원격으로 푸시',
1956
+ 'more.fetchAll': '모든 원격에서 페치',
1957
+ 'more.deleteRemoteBranch': '원격 브랜치 삭제...',
1958
+ 'more.publish': '브랜치 게시...',
1959
+ 'more.addRemote': '원격 추가...',
1960
+ 'more.removeRemote': '원격 제거...',
1961
+ 'more.stashTracked': '스태시(추적된 파일만)',
1962
+ 'more.stashStaged': '스테이지된 파일 스태시',
1963
+ 'more.stashApplyPick': '스태시 적용...',
1964
+ 'more.stashPopPick': '스태시 팝...',
1965
+ 'more.stashDropAny': '스태시 삭제...',
1966
+ 'more.stashClear': '모든 스태시 삭제...',
1967
+ 'stash.none': '사용 가능한 스태시가 없습니다',
1968
+ 'more.tagDeleteRemote': '원격 태그 삭제...',
1969
+ 'more.pushTags': '태그 푸시...',
1970
+ 'confirm.undoCommit': '마지막 커밋을 취소할까요?변경 사항은 스테이지에 유지됩니다.',
1971
+ 'confirm.discardAll': '스테이지되지 않은 모든 변경과 추적되지 않는 파일을 취소할까요?실행 취소 토스트로 복구할 수 있습니다(충돌 파일 제외).',
1972
+ 'confirm.pushForce': '강제 푸시는 원격을 이 브랜치로 덮어씁니다(--force-with-lease, 다른 사람이 푸시했으면 거부됨).계속할까요?',
1973
+ 'confirm.deleteRemoteBranch': '{remote}의 브랜치 {branch}를 삭제할까요?',
1974
+ 'confirm.remoteRemove': '원격 {name}을(를) 제거할까요?추적 설정도 함께 제거됩니다.',
1975
+ 'confirm.stashClear': '모든 스태시를 삭제할까요?이 작업은 되돌릴 수 없습니다.',
1976
+ 'confirm.pushTags': '모든 태그를 {remote}에 푸시할까요?',
1977
+ 'prompt.remoteName': '원격 이름',
1978
+ 'prompt.remoteUrl': '원격 URL',
1979
+ 'prompt.deleteRemoteTag': '삭제할 원격 태그({remote}로 푸시됨)',
1980
+ 'toolbar.more': '더 보기',
1981
+ 'more.sortGit': 'Git 순서',
1982
+ 'more.sortPath': '경로순',
1983
+ 'more.sortName': '이름순',
1984
+ 'more.sortStatus': '상태순',
1985
+ 'more.clone': '복제...',
1986
+ 'more.stashPop': '최신 스태시 팝',
1987
+ 'more.deleteTag': '태그 삭제...',
1988
+ 'more.gitOutput': 'Git 출력 표시',
1989
+ 'clone.title': '리포지토리 복제',
1990
+ 'clone.url': '리포지토리 URL',
1991
+ 'clone.dir': '대상 디렉터리(절대 경로)',
1992
+ 'clone.run': '복제',
1993
+ 'prompt.deleteTag': '삭제할 태그 이름',
1994
+ 'gitLog.title': 'Git 출력',
1995
+ 'gitLog.empty': '아직 Git 실행 기록이 없습니다',
1547
1996
  'toolbar.newBranch': '새 브랜치',
1548
1997
  'toolbar.fetch': '페치',
1549
1998
  'toolbar.pull': '풀',
@@ -1769,6 +2218,70 @@ window.__ModuleLoader__.load({
1769
2218
  'diff.copy': 'Kopieren',
1770
2219
  'diff.copied': 'Kopiert',
1771
2220
  'toolbar.refresh': 'Aktualisieren',
2221
+ 'more.sort': 'Anzeigen & Sortieren',
2222
+ 'more.commitGroup': 'Commit',
2223
+ 'more.changesGroup': 'Änderungen',
2224
+ 'more.pullpushGroup': 'Pull, Push',
2225
+ 'more.branchGroup': 'Branch',
2226
+ 'more.remoteGroup': 'Remote',
2227
+ 'more.stashGroup': 'Stash',
2228
+ 'more.tagGroup': 'Tags',
2229
+ 'more.commitStaged': 'Staged committen...',
2230
+ 'more.commitAll': 'Alles committen...',
2231
+ 'more.undoCommit': 'Letzten Commit zurücknehmen',
2232
+ 'more.rebaseAbort': 'Rebase abbrechen',
2233
+ 'more.commitStagedAmend': 'Staged committen (Amend)...',
2234
+ 'more.commitAllAmend': 'Alles committen (Amend)...',
2235
+ 'more.commitStagedSign': 'Staged committen (Signed Off)...',
2236
+ 'more.commitAllSign': 'Alles committen (Signed Off)...',
2237
+ 'prompt.commitMessage': 'Commit-Nachricht',
2238
+ 'more.discardAll': 'Alle Änderungen verwerfen...',
2239
+ 'more.sync': 'Synchronisieren (Pull, dann Push)',
2240
+ 'more.syncRebase': 'Synchronisieren (Rebase, dann Push)',
2241
+ 'more.pullRebase': 'Pull (Rebase)',
2242
+ 'more.pullFrom': 'Von Remote pullen',
2243
+ 'more.pushForce': 'Push (Erzwingen)...',
2244
+ 'more.pushTo': 'Auf Remote pushen',
2245
+ 'more.fetchAll': 'Von allen Remotes holen',
2246
+ 'more.deleteRemoteBranch': 'Remote-Branch löschen...',
2247
+ 'more.publish': 'Branch veröffentlichen...',
2248
+ 'more.addRemote': 'Remote hinzufügen...',
2249
+ 'more.removeRemote': 'Remote entfernen...',
2250
+ 'more.stashTracked': 'Stash (nur getrackt)',
2251
+ 'more.stashStaged': 'Staged stashed',
2252
+ 'more.stashApplyPick': 'Stash anwenden...',
2253
+ 'more.stashPopPick': 'Stash poppen...',
2254
+ 'more.stashDropAny': 'Stash löschen...',
2255
+ 'more.stashClear': 'Alle Stashes löschen...',
2256
+ 'stash.none': 'Keine Stashes vorhanden',
2257
+ 'more.tagDeleteRemote': 'Remote-Tag löschen...',
2258
+ 'more.pushTags': 'Tags pushen...',
2259
+ 'confirm.undoCommit': 'Letzten Commit zurücknehmen? Die Änderungen bleiben gestaged.',
2260
+ 'confirm.discardAll': 'ALLE ungestageden und ungetrackten Änderungen verwerfen? Über Undo-Toasts wiederherstellbar (Konfliktdateien ausgenommen).',
2261
+ 'confirm.pushForce': 'Force-Push überschreibt das Remote mit diesem Branch (--force-with-lease lehnt ab, wenn andere gepusht haben). Fortfahren?',
2262
+ 'confirm.deleteRemoteBranch': 'Branch {branch} auf {remote} löschen?',
2263
+ 'confirm.remoteRemove': 'Remote {name} entfernen? Die Tracking-Konfiguration wird mitentfernt.',
2264
+ 'confirm.stashClear': 'ALLE Stashes löschen? Das kann nicht rückgängig gemacht werden.',
2265
+ 'confirm.pushTags': 'Alle Tags auf {remote} pushen?',
2266
+ 'prompt.remoteName': 'Remote-Name',
2267
+ 'prompt.remoteUrl': 'Remote-URL',
2268
+ 'prompt.deleteRemoteTag': 'Zu löschendes Remote-Tag (wird nach {remote} gepusht)',
2269
+ 'toolbar.more': 'Weitere Aktionen',
2270
+ 'more.sortGit': 'Git-Reihenfolge',
2271
+ 'more.sortPath': 'Nach Pfad',
2272
+ 'more.sortName': 'Nach Name',
2273
+ 'more.sortStatus': 'Nach Status',
2274
+ 'more.clone': 'Repository klonen...',
2275
+ 'more.stashPop': 'Letzten Stash poppen',
2276
+ 'more.deleteTag': 'Tag löschen...',
2277
+ 'more.gitOutput': 'Git-Ausgabe anzeigen',
2278
+ 'clone.title': 'Repository klonen',
2279
+ 'clone.url': 'Repository-URL',
2280
+ 'clone.dir': 'Zielverzeichnis (absoluter Pfad)',
2281
+ 'clone.run': 'Klonen',
2282
+ 'prompt.deleteTag': 'Zu löschender Tag-Name',
2283
+ 'gitLog.title': 'Git-Ausgabe',
2284
+ 'gitLog.empty': 'Noch keine Git-Ausführungen',
1772
2285
  'toolbar.newBranch': 'Neuer Branch',
1773
2286
  'toolbar.fetch': 'Fetch',
1774
2287
  'toolbar.pull': 'Pull',
@@ -1994,6 +2507,70 @@ window.__ModuleLoader__.load({
1994
2507
  'diff.copy': 'Copier',
1995
2508
  'diff.copied': 'Copié',
1996
2509
  'toolbar.refresh': 'Rafraîchir',
2510
+ 'more.sort': 'Afficher & trier',
2511
+ 'more.commitGroup': 'Commit',
2512
+ 'more.changesGroup': 'Modifications',
2513
+ 'more.pullpushGroup': 'Pull, Push',
2514
+ 'more.branchGroup': 'Branche',
2515
+ 'more.remoteGroup': 'Distant',
2516
+ 'more.stashGroup': 'Remisage',
2517
+ 'more.tagGroup': 'Étiquettes',
2518
+ 'more.commitStaged': 'Commit des fichiers indexés...',
2519
+ 'more.commitAll': 'Tout committer...',
2520
+ 'more.undoCommit': 'Annuler le dernier commit',
2521
+ 'more.rebaseAbort': 'Abandonner le rebase',
2522
+ 'more.commitStagedAmend': 'Commit indexé (Amend)...',
2523
+ 'more.commitAllAmend': 'Tout committer (Amend)...',
2524
+ 'more.commitStagedSign': 'Commit indexé (Signé)...',
2525
+ 'more.commitAllSign': 'Tout committer (Signé)...',
2526
+ 'prompt.commitMessage': 'Message de commit',
2527
+ 'more.discardAll': 'Abandonner toutes les modifications...',
2528
+ 'more.sync': 'Synchroniser (pull puis push)',
2529
+ 'more.syncRebase': 'Synchroniser (rebase puis push)',
2530
+ 'more.pullRebase': 'Pull (Rebase)',
2531
+ 'more.pullFrom': 'Pull depuis un dépôt distant',
2532
+ 'more.pushForce': 'Push (Forcer)...',
2533
+ 'more.pushTo': 'Push vers un distant',
2534
+ 'more.fetchAll': 'Récupérer de tous les distants',
2535
+ 'more.deleteRemoteBranch': 'Supprimer la branche distante...',
2536
+ 'more.publish': 'Publier la branche...',
2537
+ 'more.addRemote': 'Ajouter un distant...',
2538
+ 'more.removeRemote': 'Supprimer le distant...',
2539
+ 'more.stashTracked': 'Remisage (suivis seulement)',
2540
+ 'more.stashStaged': 'Remiser les fichiers indexés',
2541
+ 'more.stashApplyPick': 'Appliquer un remisage...',
2542
+ 'more.stashPopPick': 'Dépiler un remisage...',
2543
+ 'more.stashDropAny': 'Supprimer un remisage...',
2544
+ 'more.stashClear': 'Supprimer tous les remisages...',
2545
+ 'stash.none': 'Aucun remisage disponible',
2546
+ 'more.tagDeleteRemote': 'Supprimer l\'étiquette distante...',
2547
+ 'more.pushTags': 'Pousser les étiquettes...',
2548
+ 'confirm.undoCommit': 'Annuler le dernier commit ? Ses modifications restent indexées.',
2549
+ 'confirm.discardAll': 'Abandonner TOUTES les modifications non indexées et non suivies ? Récupérable via les toasts d\'annulation (fichiers en conflit exclus).',
2550
+ 'confirm.pushForce': 'Le forçage du push écrasera le distant avec cette branche (--force-with-lease refuse si d\'autres ont poussé). Continuer ?',
2551
+ 'confirm.deleteRemoteBranch': 'Supprimer la branche {branch} sur {remote} ?',
2552
+ 'confirm.remoteRemove': 'Supprimer le distant {name} ? Sa configuration de suivi part avec lui.',
2553
+ 'confirm.stashClear': 'Supprimer TOUS les remisages ? Irréversible.',
2554
+ 'confirm.pushTags': 'Pousser toutes les étiquettes vers {remote} ?',
2555
+ 'prompt.remoteName': 'Nom du distant',
2556
+ 'prompt.remoteUrl': 'URL du distant',
2557
+ 'prompt.deleteRemoteTag': 'Étiquette distante à supprimer (poussée vers {remote})',
2558
+ 'toolbar.more': 'Actions supplémentaires',
2559
+ 'more.sortGit': 'Ordre Git',
2560
+ 'more.sortPath': 'Par chemin',
2561
+ 'more.sortName': 'Par nom',
2562
+ 'more.sortStatus': 'Par statut',
2563
+ 'more.clone': 'Cloner...',
2564
+ 'more.stashPop': 'Pop du dernier stash',
2565
+ 'more.deleteTag': 'Supprimer l\'étiquette...',
2566
+ 'more.gitOutput': 'Afficher la sortie Git',
2567
+ 'clone.title': 'Cloner le dépôt',
2568
+ 'clone.url': 'URL du dépôt',
2569
+ 'clone.dir': 'Répertoire cible (chemin absolu)',
2570
+ 'clone.run': 'Cloner',
2571
+ 'prompt.deleteTag': 'Nom de l\'étiquette à supprimer',
2572
+ 'gitLog.title': 'Sortie Git',
2573
+ 'gitLog.empty': 'Aucune exécution Git pour l\'instant',
1997
2574
  'toolbar.newBranch': 'Nouvelle branche',
1998
2575
  'toolbar.fetch': 'Récupérer',
1999
2576
  'toolbar.pull': 'Tirer',
@@ -2219,6 +2796,70 @@ window.__ModuleLoader__.load({
2219
2796
  'diff.copy': 'Копировать',
2220
2797
  'diff.copied': 'Скопировано',
2221
2798
  'toolbar.refresh': 'Обновить',
2799
+ 'more.sort': 'Просмотр и сортировка',
2800
+ 'more.commitGroup': 'Фиксация',
2801
+ 'more.changesGroup': 'Изменения',
2802
+ 'more.pullpushGroup': 'Изъять, отправить',
2803
+ 'more.branchGroup': 'Ветвь',
2804
+ 'more.remoteGroup': 'Сервер',
2805
+ 'more.stashGroup': 'Отложить',
2806
+ 'more.tagGroup': 'Метки',
2807
+ 'more.commitStaged': 'Фиксация индексированного...',
2808
+ 'more.commitAll': 'Фиксация всего...',
2809
+ 'more.undoCommit': 'Отменить последнюю фиксацию',
2810
+ 'more.rebaseAbort': 'Прервать перебазирование',
2811
+ 'more.commitStagedAmend': 'Фиксация индексированного (Amend)...',
2812
+ 'more.commitAllAmend': 'Фиксация всего (Amend)...',
2813
+ 'more.commitStagedSign': 'Фиксация индексированного (С подписью)...',
2814
+ 'more.commitAllSign': 'Фиксация всего (С подписью)...',
2815
+ 'prompt.commitMessage': 'Сообщение фиксации',
2816
+ 'more.discardAll': 'Отменить все изменения...',
2817
+ 'more.sync': 'Синхронизировать (изъять, затем отправить)',
2818
+ 'more.syncRebase': 'Синхронизировать (перебазировать, затем отправить)',
2819
+ 'more.pullRebase': 'Изъять (перебазирование)',
2820
+ 'more.pullFrom': 'Изъять с сервера',
2821
+ 'more.pushForce': 'Отправить (принудительно)...',
2822
+ 'more.pushTo': 'Отправить на сервер',
2823
+ 'more.fetchAll': 'Изъять со всех серверов',
2824
+ 'more.deleteRemoteBranch': 'Удалить ветвь на сервере...',
2825
+ 'more.publish': 'Опубликовать ветвь...',
2826
+ 'more.addRemote': 'Добавить сервер...',
2827
+ 'more.removeRemote': 'Удалить сервер...',
2828
+ 'more.stashTracked': 'Отложить (только отслеживаемое)',
2829
+ 'more.stashStaged': 'Отложить индексированное',
2830
+ 'more.stashApplyPick': 'Применить отложенное...',
2831
+ 'more.stashPopPick': 'Снять отложенное...',
2832
+ 'more.stashDropAny': 'Удалить отложенное...',
2833
+ 'more.stashClear': 'Удалить всё отложенное...',
2834
+ 'stash.none': 'Нет отложенных изменений',
2835
+ 'more.tagDeleteRemote': 'Удалить метку на сервере...',
2836
+ 'more.pushTags': 'Отправить метки...',
2837
+ 'confirm.undoCommit': 'Отменить последнюю фиксацию? Её изменения останутся в индексе.',
2838
+ 'confirm.discardAll': 'Отменить ВСЕ непроиндексированные и неотслеживаемые изменения? Можно восстановить через уведомления (кроме конфликтных файлов).',
2839
+ 'confirm.pushForce': 'Принудительная отправка перезапишет сервер этой ветвью (--force-with-lease отклонит при чужих отправках). Продолжить?',
2840
+ 'confirm.deleteRemoteBranch': 'Удалить ветвь {branch} на {remote}?',
2841
+ 'confirm.remoteRemove': 'Удалить сервер {name}? Конфигурация отслеживания будет удалена.',
2842
+ 'confirm.stashClear': 'Удалить ВСЁ отложенное? Это необратимо.',
2843
+ 'confirm.pushTags': 'Отправить все метки на {remote}?',
2844
+ 'prompt.remoteName': 'Имя сервера',
2845
+ 'prompt.remoteUrl': 'URL сервера',
2846
+ 'prompt.deleteRemoteTag': 'Метка для удаления (будет удалена с {remote})',
2847
+ 'toolbar.more': 'Дополнительные действия',
2848
+ 'more.sortGit': 'Порядок Git',
2849
+ 'more.sortPath': 'По пути',
2850
+ 'more.sortName': 'По имени',
2851
+ 'more.sortStatus': 'По состоянию',
2852
+ 'more.clone': 'Клонировать...',
2853
+ 'more.stashPop': 'Pop последнего stash',
2854
+ 'more.deleteTag': 'Удалить тег...',
2855
+ 'more.gitOutput': 'Показать вывод Git',
2856
+ 'clone.title': 'Клонировать репозиторий',
2857
+ 'clone.url': 'URL репозитория',
2858
+ 'clone.dir': 'Целевой каталог (абсолютный путь)',
2859
+ 'clone.run': 'Клонировать',
2860
+ 'prompt.deleteTag': 'Имя тега для удаления',
2861
+ 'gitLog.title': 'Вывод Git',
2862
+ 'gitLog.empty': 'Запусков Git пока нет',
2222
2863
  'toolbar.newBranch': 'Новая ветка',
2223
2864
  'toolbar.fetch': 'Получить',
2224
2865
  'toolbar.pull': 'Забрать',
@@ -2444,6 +3085,70 @@ window.__ModuleLoader__.load({
2444
3085
  'diff.copy': 'Copiar',
2445
3086
  'diff.copied': 'Copiado',
2446
3087
  'toolbar.refresh': 'Atualizar',
3088
+ 'more.sort': 'Ver & ordenar',
3089
+ 'more.commitGroup': 'Commit',
3090
+ 'more.changesGroup': 'Alterações',
3091
+ 'more.pullpushGroup': 'Pull, Push',
3092
+ 'more.branchGroup': 'Ramificação',
3093
+ 'more.remoteGroup': 'Remoto',
3094
+ 'more.stashGroup': 'Stash',
3095
+ 'more.tagGroup': 'Etiquetas',
3096
+ 'more.commitStaged': 'Commit dos ficheiros em staging...',
3097
+ 'more.commitAll': 'Commit de tudo...',
3098
+ 'more.undoCommit': 'Desfazer último commit',
3099
+ 'more.rebaseAbort': 'Abortar rebase',
3100
+ 'more.commitStagedAmend': 'Commit em staging (Amend)...',
3101
+ 'more.commitAllAmend': 'Commit de tudo (Amend)...',
3102
+ 'more.commitStagedSign': 'Commit em staging (Assinado)...',
3103
+ 'more.commitAllSign': 'Commit de tudo (Assinado)...',
3104
+ 'prompt.commitMessage': 'Mensagem de commit',
3105
+ 'more.discardAll': 'Descartar todas as alterações...',
3106
+ 'more.sync': 'Sincronizar (pull e push)',
3107
+ 'more.syncRebase': 'Sincronizar (rebase e push)',
3108
+ 'more.pullRebase': 'Pull (Rebase)',
3109
+ 'more.pullFrom': 'Pull de remoto',
3110
+ 'more.pushForce': 'Push (Forçado)...',
3111
+ 'more.pushTo': 'Push para remoto',
3112
+ 'more.fetchAll': 'Fetch de todos os remotos',
3113
+ 'more.deleteRemoteBranch': 'Eliminar ramo remoto...',
3114
+ 'more.publish': 'Publicar ramificação...',
3115
+ 'more.addRemote': 'Adicionar remoto...',
3116
+ 'more.removeRemote': 'Remover remoto...',
3117
+ 'more.stashTracked': 'Stash (apenas rastreados)',
3118
+ 'more.stashStaged': 'Stash de ficheiros em staging',
3119
+ 'more.stashApplyPick': 'Aplicar stash...',
3120
+ 'more.stashPopPick': 'Pop stash...',
3121
+ 'more.stashDropAny': 'Eliminar stash...',
3122
+ 'more.stashClear': 'Eliminar todos os stashes...',
3123
+ 'stash.none': 'Sem stashes disponíveis',
3124
+ 'more.tagDeleteRemote': 'Eliminar etiqueta remota...',
3125
+ 'more.pushTags': 'Enviar etiquetas...',
3126
+ 'confirm.undoCommit': 'Desfazer o último commit? As alterações ficam em staging.',
3127
+ 'confirm.discardAll': 'Descartar TODAS as alterações não em staging e não rastreadas? Recuperável pelos avisos de desfazer (ficheiros em conflito excluídos).',
3128
+ 'confirm.pushForce': 'O push forçado sobrescreve o remoto com esta ramificação (--force-with-lease recusa se outros enviaram). Continuar?',
3129
+ 'confirm.deleteRemoteBranch': 'Eliminar o ramo {branch} em {remote}?',
3130
+ 'confirm.remoteRemove': 'Remover o remoto {name}? A configuração de tracking vai junto.',
3131
+ 'confirm.stashClear': 'Eliminar TODOS os stashes? Isto não pode ser desfeito.',
3132
+ 'confirm.pushTags': 'Enviar todas as etiquetas para {remote}?',
3133
+ 'prompt.remoteName': 'Nome do remoto',
3134
+ 'prompt.remoteUrl': 'URL do remoto',
3135
+ 'prompt.deleteRemoteTag': 'Etiqueta remota a eliminar (enviada para {remote})',
3136
+ 'toolbar.more': 'Mais ações',
3137
+ 'more.sortGit': 'Ordem do Git',
3138
+ 'more.sortPath': 'Por caminho',
3139
+ 'more.sortName': 'Por nome',
3140
+ 'more.sortStatus': 'Por status',
3141
+ 'more.clone': 'Clonar...',
3142
+ 'more.stashPop': 'Pop do último stash',
3143
+ 'more.deleteTag': 'Excluir tag...',
3144
+ 'more.gitOutput': 'Mostrar saída do Git',
3145
+ 'clone.title': 'Clonar repositório',
3146
+ 'clone.url': 'URL do repositório',
3147
+ 'clone.dir': 'Diretório de destino (caminho absoluto)',
3148
+ 'clone.run': 'Clonar',
3149
+ 'prompt.deleteTag': 'Nome da tag a excluir',
3150
+ 'gitLog.title': 'Saída do Git',
3151
+ 'gitLog.empty': 'Ainda não há execuções do Git',
2447
3152
  'toolbar.newBranch': 'Nova branch',
2448
3153
  'toolbar.fetch': 'Buscar',
2449
3154
  'toolbar.pull': 'Puxar',
@@ -2669,6 +3374,70 @@ window.__ModuleLoader__.load({
2669
3374
  'diff.copy': 'Copia',
2670
3375
  'diff.copied': 'Copiato',
2671
3376
  'toolbar.refresh': 'Aggiorna',
3377
+ 'more.sort': 'Visualizza e ordina',
3378
+ 'more.commitGroup': 'Commit',
3379
+ 'more.changesGroup': 'Modifiche',
3380
+ 'more.pullpushGroup': 'Pull, Push',
3381
+ 'more.branchGroup': 'Branch',
3382
+ 'more.remoteGroup': 'Remoto',
3383
+ 'more.stashGroup': 'Stash',
3384
+ 'more.tagGroup': 'Tag',
3385
+ 'more.commitStaged': 'Commit dei file in staging...',
3386
+ 'more.commitAll': 'Esegui commit di tutto...',
3387
+ 'more.undoCommit': 'Annulla ultimo commit',
3388
+ 'more.rebaseAbort': 'Interrompi rebase',
3389
+ 'more.commitStagedAmend': 'Commit in staging (Amend)...',
3390
+ 'more.commitAllAmend': 'Commit di tutto (Amend)...',
3391
+ 'more.commitStagedSign': 'Commit in staging (Firmato)...',
3392
+ 'more.commitAllSign': 'Commit di tutto (Firmato)...',
3393
+ 'prompt.commitMessage': 'Messaggio di commit',
3394
+ 'more.discardAll': 'Scarta tutte le modifiche...',
3395
+ 'more.sync': 'Sincronizza (pull e push)',
3396
+ 'more.syncRebase': 'Sincronizza (rebase e push)',
3397
+ 'more.pullRebase': 'Pull (Rebase)',
3398
+ 'more.pullFrom': 'Pull da remoto',
3399
+ 'more.pushForce': 'Push (Forza)...',
3400
+ 'more.pushTo': 'Push su remoto',
3401
+ 'more.fetchAll': 'Fetch da tutti i remoti',
3402
+ 'more.deleteRemoteBranch': 'Elimina branch remoto...',
3403
+ 'more.publish': 'Pubblica branch...',
3404
+ 'more.addRemote': 'Aggiungi remoto...',
3405
+ 'more.removeRemote': 'Rimuovi remoto...',
3406
+ 'more.stashTracked': 'Stash (solo tracciati)',
3407
+ 'more.stashStaged': 'Stash dei file in staging',
3408
+ 'more.stashApplyPick': 'Applica stash...',
3409
+ 'more.stashPopPick': 'Pop stash...',
3410
+ 'more.stashDropAny': 'Elimina stash...',
3411
+ 'more.stashClear': 'Elimina tutti gli stash...',
3412
+ 'stash.none': 'Nessuno stash disponibile',
3413
+ 'more.tagDeleteRemote': 'Elimina tag remoto...',
3414
+ 'more.pushTags': 'Esegui push dei tag...',
3415
+ 'confirm.undoCommit': 'Annullare l\'ultimo commit? Le modifiche restano in staging.',
3416
+ 'confirm.discardAll': 'Scartare TUTTE le modifiche non in staging e non tracciate? Recuperabili dai toast di annullamento (file in conflitto esclusi).',
3417
+ 'confirm.pushForce': 'Il push forzato sovrascrive il remoto con questo branch (--force-with-lease rifiuta se altri hanno fatto push). Continuare?',
3418
+ 'confirm.deleteRemoteBranch': 'Eliminare il branch {branch} su {remote}?',
3419
+ 'confirm.remoteRemove': 'Rimuovere il remoto {name}? La configurazione di tracking viene rimossa.',
3420
+ 'confirm.stashClear': 'Eliminare TUTTI gli stash? Operazione irreversibile.',
3421
+ 'confirm.pushTags': 'Eseguire il push di tutti i tag su {remote}?',
3422
+ 'prompt.remoteName': 'Nome remoto',
3423
+ 'prompt.remoteUrl': 'URL remoto',
3424
+ 'prompt.deleteRemoteTag': 'Tag remoto da eliminare (verrà rimosso da {remote})',
3425
+ 'toolbar.more': 'Altre azioni',
3426
+ 'more.sortGit': 'Ordine Git',
3427
+ 'more.sortPath': 'Per percorso',
3428
+ 'more.sortName': 'Per nome',
3429
+ 'more.sortStatus': 'Per stato',
3430
+ 'more.clone': 'Clona...',
3431
+ 'more.stashPop': 'Pop dell\'ultimo stash',
3432
+ 'more.deleteTag': 'Elimina tag...',
3433
+ 'more.gitOutput': 'Mostra output Git',
3434
+ 'clone.title': 'Clona repository',
3435
+ 'clone.url': 'URL del repository',
3436
+ 'clone.dir': 'Directory di destinazione (percorso assoluto)',
3437
+ 'clone.run': 'Clona',
3438
+ 'prompt.deleteTag': 'Nome del tag da eliminare',
3439
+ 'gitLog.title': 'Output Git',
3440
+ 'gitLog.empty': 'Nessuna esecuzione Git registrata',
2672
3441
  'toolbar.newBranch': 'Nuovo branch',
2673
3442
  'toolbar.fetch': 'Fetch',
2674
3443
  'toolbar.pull': 'Pull',
@@ -2894,6 +3663,70 @@ window.__ModuleLoader__.load({
2894
3663
  'diff.copy': 'Kopiëren',
2895
3664
  'diff.copied': 'Gekopieerd',
2896
3665
  'toolbar.refresh': 'Vernieuwen',
3666
+ 'more.sort': 'Weergeven & sorteren',
3667
+ 'more.commitGroup': 'Commit',
3668
+ 'more.changesGroup': 'Wijzigingen',
3669
+ 'more.pullpushGroup': 'Pull, Push',
3670
+ 'more.branchGroup': 'Branch',
3671
+ 'more.remoteGroup': 'Remote',
3672
+ 'more.stashGroup': 'Stash',
3673
+ 'more.tagGroup': 'Tags',
3674
+ 'more.commitStaged': 'Staged committen...',
3675
+ 'more.commitAll': 'Alles committen...',
3676
+ 'more.undoCommit': 'Laatste commit ongedaan maken',
3677
+ 'more.rebaseAbort': 'Rebase afbreken',
3678
+ 'more.commitStagedAmend': 'Staged committen (Amend)...',
3679
+ 'more.commitAllAmend': 'Alles committen (Amend)...',
3680
+ 'more.commitStagedSign': 'Staged committen (Getekend)...',
3681
+ 'more.commitAllSign': 'Alles committen (Getekend)...',
3682
+ 'prompt.commitMessage': 'Commitbericht',
3683
+ 'more.discardAll': 'Alle wijzigingen verwijderen...',
3684
+ 'more.sync': 'Synchroniseren (pull, dan push)',
3685
+ 'more.syncRebase': 'Synchroniseren (rebase, dan push)',
3686
+ 'more.pullRebase': 'Pull (Rebase)',
3687
+ 'more.pullFrom': 'Pull van remote',
3688
+ 'more.pushForce': 'Push (Forceer)...',
3689
+ 'more.pushTo': 'Naar remote pushen',
3690
+ 'more.fetchAll': 'Van alle remotes halen',
3691
+ 'more.deleteRemoteBranch': 'Remote branch verwijderen...',
3692
+ 'more.publish': 'Branch publiceren...',
3693
+ 'more.addRemote': 'Remote toevoegen...',
3694
+ 'more.removeRemote': 'Remote verwijderen...',
3695
+ 'more.stashTracked': 'Stash (alleen gevolgd)',
3696
+ 'more.stashStaged': 'Staged stashen',
3697
+ 'more.stashApplyPick': 'Stash toepassen...',
3698
+ 'more.stashPopPick': 'Stash poppen...',
3699
+ 'more.stashDropAny': 'Stash verwijderen...',
3700
+ 'more.stashClear': 'Alle stashes verwijderen...',
3701
+ 'stash.none': 'Geen stashes beschikbaar',
3702
+ 'more.tagDeleteRemote': 'Remote tag verwijderen...',
3703
+ 'more.pushTags': 'Tags pushen...',
3704
+ 'confirm.undoCommit': 'Laatste commit ongedaan maken? De wijzigingen blijven staged.',
3705
+ 'confirm.discardAll': 'ALLE niet-staged en niet-gevolgde wijzigingen verwijderen? Herstelbaar via undo-toasts (conflictbestanden uitgezonderd).',
3706
+ 'confirm.pushForce': 'Force push overschrijft de remote met deze branch (--force-with-lease weigert bij en en ander zijn push). Doorgaan?',
3707
+ 'confirm.deleteRemoteBranch': 'Branch {branch} op {remote} verwijderen?',
3708
+ 'confirm.remoteRemove': 'Remote {name} verwijderen? De trackingconfiguratie gaat mee.',
3709
+ 'confirm.stashClear': 'ALLE stashes verwijderen? Dit kan niet ongedaan worden gemaakt.',
3710
+ 'confirm.pushTags': 'Alle tags naar {remote} pushen?',
3711
+ 'prompt.remoteName': 'Remote-naam',
3712
+ 'prompt.remoteUrl': 'Remote-URL',
3713
+ 'prompt.deleteRemoteTag': 'Te verwijderen remote tag (wordt naar {remote} gepusht)',
3714
+ 'toolbar.more': 'Meer acties',
3715
+ 'more.sortGit': 'Git-volgorde',
3716
+ 'more.sortPath': 'Op pad',
3717
+ 'more.sortName': 'Op naam',
3718
+ 'more.sortStatus': 'Op status',
3719
+ 'more.clone': 'Klonen...',
3720
+ 'more.stashPop': 'Pop laatste stash',
3721
+ 'more.deleteTag': 'Tag verwijderen...',
3722
+ 'more.gitOutput': 'Git-uitvoer tonen',
3723
+ 'clone.title': 'Repository klonen',
3724
+ 'clone.url': 'Repository-URL',
3725
+ 'clone.dir': 'Doelmap (absoluut pad)',
3726
+ 'clone.run': 'Klonen',
3727
+ 'prompt.deleteTag': 'Naam van de te verwijderen tag',
3728
+ 'gitLog.title': 'Git-uitvoer',
3729
+ 'gitLog.empty': 'Nog geen Git-uitvoeringen',
2897
3730
  'toolbar.newBranch': 'Nieuwe branch',
2898
3731
  'toolbar.fetch': 'Fetch',
2899
3732
  'toolbar.pull': 'Pull',
@@ -3119,6 +3952,70 @@ window.__ModuleLoader__.load({
3119
3952
  'diff.copy': 'Kopiuj',
3120
3953
  'diff.copied': 'Skopiowano',
3121
3954
  'toolbar.refresh': 'Odśwież',
3955
+ 'more.sort': 'Widok i sortowanie',
3956
+ 'more.commitGroup': 'Zatwierdź',
3957
+ 'more.changesGroup': 'Zmiany',
3958
+ 'more.pullpushGroup': 'Pobierz, wyślij',
3959
+ 'more.branchGroup': 'Gałąź',
3960
+ 'more.remoteGroup': 'Zdalne',
3961
+ 'more.stashGroup': 'Schowaj',
3962
+ 'more.tagGroup': 'Tagi',
3963
+ 'more.commitStaged': 'Zatwierdź przechowane...',
3964
+ 'more.commitAll': 'Zatwierdź wszystko...',
3965
+ 'more.undoCommit': 'Cofnij ostatnie zatwierdzenie',
3966
+ 'more.rebaseAbort': 'Przerwij rebase',
3967
+ 'more.commitStagedAmend': 'Zatwierdź przechowane (Amend)...',
3968
+ 'more.commitAllAmend': 'Zatwierdź wszystko (Amend)...',
3969
+ 'more.commitStagedSign': 'Zatwierdź przechowane (Podpisane)...',
3970
+ 'more.commitAllSign': 'Zatwierdź wszystko (Podpisane)...',
3971
+ 'prompt.commitMessage': 'Opis zatwierdzenia',
3972
+ 'more.discardAll': 'Odrzuć wszystkie zmiany...',
3973
+ 'more.sync': 'Synchronizuj (pobierz, potem wyślij)',
3974
+ 'more.syncRebase': 'Synchronizuj (rebase, potem wyślij)',
3975
+ 'more.pullRebase': 'Pobierz (Rebase)',
3976
+ 'more.pullFrom': 'Pobierz z zdalnego',
3977
+ 'more.pushForce': 'Wyślij (Wymuszone)...',
3978
+ 'more.pushTo': 'Wyślij do zdalnego',
3979
+ 'more.fetchAll': 'Pobierz ze wszystkich zdalnych',
3980
+ 'more.deleteRemoteBranch': 'Usuń zdalną gałąź...',
3981
+ 'more.publish': 'Opublikuj gałąź...',
3982
+ 'more.addRemote': 'Dodaj zdalne...',
3983
+ 'more.removeRemote': 'Usuń zdalne...',
3984
+ 'more.stashTracked': 'Schowaj (tylko śledzone)',
3985
+ 'more.stashStaged': 'Schowaj przechowane',
3986
+ 'more.stashApplyPick': 'Zastosuj schowek...',
3987
+ 'more.stashPopPick': 'Zdejmij schowek...',
3988
+ 'more.stashDropAny': 'Usuń schowek...',
3989
+ 'more.stashClear': 'Usuń wszystkie schowki...',
3990
+ 'stash.none': 'Brak dostępnych schowków',
3991
+ 'more.tagDeleteRemote': 'Usuń zdalny tag...',
3992
+ 'more.pushTags': 'Wyślij tagi...',
3993
+ 'confirm.undoCommit': 'Cofnąć ostatnie zatwierdzenie? Zmiany pozostaną przechowane.',
3994
+ 'confirm.discardAll': 'Odrzucić WSZYSTKIE nieprzechowane i nieśledzone zmiany? Do odzyskania przez powiadomienia (pliki konfliktowe wyłączone).',
3995
+ 'confirm.pushForce': 'Wymuszone wysłanie nadpisze zdalne tą gałęzią (--force-with-lease odmówi, gdy inni wysłali). Kontynuować?',
3996
+ 'confirm.deleteRemoteBranch': 'Usunąć gałąź {branch} na {remote}?',
3997
+ 'confirm.remoteRemove': 'Usunąć zdalne {name}? Konfiguracja śledzenia zostanie usunięta.',
3998
+ 'confirm.stashClear': 'Usunąć WSZYSTKIE schowki? Tego nie da się cofnąć.',
3999
+ 'confirm.pushTags': 'Wysłać wszystkie tagi do {remote}?',
4000
+ 'prompt.remoteName': 'Nazwa zdalnego',
4001
+ 'prompt.remoteUrl': 'URL zdalnego',
4002
+ 'prompt.deleteRemoteTag': 'Zdalny tag do usunięcia (zostanie usunięty z {remote})',
4003
+ 'toolbar.more': 'Więcej akcji',
4004
+ 'more.sortGit': 'Kolejność Gita',
4005
+ 'more.sortPath': 'Według ścieżki',
4006
+ 'more.sortName': 'Według nazwy',
4007
+ 'more.sortStatus': 'Według stanu',
4008
+ 'more.clone': 'Klonuj...',
4009
+ 'more.stashPop': 'Pop ostatniego stash',
4010
+ 'more.deleteTag': 'Usuń tag...',
4011
+ 'more.gitOutput': 'Pokaż dane wyjściowe Git',
4012
+ 'clone.title': 'Klonuj repozytorium',
4013
+ 'clone.url': 'URL repozytorium',
4014
+ 'clone.dir': 'Katalog docelowy (ścieżka bezwzględna)',
4015
+ 'clone.run': 'Klonuj',
4016
+ 'prompt.deleteTag': 'Nazwa tagu do usunięcia',
4017
+ 'gitLog.title': 'Dane wyjściowe Git',
4018
+ 'gitLog.empty': 'Brak zapisanych wykonań Git',
3122
4019
  'toolbar.newBranch': 'Nowa gałąź',
3123
4020
  'toolbar.fetch': 'Pobierz',
3124
4021
  'toolbar.pull': 'Ściągnij',
@@ -3344,6 +4241,70 @@ window.__ModuleLoader__.load({
3344
4241
  'diff.copy': 'Kopiera',
3345
4242
  'diff.copied': 'Kopierat',
3346
4243
  'toolbar.refresh': 'Uppdatera',
4244
+ 'more.sort': 'Visa & sortera',
4245
+ 'more.commitGroup': 'Checka in',
4246
+ 'more.changesGroup': 'Ändringar',
4247
+ 'more.pullpushGroup': 'Hämta, skicka',
4248
+ 'more.branchGroup': 'Gren',
4249
+ 'more.remoteGroup': 'Fjärr',
4250
+ 'more.stashGroup': 'Stash',
4251
+ 'more.tagGroup': 'Taggar',
4252
+ 'more.commitStaged': 'Checka in stagingade...',
4253
+ 'more.commitAll': 'Checka in allt...',
4254
+ 'more.undoCommit': 'Ångra senaste incheckning',
4255
+ 'more.rebaseAbort': 'Avbryt rebase',
4256
+ 'more.commitStagedAmend': 'Checka in stagingade (Amend)...',
4257
+ 'more.commitAllAmend': 'Checka in allt (Amend)...',
4258
+ 'more.commitStagedSign': 'Checka in stagingade (Signerat)...',
4259
+ 'more.commitAllSign': 'Checka in allt (Signerat)...',
4260
+ 'prompt.commitMessage': 'Incheckningsmeddelande',
4261
+ 'more.discardAll': 'Kasta alla ändringar...',
4262
+ 'more.sync': 'Synka (hämta, sedan skicka)',
4263
+ 'more.syncRebase': 'Synka (rebase, sedan skicka)',
4264
+ 'more.pullRebase': 'Hämta (Rebase)',
4265
+ 'more.pullFrom': 'Hämta från fjärr',
4266
+ 'more.pushForce': 'Skicka (Tvinga)...',
4267
+ 'more.pushTo': 'Skicka till fjärr',
4268
+ 'more.fetchAll': 'Hämta från alla fjärrar',
4269
+ 'more.deleteRemoteBranch': 'Ta bort fjärrgren...',
4270
+ 'more.publish': 'Publicera gren...',
4271
+ 'more.addRemote': 'Lägg till fjärr...',
4272
+ 'more.removeRemote': 'Ta bort fjärr...',
4273
+ 'more.stashTracked': 'Stash (endast spårade)',
4274
+ 'more.stashStaged': 'Stasha stagingade',
4275
+ 'more.stashApplyPick': 'Använd stash...',
4276
+ 'more.stashPopPick': 'Poppa stash...',
4277
+ 'more.stashDropAny': 'Ta bort stash...',
4278
+ 'more.stashClear': 'Ta bort alla stash:ar...',
4279
+ 'stash.none': 'Inga stash:ar tillgängliga',
4280
+ 'more.tagDeleteRemote': 'Ta bort fjärrtagg...',
4281
+ 'more.pushTags': 'Skicka taggar...',
4282
+ 'confirm.undoCommit': 'Ångra senaste incheckningen? Ändringarna stannar i staging.',
4283
+ 'confirm.discardAll': 'Kasta ALLA ostagingade och ospårade ändringar? Återställningsbara via ångra-toasts (konfliktfiler undantagna).',
4284
+ 'confirm.pushForce': 'Tvingad sändning skriver över fjärren med denna gren (--force-with-lease nekar om andra skickat). Fortsätta?',
4285
+ 'confirm.deleteRemoteBranch': 'Ta bort grenen {branch} på {remote}?',
4286
+ 'confirm.remoteRemove': 'Ta bort fjärren {name}? Spårningskonfigurationen tas bort med den.',
4287
+ 'confirm.stashClear': 'Ta bort ALLA stash:ar? Detta går inte att ångra.',
4288
+ 'confirm.pushTags': 'Skicka alla taggar till {remote}?',
4289
+ 'prompt.remoteName': 'Fjärrnamn',
4290
+ 'prompt.remoteUrl': 'Fjärr-URL',
4291
+ 'prompt.deleteRemoteTag': 'Fjärrtagg att ta bort (skickas till {remote})',
4292
+ 'toolbar.more': 'Fler åtgärder',
4293
+ 'more.sortGit': 'Git-ordning',
4294
+ 'more.sortPath': 'Efter sökväg',
4295
+ 'more.sortName': 'Efter namn',
4296
+ 'more.sortStatus': 'Efter status',
4297
+ 'more.clone': 'Klona...',
4298
+ 'more.stashPop': 'Pop:a senaste stash',
4299
+ 'more.deleteTag': 'Ta bort tagg...',
4300
+ 'more.gitOutput': 'Visa Git-utdata',
4301
+ 'clone.title': 'Klona lagringsplats',
4302
+ 'clone.url': 'URL till lagringsplats',
4303
+ 'clone.dir': 'Målkatalog (absolut sökväg)',
4304
+ 'clone.run': 'Klona',
4305
+ 'prompt.deleteTag': 'Tagnamn som ska tas bort',
4306
+ 'gitLog.title': 'Git-utdata',
4307
+ 'gitLog.empty': 'Inga Git-körningar ännu',
3347
4308
  'toolbar.newBranch': 'Ny gren',
3348
4309
  'toolbar.fetch': 'Fetch',
3349
4310
  'toolbar.pull': 'Pull',
@@ -3569,6 +4530,70 @@ window.__ModuleLoader__.load({
3569
4530
  'diff.copy': 'Kopyala',
3570
4531
  'diff.copied': 'Kopyalandı',
3571
4532
  'toolbar.refresh': 'Yenile',
4533
+ 'more.sort': 'Görüntüle & sırala',
4534
+ 'more.commitGroup': 'İşle',
4535
+ 'more.changesGroup': 'Değişiklikler',
4536
+ 'more.pullpushGroup': 'Çek, Gönder',
4537
+ 'more.branchGroup': 'Dal',
4538
+ 'more.remoteGroup': 'Uzak',
4539
+ 'more.stashGroup': 'Stash',
4540
+ 'more.tagGroup': 'Etiket',
4541
+ 'more.commitStaged': 'Staged dosyaları işle...',
4542
+ 'more.commitAll': 'Tümünü işle...',
4543
+ 'more.undoCommit': 'Son işlemi geri al',
4544
+ 'more.rebaseAbort': 'Rebase\'i durdur',
4545
+ 'more.commitStagedAmend': 'Staged dosyaları işle (Düzelt)...',
4546
+ 'more.commitAllAmend': 'Tümünü işle (Düzelt)...',
4547
+ 'more.commitStagedSign': 'Staged dosyaları işle (İmzalı)...',
4548
+ 'more.commitAllSign': 'Tümünü işle (İmzalı)...',
4549
+ 'prompt.commitMessage': 'İşleme mesajı',
4550
+ 'more.discardAll': 'TÜM değişiklikleri at...',
4551
+ 'more.sync': 'Eşitle (çek, sonra gönder)',
4552
+ 'more.syncRebase': 'Eşitle (rebase, sonra gönder)',
4553
+ 'more.pullRebase': 'Çek (Rebase)',
4554
+ 'more.pullFrom': 'Uzaktan çek',
4555
+ 'more.pushForce': 'Gönder (Zorla)...',
4556
+ 'more.pushTo': 'Uzağa gönder',
4557
+ 'more.fetchAll': 'Tüm uzaklardan al',
4558
+ 'more.deleteRemoteBranch': 'Uzak dalı sil...',
4559
+ 'more.publish': 'Dalı yayınla...',
4560
+ 'more.addRemote': 'Uzak ekle...',
4561
+ 'more.removeRemote': 'Uzağı kaldır...',
4562
+ 'more.stashTracked': 'Stash (yalnızca izlenen)',
4563
+ 'more.stashStaged': 'Staged dosyaları stash\'le',
4564
+ 'more.stashApplyPick': 'Stash uygula...',
4565
+ 'more.stashPopPick': 'Stash pop yap...',
4566
+ 'more.stashDropAny': 'Stash sil...',
4567
+ 'more.stashClear': 'Tüm stash\'leri sil...',
4568
+ 'stash.none': 'Kullanılabilir stash yok',
4569
+ 'more.tagDeleteRemote': 'Uzak etiketi sil...',
4570
+ 'more.pushTags': 'Etiketleri gönder...',
4571
+ 'confirm.undoCommit': 'Son işlem geri alınsın mı? Değişiklikler staged olarak kalır.',
4572
+ 'confirm.discardAll': 'Staged OLMAYAN ve izlenmeyen TÜM değişiklikler atılsın mı? Geri alma bildirimlerinden kurtarılabilir (çakışan dosyalar hariç).',
4573
+ 'confirm.pushForce': 'Zorla gönderme, uzakta olanı bu dalla üzerine yazar (--force-with-lease, başkası göndermişse reddeder).Devam edilsin mi?',
4574
+ 'confirm.deleteRemoteBranch': '{remote} üzerindeki {branch} dalı silinsin mi?',
4575
+ 'confirm.remoteRemove': '{name} uzaklığı kaldırılsın mı? Takip yapılandırması da kaldırılır.',
4576
+ 'confirm.stashClear': 'TÜM stash\'ler silinsin mi? Bu geri alınamaz.',
4577
+ 'confirm.pushTags': 'Tüm etiketler {remote} üzerine gönderilsin mi?',
4578
+ 'prompt.remoteName': 'Uzak adı',
4579
+ 'prompt.remoteUrl': 'Uzak URL',
4580
+ 'prompt.deleteRemoteTag': 'Silinecek uzak etiket ({remote} üzerine gönderilir)',
4581
+ 'toolbar.more': 'Diğer eylemler',
4582
+ 'more.sortGit': 'Git sırası',
4583
+ 'more.sortPath': 'Yola göre',
4584
+ 'more.sortName': 'Ada göre',
4585
+ 'more.sortStatus': 'Duruma göre',
4586
+ 'more.clone': 'Klonla...',
4587
+ 'more.stashPop': 'Son stash\'i aç',
4588
+ 'more.deleteTag': 'Etiket sil...',
4589
+ 'more.gitOutput': 'Git çıktısını göster',
4590
+ 'clone.title': 'Depoyu klonla',
4591
+ 'clone.url': 'Depo URL\'si',
4592
+ 'clone.dir': 'Hedef dizin (mutlak yol)',
4593
+ 'clone.run': 'Klonla',
4594
+ 'prompt.deleteTag': 'Silinecek etiket adı',
4595
+ 'gitLog.title': 'Git çıktısı',
4596
+ 'gitLog.empty': 'Henüz Git çalıştırması yok',
3572
4597
  'toolbar.newBranch': 'Yeni dal',
3573
4598
  'toolbar.fetch': 'Fetch',
3574
4599
  'toolbar.pull': 'Pull',
@@ -3794,6 +4819,70 @@ window.__ModuleLoader__.load({
3794
4819
  'diff.copy': 'Salin',
3795
4820
  'diff.copied': 'Tersalin',
3796
4821
  'toolbar.refresh': 'Segarkan',
4822
+ 'more.sort': 'Lihat & urutkan',
4823
+ 'more.commitGroup': 'Commit',
4824
+ 'more.changesGroup': 'Perubahan',
4825
+ 'more.pullpushGroup': 'Pull, Push',
4826
+ 'more.branchGroup': 'Cabang',
4827
+ 'more.remoteGroup': 'Remote',
4828
+ 'more.stashGroup': 'Stash',
4829
+ 'more.tagGroup': 'Tag',
4830
+ 'more.commitStaged': 'Commit file yang di-stage...',
4831
+ 'more.commitAll': 'Commit semua...',
4832
+ 'more.undoCommit': 'Batalkan commit terakhir',
4833
+ 'more.rebaseAbort': 'Batalkan rebase',
4834
+ 'more.commitStagedAmend': 'Commit stage (Amend)...',
4835
+ 'more.commitAllAmend': 'Commit semua (Amend)...',
4836
+ 'more.commitStagedSign': 'Commit stage (Ditandatangani)...',
4837
+ 'more.commitAllSign': 'Commit semua (Ditandatangani)...',
4838
+ 'prompt.commitMessage': 'Pesan commit',
4839
+ 'more.discardAll': 'Buang semua perubahan...',
4840
+ 'more.sync': 'Sinkronkan (pull lalu push)',
4841
+ 'more.syncRebase': 'Sinkronkan (rebase lalu push)',
4842
+ 'more.pullRebase': 'Pull (Rebase)',
4843
+ 'more.pullFrom': 'Pull dari remote',
4844
+ 'more.pushForce': 'Push (Paksa)...',
4845
+ 'more.pushTo': 'Push ke remote',
4846
+ 'more.fetchAll': 'Fetch dari semua remote',
4847
+ 'more.deleteRemoteBranch': 'Hapus cabang remote...',
4848
+ 'more.publish': 'Publikasikan cabang...',
4849
+ 'more.addRemote': 'Tambah remote...',
4850
+ 'more.removeRemote': 'Hapus remote...',
4851
+ 'more.stashTracked': 'Stash (hanya yang dilacak)',
4852
+ 'more.stashStaged': 'Stage yang di-stash',
4853
+ 'more.stashApplyPick': 'Terapkan stash...',
4854
+ 'more.stashPopPick': 'Pop stash...',
4855
+ 'more.stashDropAny': 'Buang stash...',
4856
+ 'more.stashClear': 'Buang semua stash...',
4857
+ 'stash.none': 'Tidak ada stash tersedia',
4858
+ 'more.tagDeleteRemote': 'Hapus tag remote...',
4859
+ 'more.pushTags': 'Kirim tag...',
4860
+ 'confirm.undoCommit': 'Batalkan commit terakhir? Perubahannya tetap di stage.',
4861
+ 'confirm.discardAll': 'Buang SEMUA perubahan yang tidak di-stage dan tidak dilacak? Dapat dipulihkan lewat toast undo (file konflik dikecualikan).',
4862
+ 'confirm.pushForce': 'Push paksa menimpa remote dengan cabang ini (--force-with-lease menolak jika orang lain sudah push).Lanjutkan?',
4863
+ 'confirm.deleteRemoteBranch': 'Hapus cabang {branch} di {remote}?',
4864
+ 'confirm.remoteRemove': 'Hapus remote {name}? Konfigurasi trackingnya ikut terhapus.',
4865
+ 'confirm.stashClear': 'Buang SEMUA stash? Ini tidak bisa dibatalkan.',
4866
+ 'confirm.pushTags': 'Kirim semua tag ke {remote}?',
4867
+ 'prompt.remoteName': 'Nama remote',
4868
+ 'prompt.remoteUrl': 'URL remote',
4869
+ 'prompt.deleteRemoteTag': 'Tag remote yang akan dihapus (didorong ke {remote})',
4870
+ 'toolbar.more': 'Tindakan lainnya',
4871
+ 'more.sortGit': 'Urutan Git',
4872
+ 'more.sortPath': 'Menurut jalur',
4873
+ 'more.sortName': 'Menurut nama',
4874
+ 'more.sortStatus': 'Menurut status',
4875
+ 'more.clone': 'Kloning...',
4876
+ 'more.stashPop': 'Pop stash terbaru',
4877
+ 'more.deleteTag': 'Hapus tag...',
4878
+ 'more.gitOutput': 'Tampilkan keluaran Git',
4879
+ 'clone.title': 'Kloning repositori',
4880
+ 'clone.url': 'URL repositori',
4881
+ 'clone.dir': 'Direktori target (jalur absolut)',
4882
+ 'clone.run': 'Kloning',
4883
+ 'prompt.deleteTag': 'Nama tag yang akan dihapus',
4884
+ 'gitLog.title': 'Keluaran Git',
4885
+ 'gitLog.empty': 'Belum ada eksekusi Git',
3797
4886
  'toolbar.newBranch': 'Branch baru',
3798
4887
  'toolbar.fetch': 'Fetch',
3799
4888
  'toolbar.pull': 'Pull',
@@ -4019,6 +5108,70 @@ window.__ModuleLoader__.load({
4019
5108
  'diff.copy': 'Sao chép',
4020
5109
  'diff.copied': 'Đã sao chép',
4021
5110
  'toolbar.refresh': 'Làm mới',
5111
+ 'more.sort': 'Xem & sắp xếp',
5112
+ 'more.commitGroup': 'Commit',
5113
+ 'more.changesGroup': 'Thay đổi',
5114
+ 'more.pullpushGroup': 'Pull, Push',
5115
+ 'more.branchGroup': 'Nhánh',
5116
+ 'more.remoteGroup': 'Remote',
5117
+ 'more.stashGroup': 'Stash',
5118
+ 'more.tagGroup': 'Thẻ',
5119
+ 'more.commitStaged': 'Commit các tệp đã stage...',
5120
+ 'more.commitAll': 'Commit tất cả...',
5121
+ 'more.undoCommit': 'Hoàn tác commit cuối',
5122
+ 'more.rebaseAbort': 'Hủy rebase',
5123
+ 'more.commitStagedAmend': 'Commit đã stage (Sửa)...',
5124
+ 'more.commitAllAmend': 'Commit tất cả (Sửa)...',
5125
+ 'more.commitStagedSign': 'Commit đã stage (Ký)...',
5126
+ 'more.commitAllSign': 'Commit tất cả (Ký)...',
5127
+ 'prompt.commitMessage': 'Thông điệp commit',
5128
+ 'more.discardAll': 'Bỏ TẤT CẢ thay đổi...',
5129
+ 'more.sync': 'Đồng bộ (pull rồi push)',
5130
+ 'more.syncRebase': 'Đồng bộ (rebase rồi push)',
5131
+ 'more.pullRebase': 'Pull (Rebase)',
5132
+ 'more.pullFrom': 'Pull từ remote',
5133
+ 'more.pushForce': 'Push (Ép)...',
5134
+ 'more.pushTo': 'Push lên remote',
5135
+ 'more.fetchAll': 'Fetch từ mọi remote',
5136
+ 'more.deleteRemoteBranch': 'Xóa nhánh remote...',
5137
+ 'more.publish': 'Công bố nhánh...',
5138
+ 'more.addRemote': 'Thêm remote...',
5139
+ 'more.removeRemote': 'Xóa remote...',
5140
+ 'more.stashTracked': 'Stash (chỉ theo dõi)',
5141
+ 'more.stashStaged': 'Stash các tệp đã stage',
5142
+ 'more.stashApplyPick': 'Áp dụng stash...',
5143
+ 'more.stashPopPick': 'Pop stash...',
5144
+ 'more.stashDropAny': 'Xóa stash...',
5145
+ 'more.stashClear': 'Xóa tất cả stash...',
5146
+ 'stash.none': 'Không có stash nào',
5147
+ 'more.tagDeleteRemote': 'Xóa thẻ remote...',
5148
+ 'more.pushTags': 'Đẩy thẻ...',
5149
+ 'confirm.undoCommit': 'Hoàn tác commit cuối? Các thay đổi vẫn giữ trong stage.',
5150
+ 'confirm.discardAll': 'Bỏ TẤT CẢ thay đổi chưa stage và chưa theo dõi? Có thể khôi phục qua toast hoàn tác (trừ tệp xung đột).',
5151
+ 'confirm.pushForce': 'Push ép sẽ ghi đè remote bằng nhánh này (--force-with-lease từ chối nếu người khác đã đẩy).Tiếp tục?',
5152
+ 'confirm.deleteRemoteBranch': 'Xóa nhánh {branch} trên {remote}?',
5153
+ 'confirm.remoteRemove': 'Xóa remote {name}? Cấu hình theo dõi cũng bị xóa.',
5154
+ 'confirm.stashClear': 'Xóa TẤT CẢ stash? Không thể hoàn tác.',
5155
+ 'confirm.pushTags': 'Đẩy tất cả thẻ lên {remote}?',
5156
+ 'prompt.remoteName': 'Tên remote',
5157
+ 'prompt.remoteUrl': 'URL remote',
5158
+ 'prompt.deleteRemoteTag': 'Thẻ remote cần xóa (sẽ đẩy lên {remote})',
5159
+ 'toolbar.more': 'Thao tác khác',
5160
+ 'more.sortGit': 'Thứ tự Git',
5161
+ 'more.sortPath': 'Theo đường dẫn',
5162
+ 'more.sortName': 'Theo tên',
5163
+ 'more.sortStatus': 'Theo trạng thái',
5164
+ 'more.clone': 'Clone...',
5165
+ 'more.stashPop': 'Pop stash gần nhất',
5166
+ 'more.deleteTag': 'Xóa thẻ...',
5167
+ 'more.gitOutput': 'Hiện đầu ra Git',
5168
+ 'clone.title': 'Clone kho',
5169
+ 'clone.url': 'URL kho',
5170
+ 'clone.dir': 'Thư mục đích (đường dẫn tuyệt đối)',
5171
+ 'clone.run': 'Clone',
5172
+ 'prompt.deleteTag': 'Tên thẻ cần xóa',
5173
+ 'gitLog.title': 'Đầu ra Git',
5174
+ 'gitLog.empty': 'Chưa có lần chạy Git nào',
4022
5175
  'toolbar.newBranch': 'Nhánh mới',
4023
5176
  'toolbar.fetch': 'Fetch',
4024
5177
  'toolbar.pull': 'Pull',
@@ -4244,6 +5397,70 @@ window.__ModuleLoader__.load({
4244
5397
  'diff.copy': 'نسخ',
4245
5398
  'diff.copied': 'تم النسخ',
4246
5399
  'toolbar.refresh': 'تحديث',
5400
+ 'more.sort': 'عرض وفرز',
5401
+ 'more.commitGroup': 'إيداع',
5402
+ 'more.changesGroup': 'التغييرات',
5403
+ 'more.pullpushGroup': 'سحب، دفع',
5404
+ 'more.branchGroup': 'فرع',
5405
+ 'more.remoteGroup': 'جهاز بعيد',
5406
+ 'more.stashGroup': 'خبئ',
5407
+ 'more.tagGroup': 'وسوم',
5408
+ 'more.commitStaged': 'إيداع الملفات المجهزة...',
5409
+ 'more.commitAll': 'إيداع الكل...',
5410
+ 'more.undoCommit': 'تراجع عن آخر إيداع',
5411
+ 'more.rebaseAbort': 'إلغاء إعادة الأساس',
5412
+ 'more.commitStagedAmend': 'إيداع المجهز (تعديل)...',
5413
+ 'more.commitAllAmend': 'إيداع الكل (تعديل)...',
5414
+ 'more.commitStagedSign': 'إيداع المجهز (موقع)...',
5415
+ 'more.commitAllSign': 'إيداع الكل (موقع)...',
5416
+ 'prompt.commitMessage': 'رسالة الإيداع',
5417
+ 'more.discardAll': 'تجاهل جميع التغييرات...',
5418
+ 'more.sync': 'مزامنة (سحب ثم دفع)',
5419
+ 'more.syncRebase': 'مزامنة (إعادة أساس ثم دفع)',
5420
+ 'more.pullRebase': 'سحب (إعادة أساس)',
5421
+ 'more.pullFrom': 'سحب من الجهاز البعيد',
5422
+ 'more.pushForce': 'دفع (قسري)...',
5423
+ 'more.pushTo': 'دفع إلى جهاز بعيد',
5424
+ 'more.fetchAll': 'جلب من كل الأجهزة البعيدة',
5425
+ 'more.deleteRemoteBranch': 'حذف فرع بعيد...',
5426
+ 'more.publish': 'نشر الفرع...',
5427
+ 'more.addRemote': 'إضافة جهاز بعيد...',
5428
+ 'more.removeRemote': 'إزالة جهاز بعيد...',
5429
+ 'more.stashTracked': 'خبئ (المتتبع فقط)',
5430
+ 'more.stashStaged': 'خبئ الملفات المجهزة',
5431
+ 'more.stashApplyPick': 'تطبيق مخبأ...',
5432
+ 'more.stashPopPick': 'فرقغ مخبأ...',
5433
+ 'more.stashDropAny': 'حذف مخبأ...',
5434
+ 'more.stashClear': 'حذف جميع المخبآء...',
5435
+ 'stash.none': 'لا توجد مخبآء متاحة',
5436
+ 'more.tagDeleteRemote': 'حذف وسم بعيد...',
5437
+ 'more.pushTags': 'دفع الوسوم...',
5438
+ 'confirm.undoCommit': 'التراجع عن آخر إيداع؟تبقى تغييراته في منطقة التجهيز.',
5439
+ 'confirm.discardAll': 'تجاهل كل التغييرات غير المجهزة وغير المتتبعة؟يمكن استعادتها من إشعارات التراجع (ملفات التعارض مستثناة).',
5440
+ 'confirm.pushForce': 'الدفع القسري سيستبدل البعيد بهذا الفرع(--force-with-lease يرفض إن دفع غيرك).المتابعة؟',
5441
+ 'confirm.deleteRemoteBranch': 'حذف الفرع {branch} على {remote}؟',
5442
+ 'confirm.remoteRemove': 'إزالة البعيد {name}؟ستُزال إعدادات التتبع معه.',
5443
+ 'confirm.stashClear': 'حذف جميع المخبآء؟لا يمكن التراجع.',
5444
+ 'confirm.pushTags': 'دفع كل الوسوم إلى {remote}؟',
5445
+ 'prompt.remoteName': 'اسم البعيد',
5446
+ 'prompt.remoteUrl': 'رابط البعيد',
5447
+ 'prompt.deleteRemoteTag': 'الوسم البعيد المراد حذفه(سيُدفع إلى {remote})',
5448
+ 'toolbar.more': 'مزيد من الإجراءات',
5449
+ 'more.sortGit': 'ترتيب Git',
5450
+ 'more.sortPath': 'حسب المسار',
5451
+ 'more.sortName': 'حسب الاسم',
5452
+ 'more.sortStatus': 'حسب الحالة',
5453
+ 'more.clone': 'استنساخ...',
5454
+ 'more.stashPop': 'تفريغ آخر stash',
5455
+ 'more.deleteTag': 'حذف الوسم...',
5456
+ 'more.gitOutput': 'إظهار مخرجات Git',
5457
+ 'clone.title': 'استنساخ المستودع',
5458
+ 'clone.url': 'رابط المستودع',
5459
+ 'clone.dir': 'المجلد الهدف (مسار مطلق)',
5460
+ 'clone.run': 'استنساخ',
5461
+ 'prompt.deleteTag': 'اسم الوسم المطلوب حذفه',
5462
+ 'gitLog.title': 'مخرجات Git',
5463
+ 'gitLog.empty': 'لا توجد عمليات Git بعد',
4247
5464
  'toolbar.newBranch': 'فرع جديد',
4248
5465
  'toolbar.fetch': 'جلب',
4249
5466
  'toolbar.pull': 'سحب',
@@ -4469,6 +5686,70 @@ window.__ModuleLoader__.load({
4469
5686
  'diff.copy': 'कॉपी करें',
4470
5687
  'diff.copied': 'कॉपी हो गया',
4471
5688
  'toolbar.refresh': 'ताज़ा करें',
5689
+ 'more.sort': 'देखें और क्रमबद्ध करें',
5690
+ 'more.commitGroup': 'कमिट',
5691
+ 'more.changesGroup': 'बदलाव',
5692
+ 'more.pullpushGroup': 'पुल, पुश',
5693
+ 'more.branchGroup': 'ब्रांच',
5694
+ 'more.remoteGroup': 'रिमोट',
5695
+ 'more.stashGroup': 'स्टैश',
5696
+ 'more.tagGroup': 'टैग',
5697
+ 'more.commitStaged': 'स्टेज्ड फ़ाइलें कमिट करें...',
5698
+ 'more.commitAll': 'सभी कमिट करें...',
5699
+ 'more.undoCommit': 'अंतिम कमिट रद्द करें',
5700
+ 'more.rebaseAbort': 'रिबेस रोकें',
5701
+ 'more.commitStagedAmend': 'स्टेज्ड कमिट (संशोधन)...',
5702
+ 'more.commitAllAmend': 'सभी कमिट (संशोधन)...',
5703
+ 'more.commitStagedSign': 'स्टेज्ड कमिट (हस्ताक्षरित)...',
5704
+ 'more.commitAllSign': 'सभी कमिट (हस्ताक्षरित)...',
5705
+ 'prompt.commitMessage': 'कमिट संदेश',
5706
+ 'more.discardAll': 'सभी बदलाव छोड़ें...',
5707
+ 'more.sync': 'सिंक (पुल फिर पुश)',
5708
+ 'more.syncRebase': 'सिंक (रिबेस फिर पुश)',
5709
+ 'more.pullRebase': 'पुल (रिबेस)',
5710
+ 'more.pullFrom': 'रिमोट से पुल',
5711
+ 'more.pushForce': 'पुश (बलपूर्वक)...',
5712
+ 'more.pushTo': 'रिमोट पर पुश',
5713
+ 'more.fetchAll': 'सभी रिमोट से फ़ेच',
5714
+ 'more.deleteRemoteBranch': 'रिमोट ब्रांच हटाएँ...',
5715
+ 'more.publish': 'ब्रांच प्रकाशित करें...',
5716
+ 'more.addRemote': 'रिमोट जोड़ें...',
5717
+ 'more.removeRemote': 'रिमोट हटाएँ...',
5718
+ 'more.stashTracked': 'स्टैश (केवल ट्रैक की गई)',
5719
+ 'more.stashStaged': 'स्टेज्ड स्टैश करें',
5720
+ 'more.stashApplyPick': 'स्टैश लागू करें...',
5721
+ 'more.stashPopPick': 'स्टैश पॉप करें...',
5722
+ 'more.stashDropAny': 'स्टैश हटाएँ...',
5723
+ 'more.stashClear': 'सभी स्टैश हटाएँ...',
5724
+ 'stash.none': 'कोई स्टैश उपलब्ध नहीं',
5725
+ 'more.tagDeleteRemote': 'रिमोट टैग हटाएँ...',
5726
+ 'more.pushTags': 'टैग पुश करें...',
5727
+ 'confirm.undoCommit': 'अंतिम कमिट रद्द करें?इसके बदलाव स्टेज पर रहेंगे।',
5728
+ 'confirm.discardAll': 'सभी अन-स्टेज्ड और अन-ट्रैक्ड बदलाव छोड़ें?अन्डू टोस्ट से पुनर्प्राप्त संभव (कॉन्फ्लिक्ट फ़ाइलें छोड़कर)।',
5729
+ 'confirm.pushForce': 'फोर्स पुश रिमोट को इस ब्रांच से अधिलेखित करेगा(--force-with-lease अस्वीकार करेगा यदि दूसरों ने पुश किया)।जारी रखें?',
5730
+ 'confirm.deleteRemoteBranch': '{remote} पर ब्रांच {branch} हटाएँ?',
5731
+ 'confirm.remoteRemove': 'रिमोट {name} हटाएँ?इसकी ट्रैकिंग सेटिंग भी हट जाएगी।',
5732
+ 'confirm.stashClear': 'सभी स्टैश हटाएँ?यह पूर्ववत नहीं हो सकता।',
5733
+ 'confirm.pushTags': 'सभी टैग {remote} पर पुश करें?',
5734
+ 'prompt.remoteName': 'रिमोट नाम',
5735
+ 'prompt.remoteUrl': 'रिमोट URL',
5736
+ 'prompt.deleteRemoteTag': 'हटाने के लिए रिमोट टैग ({remote} पर पुश होगा)',
5737
+ 'toolbar.more': 'अधिक क्रियाएँ',
5738
+ 'more.sortGit': 'Git क्रम',
5739
+ 'more.sortPath': 'पथ से',
5740
+ 'more.sortName': 'नाम से',
5741
+ 'more.sortStatus': 'स्थिति से',
5742
+ 'more.clone': 'क्लोन करें...',
5743
+ 'more.stashPop': 'नवीनतम स्टैश पॉप करें',
5744
+ 'more.deleteTag': 'टैग हटाएँ...',
5745
+ 'more.gitOutput': 'Git आउटपुट दिखाएँ',
5746
+ 'clone.title': 'रिपॉज़िटरी क्लोन करें',
5747
+ 'clone.url': 'रिपॉज़िटरी URL',
5748
+ 'clone.dir': 'लक्ष्य डायरेक्टरी (पूर्ण पथ)',
5749
+ 'clone.run': 'क्लोन करें',
5750
+ 'prompt.deleteTag': 'हटाने के लिए टैग नाम',
5751
+ 'gitLog.title': 'Git आउटपुट',
5752
+ 'gitLog.empty': 'अभी तक कोई Git रन नहीं',
4472
5753
  'toolbar.newBranch': 'नई शाखा',
4473
5754
  'toolbar.fetch': 'फ़ेच',
4474
5755
  'toolbar.pull': 'पुल',
@@ -4694,6 +5975,70 @@ window.__ModuleLoader__.load({
4694
5975
  'diff.copy': 'คัดลอก',
4695
5976
  'diff.copied': 'คัดลอกแล้ว',
4696
5977
  'toolbar.refresh': 'รีเฟรช',
5978
+ 'more.sort': 'ดู & เรียงลำดับ',
5979
+ 'more.commitGroup': 'คอมมิต',
5980
+ 'more.changesGroup': 'การเปลี่ยนแปลง',
5981
+ 'more.pullpushGroup': 'พูล, พุช',
5982
+ 'more.branchGroup': 'บรานช์',
5983
+ 'more.remoteGroup': 'รีโมต',
5984
+ 'more.stashGroup': 'สแตช',
5985
+ 'more.tagGroup': 'แท็ก',
5986
+ 'more.commitStaged': 'คอมมิตไฟล์ที่สเตจ...',
5987
+ 'more.commitAll': 'คอมมิตทั้งหมด...',
5988
+ 'more.undoCommit': 'ยกเลิกคอมมิตล่าสุด',
5989
+ 'more.rebaseAbort': 'ยกเลิกรีเบส',
5990
+ 'more.commitStagedAmend': 'คอมมิตไฟล์ที่สเตจ(แก้ไข)...',
5991
+ 'more.commitAllAmend': 'คอมมิตทั้งหมด(แก้ไข)...',
5992
+ 'more.commitStagedSign': 'คอมมิตไฟล์ที่สเตจ(ลงนาม)...',
5993
+ 'more.commitAllSign': 'คอมมิตทั้งหมด(ลงนาม)...',
5994
+ 'prompt.commitMessage': 'ข้อความคอมมิต',
5995
+ 'more.discardAll': 'ทิ้งการเปลี่ยนแปลงทั้งหมด...',
5996
+ 'more.sync': 'ซิงก์(พูลแล้วพุช)',
5997
+ 'more.syncRebase': 'ซิงก์(รีเบสแล้วพุช)',
5998
+ 'more.pullRebase': 'พูล(รีเบส)',
5999
+ 'more.pullFrom': 'พูลจากรีโมต',
6000
+ 'more.pushForce': 'พุช(บังคับ)...',
6001
+ 'more.pushTo': 'พุชไปรีโมต',
6002
+ 'more.fetchAll': 'ฟีตช์จากทุกรีโมต',
6003
+ 'more.deleteRemoteBranch': 'ลบบรานช์รีโมต...',
6004
+ 'more.publish': 'เผยแพร่บรานช์...',
6005
+ 'more.addRemote': 'เพิ่มรีโมต...',
6006
+ 'more.removeRemote': 'ลบรีโมต...',
6007
+ 'more.stashTracked': 'สแตช(เฉพาะไฟล์ที่ติดตาม)',
6008
+ 'more.stashStaged': 'สแตชไฟล์ที่สเตจ',
6009
+ 'more.stashApplyPick': 'ใช้สแตช...',
6010
+ 'more.stashPopPick': 'ป๊อปสแตช...',
6011
+ 'more.stashDropAny': 'ลบสแตช...',
6012
+ 'more.stashClear': 'ลบสแตชทั้งหมด...',
6013
+ 'stash.none': 'ไม่มีสแตชที่ใช้ได้',
6014
+ 'more.tagDeleteRemote': 'ลบแท็กรีโมต...',
6015
+ 'more.pushTags': 'พุชแท็ก...',
6016
+ 'confirm.undoCommit': 'ยกเลิกคอมมิตล่าสุด?การเปลี่ยนแปลงจะอยู่ในสเตจ',
6017
+ 'confirm.discardAll': 'ทิ้งการเปลี่ยนแปลงที่ไม่ได้สเตจและไม่ได้ติดตามทั้งหมด?กู้คืนได้จาก toast เลิกทำ(ยกเว้นไฟล์ขัดแย้ง)',
6018
+ 'confirm.pushForce': 'พุชแบบบังคับจะเขียนทับรีโมตด้วยบรานช์นี้(--force-with-lease จะปฏิเสธหากคนอื่นพุชแล้ว)ดำเนินการต่อ?',
6019
+ 'confirm.deleteRemoteBranch': 'ลบบรานช์ {branch} บน {remote}?',
6020
+ 'confirm.remoteRemove': 'ลบรีโมต {name}?การตั้งค่าติดตามจะถูกลบด้วย',
6021
+ 'confirm.stashClear': 'ลบสแตชทั้งหมด?ทำไม่ได้ที่จะย้อนกลับ',
6022
+ 'confirm.pushTags': 'พุชแท็กทั้งหมดไปยัง {remote}?',
6023
+ 'prompt.remoteName': 'ชื่อรีโมต',
6024
+ 'prompt.remoteUrl': 'URL รีโมต',
6025
+ 'prompt.deleteRemoteTag': 'แท็กรีโมตที่จะลบ(จะพุชไปยัง {remote})',
6026
+ 'toolbar.more': 'การกระทำเพิ่มเติม',
6027
+ 'more.sortGit': 'ลำดับ Git',
6028
+ 'more.sortPath': 'ตามเส้นทาง',
6029
+ 'more.sortName': 'ตามชื่อ',
6030
+ 'more.sortStatus': 'ตามสถานะ',
6031
+ 'more.clone': 'โคลน...',
6032
+ 'more.stashPop': 'ป๊อป stash ล่าสุด',
6033
+ 'more.deleteTag': 'ลบแท็ก...',
6034
+ 'more.gitOutput': 'แสดงเอาต์พุต Git',
6035
+ 'clone.title': 'โคลนรีโพซิทอรี',
6036
+ 'clone.url': 'URL ของรีโพซิทอรี',
6037
+ 'clone.dir': 'ไดเรกทอรีเป้าหมาย (พาธสัมบูรณ์)',
6038
+ 'clone.run': 'โคลน',
6039
+ 'prompt.deleteTag': 'ชื่อแท็กที่จะลบ',
6040
+ 'gitLog.title': 'เอาต์พุต Git',
6041
+ 'gitLog.empty': 'ยังไม่มีการรัน Git',
4697
6042
  'toolbar.newBranch': 'แบรนช์ใหม่',
4698
6043
  'toolbar.fetch': 'Fetch',
4699
6044
  'toolbar.pull': 'Pull',
@@ -5236,17 +6581,24 @@ window.__ModuleLoader__.load({
5236
6581
  { id: 'float', icon: 'float', key: 'action.float', tone: 'accent' },
5237
6582
  { id: 'split', icon: 'split', key: 'action.split', tone: 'secondary' },
5238
6583
  { id: 'fullscreen', icon: 'fullscreen', key: 'action.fullscreen', tone: 'secondary' },
5239
- { id: 'newBranch', icon: 'plus', key: 'toolbar.newBranch', tone: 'success' },
6584
+ { id: 'newBranch', icon: 'branchCreate', key: 'toolbar.newBranch', tone: 'success' },
5240
6585
  { id: 'checkout', icon: 'checkout', key: 'action.checkout', tone: 'accent' },
5241
6586
  { id: 'delete', icon: 'trash', key: 'action.delete', tone: 'danger' },
5242
6587
  { id: 'compare', icon: 'compare', key: 'action.compare', tone: 'violet' },
5243
- { id: 'diff', icon: 'file', key: 'action.showDiff', tone: 'secondary' },
6588
+ { id: 'diff', icon: 'diff', key: 'action.showDiff', tone: 'secondary' },
5244
6589
  { id: 'stash', icon: 'stash', key: 'action.stash', tone: 'cyan' },
5245
6590
  { id: 'tag', icon: 'tag', key: 'action.newTagHere', tone: 'warn' },
5246
6591
  { id: 'favorite', icon: 'star', key: 'action.favorite', tone: 'warn' },
6592
+ /* fetch keeps its own button (user walked back the removal, 2026-10-10);
6593
+ the refresh button ALSO fetches when clicked manually — refresh means
6594
+ "see whether the remote has anything new", and the dedicated fetch is
6595
+ still there for the explicit gesture. */
5247
6596
  { id: 'fetch', icon: 'fetch', key: 'toolbar.fetch', tone: 'cyan' },
5248
6597
  { id: 'pull', icon: 'pull', key: 'toolbar.pull', tone: 'accent' },
5249
6598
  { id: 'push', icon: 'push', key: 'toolbar.push', tone: 'success' },
6599
+ /* The ⋯ menu moved to the changes-section header (user correction,
6600
+ 2026-10-10: the SCM ▸ menu lives where the changes live, not on the
6601
+ rail). Nothing else about the menu changed. */
5250
6602
  ]
5251
6603
  const RAIL_IDS = RAIL_SPECS.map((spec) => spec.id)
5252
6604
  /* Actions that arrange the view rather than touch the repository, so a running
@@ -5933,7 +7285,10 @@ window.__ModuleLoader__.load({
5933
7285
  return segments
5934
7286
  }
5935
7287
 
5936
- function GraphCell(props) {
7288
+ /* Memoised: rows come from a useMemo, so a row object is referentially
7289
+ stable across re-renders unless the commit list actually changed — the
7290
+ 26px graph cells then skip re-rendering on every filter keystroke. */
7291
+ const GraphCell = memo(function GraphCell(props) {
5937
7292
  const row = props.row
5938
7293
  const height = props.height
5939
7294
  const width = Math.min(Math.max(1, row.width), GRAPH_MAX_LANES) * LANE_WIDTH + 8
@@ -5946,6 +7301,7 @@ window.__ModuleLoader__.load({
5946
7301
  for (const segment of laneSegments(row, height)) {
5947
7302
  children.push(E('line', {
5948
7303
  key: 'lane-' + segment.lane,
7304
+ 'data-lane': String(segment.lane),
5949
7305
  x1: x(segment.lane), y1: segment.y1, x2: x(segment.lane), y2: segment.y2,
5950
7306
  stroke: color(segment.lane), strokeWidth: 2, strokeLinecap: 'round',
5951
7307
  }))
@@ -5954,6 +7310,7 @@ window.__ModuleLoader__.load({
5954
7310
  if (parentLane === row.lane) continue
5955
7311
  children.push(E('path', {
5956
7312
  key: 'edge-' + parentLane,
7313
+ 'data-lane': String(parentLane),
5957
7314
  d: 'M ' + x(row.lane) + ' ' + middle + ' C ' + x(row.lane) + ' ' + height + ', ' + x(parentLane) + ' ' + middle + ', ' + x(parentLane) + ' ' + height,
5958
7315
  fill: 'none',
5959
7316
  stroke: color(parentLane),
@@ -5963,10 +7320,10 @@ window.__ModuleLoader__.load({
5963
7320
  }
5964
7321
  const isHead = Array.isArray(row.commit.refs) && row.commit.refs.some((ref) => String(ref).indexOf('HEAD') >= 0)
5965
7322
  children.push(isHead
5966
- ? E('circle', { key: 'dot', cx: x(row.lane), cy: middle, r: 3.8, fill: 'none', stroke: color(row.lane), strokeWidth: 2 })
5967
- : E('circle', { key: 'dot', cx: x(row.lane), cy: middle, r: 3.6, fill: color(row.lane) }))
7323
+ ? E('circle', { key: 'dot', 'data-lane': String(row.lane), cx: x(row.lane), cy: middle, r: 3.8, fill: 'none', stroke: color(row.lane), strokeWidth: 2 })
7324
+ : E('circle', { key: 'dot', 'data-lane': String(row.lane), cx: x(row.lane), cy: middle, r: 3.6, fill: color(row.lane) }))
5968
7325
  return E('svg', { className: 'dig-graph', width: width, height: height, viewBox: '0 0 ' + width + ' ' + height }, children)
5969
- }
7326
+ }, (prev, next) => prev.row === next.row && prev.height === next.height)
5970
7327
 
5971
7328
  /* ============================== composer send ============================== */
5972
7329
  /* "Send to chat": drop a reference chip / short text into the CURRENT
@@ -6879,12 +8236,16 @@ window.__ModuleLoader__.load({
6879
8236
  branch: ['M4 3.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z', 'M12 3.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z', 'M4 6.5v3a3 3 0 0 0 3 3h2', 'M12 6.5v1a3 3 0 0 1-3 3'],
6880
8237
  commit: ['M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5Z', 'M8 1.5v4', 'M8 10.5v4'],
6881
8238
  refresh: ['M13 8a5 5 0 1 1-1.6-3.7', 'M13 3v3h-3'],
6882
- fetch: ['M8 2v8', 'M4.5 6.5 8 10l3.5-3.5', 'M3 13h10'],
6883
- // pull mirrors push: arrow INTO the local side (down) with the rail on top,
6884
- // where fetch keeps its arrow against the bottom rail. Both used to point up,
6885
- // so pull read as "push" in the rail (reported on v0.3.9).
6886
- pull: ['M8 3v8', 'M4.5 7.5 8 11l3.5-3.5', 'M3 2h10'],
6887
- push: ['M8 2v8', 'M4.5 5.5 8 2l3.5 3.5', 'M3 13h10'],
8239
+ /* Sync icons redrawn after the VS Code SCM trio (user request, 2026-10-10:
8240
+ the colour-coding alone was too abstract). All three share the SAME
8241
+ ground rail at the bottom (the local side) and differ only in the
8242
+ arrow, so the shape alone tells the actions apart:
8243
+ fetch — a BROKEN stem: the references come down, nothing is merged;
8244
+ pull — a solid stem pointing down: the remote lands here;
8245
+ push — a solid stem pointing up: the local goes out. */
8246
+ fetch: ['M8 2v2', 'M8 5.6v2.2', 'M4.6 6.4 8 9.8l3.4-3.4', 'M3 13h10'],
8247
+ pull: ['M8 2v7.8', 'M4.6 6.4 8 9.8l3.4-3.4', 'M3 13h10'],
8248
+ push: ['M8 9.8V2.2', 'M4.6 5.6 8 2.2l3.4 3.4', 'M3 13h10'],
6888
8249
  plus: ['M8 3v10', 'M3 8h10'],
6889
8250
  minus: ['M3 8h10'],
6890
8251
  undo: ['M5 6H2V3', 'M2.5 6.5a5.5 5.5 0 1 1 1.8 5'],
@@ -6893,6 +8254,12 @@ window.__ModuleLoader__.load({
6893
8254
  star: ['M8 2.2l1.8 3.8 4 .5-2.9 2.7.8 4-3.7-2.1-3.7 2.1.8-4L2.2 6.5l4-.5z'],
6894
8255
  folder: ['M2 4.5h4l1.2 1.5H14v6.5H2z'],
6895
8256
  file: ['M4 2h5l3 3v9H4z', 'M9 2v3h3'],
8257
+ /* diff follows the codicon-diff anatomy: a document whose right half
8258
+ carries a + above a −, so it reads as "what changed" and not just
8259
+ "a file". newBranch is the branch glyph with a small + at the new
8260
+ growth tip (codicon git-branch-create's idea at 16px). */
8261
+ diff: ['M3.5 2h6l3.5 3.5V14h-9.5z', 'M9.5 2v3.5H13', 'M8 6.2v3', 'M6.5 7.7h3', 'M6.5 11h3'],
8262
+ branchCreate: ['M4 3.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z', 'M11.5 3.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Z', 'M4 6.5v3a3 3 0 0 0 3 3h2.2', 'M11.5 6.5v1.5', 'M13.2 10.6v3.8', 'M11.3 12.5h3.8'],
6896
8263
  trash: ['M3 5h10', 'M6.5 5V3.5h3V5', 'M4.5 5l.6 8.5h5.8L11.5 5', 'M6.8 7v4', 'M9.2 7v4'],
6897
8264
  checkout: ['M3 8h7', 'M7.5 5.5 10 8l-2.5 2.5', 'M13 3.5v9'],
6898
8265
  compare: ['M4 4h7', 'M4 12h7', 'M6.5 1.5 4 4l2.5 2.5', 'M9.5 9.5 12 12l-2.5 2.5'],
@@ -6944,13 +8311,18 @@ window.__ModuleLoader__.load({
6944
8311
  the panel box, and the menu scrolls when it is taller than the room left. */
6945
8312
  function ContextMenu(props) {
6946
8313
  const ref = useRef(null)
8314
+ const cardRef = useRef(null)
6947
8315
  const closeRef = useRef(props.onClose)
6948
8316
  const [position, setPosition] = useState({ left: props.anchor.x, top: props.anchor.y })
8317
+ /* The open submenu: { items, left, top } in wrapper-relative pixels, or
8318
+ null. Hover-driven — moving onto a different parent row swaps or
8319
+ closes it, so no timers are needed. */
8320
+ const [sub, setSub] = useState(null)
6949
8321
  useEffect(() => { closeRef.current = props.onClose })
6950
8322
  // Layout effect: the clamp below runs before the browser paints, so the menu
6951
8323
  // never flashes at its raw (possibly out-of-panel) anchor position.
6952
8324
  useLayoutEffect(() => {
6953
- const element = ref.current
8325
+ const element = cardRef.current
6954
8326
  if (element === null) return
6955
8327
  const rect = element.getBoundingClientRect()
6956
8328
  const width = props.boundsWidth === undefined ? 0 : props.boundsWidth
@@ -6975,37 +8347,64 @@ window.__ModuleLoader__.load({
6975
8347
  document.removeEventListener('keydown', onKey)
6976
8348
  }
6977
8349
  }, [])
8350
+ /* Hover onto a parent row opens its submenu flush with the row's right
8351
+ edge (VS Code's SCM ▸ pattern); when the panel has no room to the
8352
+ right the submenu flips to the left edge. Only the TOP level opens
8353
+ submenus — hovering rows inside the submenu never re-enters here. */
8354
+ const openSub = (item, inSub, event) => {
8355
+ if (inSub === true || Array.isArray(item.items) !== true || item.items.length === 0) return
8356
+ const card = cardRef.current
8357
+ if (card === null) return
8358
+ const cardRect = card.getBoundingClientRect()
8359
+ const itemRect = event.currentTarget.getBoundingClientRect()
8360
+ const bounds = props.boundsWidth === undefined ? 0 : props.boundsWidth
8361
+ const SUB_W = 220
8362
+ let left = cardRect.width + 2
8363
+ if (bounds > 0 && position.left + left + SUB_W > bounds - 4) left = -SUB_W - 2
8364
+ setSub({ items: item.items, left: left, top: Math.max(0, itemRect.top - cardRect.top - 4) })
8365
+ }
8366
+ const renderItem = (item, index, inSub) => {
8367
+ if (item === null) return E('div', { key: 'sep-' + index, className: 'dig-menu-sep' })
8368
+ const hasSub = inSub !== true && Array.isArray(item.items) === true && item.items.length > 0
8369
+ return E('button', {
8370
+ key: item.id + '-' + index + (inSub === true ? '-sub' : ''),
8371
+ type: 'button',
8372
+ title: item.disabled === true && item.reason !== undefined ? item.reason : '',
8373
+ className: 'dig-menu-item'
8374
+ + (item.danger === true ? ' dig-menu-danger' : '')
8375
+ + (item.disabled === true ? ' dig-menu-disabled' : '')
8376
+ + (item.active === true ? ' dig-menu-item-active' : ''),
8377
+ // The row keeps its id on the DOM so a `⋯ 更多` entry is addressable
8378
+ // (the rail's own buttons do the same; probes click through either).
8379
+ 'data-action': item.id === undefined ? undefined : item.id,
8380
+ disabled: item.disabled === true,
8381
+ onMouseEnter: (event) => openSub(item, inSub, event),
8382
+ onClick: (event) => {
8383
+ if (hasSub === true) return // a submenu parent opens on hover; the click must not close the menu
8384
+ closeRef.current(); item.run(event)
8385
+ },
8386
+ },
8387
+ item.icon === undefined ? null : E('span', { className: 'dig-menu-icon dig-tone-' + (item.tone === undefined ? 'secondary' : item.tone) }, E(Icon, { name: item.icon, size: 13 })),
8388
+ E('span', { className: 'dig-menu-label' }, item.label),
8389
+ hasSub === true ? E('span', { className: 'dig-menu-chevron' }, E(Icon, { name: 'chevron', size: 10 })) : null)
8390
+ }
6978
8391
  /* Two boxes on purpose (dsh 0.1.7-rc.2 menu-material contract): the outer
6979
8392
  card owns the material (a `::before` painted on its own layer, so the
6980
8393
  backdrop filter never becomes the containing block or backdrop root of
6981
8394
  the rows) and the inner box owns the scroll. `data-menu-material` is the
6982
8395
  theme's hook for the dark-menu stroke rebind — the same hook the shipped
6983
- MenuSurface sets. The card must stay a plain `position: absolute` box
8396
+ MenuSurface sets. Everything stays a plain `position: absolute` box
6984
8397
  inside `.dig-root`: panel-internal positioning is an invariant (13), and
6985
- a portal to `document.body` would land outside the panel. */
6986
- return E('div', {
6987
- className: 'dig-menu', ref: ref, 'data-menu-material': 'translucent',
6988
- style: { left: position.left + 'px', top: position.top + 'px' },
6989
- },
6990
- E('div', { className: 'dig-menu-scroll' },
6991
- props.items.map((item, index) => (item === null
6992
- ? E('div', { key: 'sep-' + index, className: 'dig-menu-sep' })
6993
- : E('button', {
6994
- key: item.id + '-' + index,
6995
- type: 'button',
6996
- title: item.disabled === true && item.reason !== undefined ? item.reason : '',
6997
- className: 'dig-menu-item'
6998
- + (item.danger === true ? ' dig-menu-danger' : '')
6999
- + (item.disabled === true ? ' dig-menu-disabled' : '')
7000
- + (item.active === true ? ' dig-menu-item-active' : ''),
7001
- // The row keeps its id on the DOM so a `⋯ 更多` entry is addressable
7002
- // (the rail's own buttons do the same; probes click through either).
7003
- 'data-action': item.id === undefined ? undefined : item.id,
7004
- disabled: item.disabled === true,
7005
- onClick: (event) => { closeRef.current(); item.run(event) },
7006
- },
7007
- item.icon === undefined ? null : E('span', { className: 'dig-menu-icon dig-tone-' + (item.tone === undefined ? 'secondary' : item.tone) }, E(Icon, { name: item.icon, size: 13 })),
7008
- E('span', { className: 'dig-menu-label' }, item.label))))))
8398
+ a portal to `document.body` would land outside the panel. The submenu is
8399
+ a second card inside the same wrapper, so outside-click and Escape cover
8400
+ both without extra wiring. */
8401
+ return E('div', { className: 'dig-menu-wrap', ref: ref, style: { left: position.left + 'px', top: position.top + 'px' } },
8402
+ E('div', { className: 'dig-menu', ref: cardRef, 'data-menu-material': 'translucent' },
8403
+ E('div', { className: 'dig-menu-scroll' },
8404
+ props.items.map((item, index) => renderItem(item, index, false)))),
8405
+ sub === null ? null : E('div', { className: 'dig-menu dig-menu-sub', 'data-menu-material': 'translucent', style: { left: sub.left + 'px', top: sub.top + 'px' } },
8406
+ E('div', { className: 'dig-menu-scroll' },
8407
+ sub.items.map((item, index) => renderItem(item, index, true)))))
7009
8408
  }
7010
8409
 
7011
8410
  /* Lets the user choose which actions the rail shows and in which order. The
@@ -7596,6 +8995,68 @@ window.__ModuleLoader__.load({
7596
8995
  E('button', { type: 'button', className: 'dig-btn dig-btn-primary', onClick: submit }, props.okLabel))))
7597
8996
  }
7598
8997
 
8998
+ /* VS Code's "Clone...": a URL plus an absolute target path. Two inputs in
8999
+ one dialog — chaining two single prompts would forget the URL by the
9000
+ time the directory is typed. */
9001
+ function CloneDialog(props) {
9002
+ const [url, setUrl] = useState('')
9003
+ const [dir, setDir] = useState('')
9004
+ const ref = useRef(null)
9005
+ useEffect(() => { if (ref.current !== null) ref.current.focus() }, [])
9006
+ const ready = url.trim() !== '' && dir.trim() !== ''
9007
+ const submit = () => { if (ready === true) props.onSubmit(url.trim(), dir.trim()) }
9008
+ const onKey = (event) => {
9009
+ if (event.key === 'Enter') { event.preventDefault(); submit() }
9010
+ if (event.key === 'Escape') { event.preventDefault(); props.onCancel() }
9011
+ }
9012
+ return E('div', { className: 'dig-overlay' },
9013
+ E('div', { className: 'dig-dialog' },
9014
+ E('div', { className: 'dig-dialog-title' }, props.title),
9015
+ E('input', {
9016
+ ref: ref, className: 'dig-input', value: url, spellCheck: false,
9017
+ placeholder: props.urlPlaceholder === undefined ? '' : props.urlPlaceholder,
9018
+ onChange: (event) => setUrl(event.target.value), onKeyDown: onKey,
9019
+ }),
9020
+ E('input', {
9021
+ className: 'dig-input', value: dir, spellCheck: false,
9022
+ placeholder: props.dirPlaceholder === undefined ? '' : props.dirPlaceholder,
9023
+ onChange: (event) => setDir(event.target.value), onKeyDown: onKey,
9024
+ }),
9025
+ E('div', { className: 'dig-dialog-actions' },
9026
+ E('button', { type: 'button', className: 'dig-btn', onClick: props.onCancel }, props.cancelLabel),
9027
+ E('button', { type: 'button', className: 'dig-btn dig-btn-primary', disabled: ready === false, onClick: submit }, props.okLabel))))
9028
+ }
9029
+
9030
+ /* VS Code's "Git output" view (showOutput): the tail of the host's git-run
9031
+ ring buffer, fetched through the gitLogs method. A debugging view —
9032
+ opened on demand, refreshed by hand; newest land at the bottom and the
9033
+ body auto-scrolls to them. Panel-internal absolute positioning (13). */
9034
+ function GitOutputDrawer(props) {
9035
+ const t = props.t
9036
+ const ref = useRef(null)
9037
+ useEffect(() => { if (ref.current !== null) ref.current.scrollTop = ref.current.scrollHeight }, [props.entries])
9038
+ return E('div', { className: 'dig-gitlog' },
9039
+ E('div', { className: 'dig-gitlog-head' },
9040
+ E('span', { className: 'dig-gitlog-title' }, t('gitLog.title')),
9041
+ E('span', { className: 'dig-topbar-spacer' }),
9042
+ E('button', { type: 'button', className: 'dig-icon-btn dig-icon-btn-small', title: t('toolbar.refresh'), onClick: props.onRefresh }, E(Icon, { name: 'refresh', size: 13 })),
9043
+ E('button', { type: 'button', className: 'dig-icon-btn dig-icon-btn-small', title: t('confirm.cancel'), onClick: props.onClose }, E(Icon, { name: 'collapse', size: 13 }))),
9044
+ props.entries.length === 0
9045
+ ? E('div', { className: 'dig-gitlog-empty' }, t('gitLog.empty'))
9046
+ : E('div', { className: 'dig-gitlog-body', ref: ref },
9047
+ props.entries.map((entry, index) => E('div', {
9048
+ key: index,
9049
+ className: 'dig-gitlog-row' + (String(entry.code) === '0' ? '' : ' dig-gitlog-row-bad'),
9050
+ },
9051
+ E('div', { className: 'dig-gitlog-meta' },
9052
+ E('span', { className: 'dig-gitlog-code' }, String(entry.code)),
9053
+ E('span', { className: 'dig-gitlog-ms' }, String(entry.ms) + 'ms'),
9054
+ E('span', { className: 'dig-gitlog-at' }, String(entry.at)),
9055
+ E('span', { className: 'dig-gitlog-argv' }, String(entry.argv))),
9056
+ entry.out === '' && entry.err === '' ? null
9057
+ : E('pre', { className: 'dig-gitlog-pre' }, String(entry.err) === '' ? String(entry.out) : String(entry.out) + '\n' + String(entry.err))))))
9058
+ }
9059
+
7599
9060
  /* A destructive confirm: focus starts on Cancel (never on the red button), Enter
7600
9061
  only submits when the dialog asked for a typed confirmation, and passing
7601
9062
  requireText turns the dialog into a type-the-name gate. */
@@ -7726,7 +9187,7 @@ window.__ModuleLoader__.load({
7726
9187
  system colours over a dark translucent panel) and was unreadable. */
7727
9188
  let current = null
7728
9189
  for (const repo of props.repos) { if (repo.path === props.value) { current = repo; break } }
7729
- return E('span', { className: 'dig-select-wrap', title: t('repo.switch') },
9190
+ return E('span', { className: 'dig-select-wrap', title: current === null ? t('repo.switch') : current.path },
7730
9191
  E(Icon, { name: 'folder', size: 12 }),
7731
9192
  E('button', {
7732
9193
  type: 'button',
@@ -7913,7 +9374,15 @@ window.__ModuleLoader__.load({
7913
9374
 
7914
9375
  /* ============================== changes ============================== */
7915
9376
 
7916
- function ChangeRow(props) {
9377
+ /* Memoised with a hand-rolled comparison (issue #11): the callbacks are
9378
+ inline arrows and change identity on every parent render, so default
9379
+ shallow memo would never hit. Rows re-render only when their `item`
9380
+ reference changes — which is exactly what the two-phase stats merge
9381
+ produces (untouched rows keep their object). Stale-callback safety:
9382
+ every callback closes over `run`/`base`, and those only change together
9383
+ with a repo switch, which lands as a brand-new summary array — i.e.
9384
+ new `item` references, so the rows do re-render with fresh closures. */
9385
+ const ChangeRow = memo(function ChangeRow(props) {
7917
9386
  const t = props.t
7918
9387
  const item = props.item
7919
9388
  const raw = item.index === '?' ? '?' : (item.index + item.worktree).trim()
@@ -7939,7 +9408,7 @@ window.__ModuleLoader__.load({
7939
9408
  ? E('button', { type: 'button', className: 'dig-mini', title: t('action.unstage'), onClick: (event) => { event.stopPropagation(); props.onUnstage(item) } }, E(Icon, { name: 'minus', size: 12 }))
7940
9409
  : E('button', { type: 'button', className: 'dig-mini', title: t('action.stage'), onClick: (event) => { event.stopPropagation(); props.onStage(item) } }, E(Icon, { name: 'plus', size: 12 }))),
7941
9410
  readOnly ? null : E('button', { type: 'button', className: 'dig-mini', title: t('action.discard'), onClick: (event) => { event.stopPropagation(); props.onDiscard(item, props.group) } }, E(Icon, { name: 'undo', size: 12 })))
7942
- }
9411
+ }, (prev, next) => prev.item === next.item && prev.group === next.group && prev.t === next.t)
7943
9412
 
7944
9413
  function ChangesPanel(props) {
7945
9414
  const t = props.t
@@ -7965,7 +9434,12 @@ window.__ModuleLoader__.load({
7965
9434
  // clusters each group's files under their folder, the way "Group by:
7966
9435
  // Directory" does in an IDE. Folders are addressed by <group>|<dir>, so a
7967
9436
  // refresh never loses which ones the user folded away.
7968
- const [groupBy, setGroupBy] = useState('flat')
9437
+ // Lifted to the panel (v0.15): the "..." menu drives the same state, so
9438
+ // the header toggle and the menu can never disagree. The alias pair keeps
9439
+ // the body reads below untouched.
9440
+ const groupBy = props.groupBy === 'dir' ? 'dir' : 'flat'
9441
+ const setGroupBy = props.onGroupBy
9442
+ const sortKey = props.sortKey === undefined ? 'git' : props.sortKey
7969
9443
  const [foldedDirs, setFoldedDirs] = useState({})
7970
9444
  const changes = summary === null ? null : summary.changes
7971
9445
  const conflicted = changes === null ? [] : changes.conflicted
@@ -8021,29 +9495,79 @@ window.__ModuleLoader__.load({
8021
9495
  const dir = dirName(item.path)
8022
9496
  return dir === '' ? t('changes.rootDir') : dir
8023
9497
  }
8024
- const rowsOf = (key, entries) => {
8025
- if (groupBy !== 'dir') return entries.map((item) => entryRow(key, item))
8026
- const folders = new Map()
8027
- for (const item of entries) {
8028
- const name = folderOf(item)
8029
- if (folders.has(name) === false) folders.set(name, [])
8030
- folders.get(name).push(item)
8031
- }
8032
- return Array.from(folders.keys()).sort().map((name) => {
8033
- const id = key + '|' + name
8034
- const open = foldedDirs[id] !== true
8035
- const list = folders.get(name)
8036
- return E('div', { className: 'dig-folder', key: id },
8037
- E('button', {
8038
- type: 'button', className: 'dig-folder-head',
8039
- onClick: () => setFoldedDirs((previous) => Object.assign({}, previous, { [id]: previous[id] !== true })),
8040
- },
8041
- E('span', { className: 'dig-chevron' + (open ? ' dig-chevron-open' : '') }, E(Icon, { name: 'chevron', size: 10 })),
8042
- E('span', { className: 'dig-folder-name' }, name),
8043
- E('span', { className: 'dig-count' }, String(list.length))),
8044
- open ? list.map((item) => entryRow(key, item)) : null)
8045
- })
9498
+ /* The "..." menu's sort orders (v0.15). 'git' keeps git's own status
9499
+ order; the others are pure client-side sorts of already-loaded
9500
+ entries — no extra git calls, no re-fetch. */
9501
+ const sortedOf = (entries) => {
9502
+ if (sortKey === 'path') return entries.slice().sort((a, b) => a.path.localeCompare(b.path))
9503
+ if (sortKey === 'name') return entries.slice().sort((a, b) => baseName(a.path).localeCompare(baseName(b.path)) || a.path.localeCompare(b.path))
9504
+ if (sortKey === 'status') return entries.slice().sort((a, b) => (a.index + a.worktree).localeCompare(b.index + b.worktree) || a.path.localeCompare(b.path))
9505
+ return entries
8046
9506
  }
9507
+ /* Windowing: the change list is flattened into (group-head | folder-head
9508
+ | row) items with FIXED heights (see ITEM_HEIGHTS + the CSS pins), a
9509
+ prefix-sum index locates the visible window, and only that slice is
9510
+ mounted. A tree with thousands of files scrolls at frame rate. */
9511
+ const changesScrollRef = useRef(null)
9512
+ const changesViewport = useViewportBox(changesScrollRef)
9513
+ const changeItems = useMemo(() => {
9514
+ const out = []
9515
+ const groupHead = (key, title, entries) => E('div', { className: 'dig-group-head' },
9516
+ E('span', null, title),
9517
+ key === 'staged' ? E('button', { type: 'button', className: 'dig-link', onClick: () => props.onUnstageAll() }, t('changes.unstageAll')) : null,
9518
+ key === 'unstaged' || key === 'untracked' ? E('button', { type: 'button', className: 'dig-link', onClick: () => props.onStageAll(entries) }, t('changes.stageAll')) : null)
9519
+ const emitRows = (key, entries) => {
9520
+ const sorted = sortedOf(entries)
9521
+ if (groupBy !== 'dir') {
9522
+ for (const item of sorted) out.push({ kind: 'row', key: key + ':' + item.path, h: ITEM_HEIGHTS.row, node: entryRow(key, item) })
9523
+ return
9524
+ }
9525
+ const folders = new Map()
9526
+ for (const item of sorted) {
9527
+ const name = folderOf(item)
9528
+ if (folders.has(name) === false) folders.set(name, [])
9529
+ folders.get(name).push(item)
9530
+ }
9531
+ for (const name of Array.from(folders.keys()).sort()) {
9532
+ const id = key + '|' + name
9533
+ const open = foldedDirs[id] !== true
9534
+ const list = folders.get(name)
9535
+ out.push({
9536
+ kind: 'folder', key: 'folder:' + id, h: ITEM_HEIGHTS.folder,
9537
+ node: E('button', {
9538
+ type: 'button', className: 'dig-folder-head',
9539
+ onClick: () => setFoldedDirs((previous) => Object.assign({}, previous, { [id]: previous[id] !== true })),
9540
+ },
9541
+ E('span', { className: 'dig-chevron' + (open ? ' dig-chevron-open' : '') }, E(Icon, { name: 'chevron', size: 10 })),
9542
+ E('span', { className: 'dig-folder-name' }, name),
9543
+ E('span', { className: 'dig-count' }, String(list.length))),
9544
+ })
9545
+ if (open === true) for (const item of list) out.push({ kind: 'row', key: key + ':' + item.path, h: ITEM_HEIGHTS.row, node: entryRow(key, item) })
9546
+ }
9547
+ }
9548
+ const emitGroup = (key, title, entries) => {
9549
+ if (entries.length === 0) return
9550
+ out.push({ kind: 'group', key: 'group:' + key, h: ITEM_HEIGHTS.group, node: groupHead(key, title, entries) })
9551
+ emitRows(key, entries)
9552
+ }
9553
+ emitGroup('conflicted', fill(t('counts.conflicted'), { n: conflicted.length }), conflicted)
9554
+ emitGroup('staged', fill(t('counts.staged'), { n: staged.length }), staged)
9555
+ emitGroup('unstaged', fill(t('counts.unstaged'), { n: unstaged.length }), unstaged)
9556
+ emitGroup('untracked', fill(t('counts.untracked'), { n: untracked.length }), untracked)
9557
+ emitGroup('ignored', fill(t('counts.ignored'), { n: ignored.length }), ignored)
9558
+ return out
9559
+ }, [conflicted, staged, unstaged, untracked, ignored, groupBy, foldedDirs, sortKey, t, props.onUnstageAll, props.onStageAll])
9560
+ const changeOffsets = useMemo(() => {
9561
+ const sums = [0]
9562
+ for (const item of changeItems) sums.push(sums[sums.length - 1] + item.h)
9563
+ return sums
9564
+ }, [changeItems])
9565
+ const winFirst = Math.max(0, windowStart(changeOffsets, changesViewport.scrollTop) - WINDOW_OVERSCAN)
9566
+ const winLimit = Math.ceil(changesViewport.viewH / ITEM_HEIGHTS.row) + WINDOW_OVERSCAN * 3
9567
+ let winLast = winFirst
9568
+ let used = 0
9569
+ while (winLast < changeItems.length && used < winLimit) { used += changeItems[winLast].h; winLast += 1 }
9570
+ const visibleItems = changeItems.slice(winFirst, winLast)
8047
9571
  const foldAll = (value) => {
8048
9572
  const next = {}
8049
9573
  if (value === true) {
@@ -8052,12 +9576,6 @@ window.__ModuleLoader__.load({
8052
9576
  }
8053
9577
  setFoldedDirs(next)
8054
9578
  }
8055
- const group = (key, title, entries) => entries.length === 0 ? null : E('div', { className: 'dig-group', key: key },
8056
- E('div', { className: 'dig-group-head' },
8057
- E('span', null, title),
8058
- key === 'staged' ? E('button', { type: 'button', className: 'dig-link', onClick: () => props.onUnstageAll() }, t('changes.unstageAll')) : null,
8059
- key === 'unstaged' || key === 'untracked' ? E('button', { type: 'button', className: 'dig-link', onClick: () => props.onStageAll(entries) }, t('changes.stageAll')) : null),
8060
- rowsOf(key, entries))
8061
9579
  const aiConfirmDialog = aiAsk !== true ? null : E(ConfirmDialog, {
8062
9580
  t: t,
8063
9581
  title: t('changes.aiOverwriteTitle'),
@@ -8157,19 +9675,26 @@ window.__ModuleLoader__.load({
8157
9675
  className: 'dig-icon-btn dig-icon-btn-small' + (props.showIgnored === true ? ' dig-icon-btn-active' : ''),
8158
9676
  title: t('changes.showIgnored'),
8159
9677
  onClick: () => props.onToggleIgnored(),
8160
- }, E(Icon, { name: props.showIgnored === true ? 'eye' : 'eyeOff', size: 13 })))
9678
+ }, E(Icon, { name: props.showIgnored === true ? 'eye' : 'eyeOff', size: 13 })),
9679
+ props.onMoreMenu === undefined ? null : E('button', {
9680
+ type: 'button',
9681
+ className: 'dig-icon-btn dig-icon-btn-small',
9682
+ title: t('toolbar.more'),
9683
+ 'data-action': 'more',
9684
+ onClick: (event) => props.onMoreMenu(event),
9685
+ }, E(Icon, { name: 'more', size: 13 })))
8161
9686
  return E('div', { className: 'dig-changes' },
8162
9687
  aiConfirmDialog,
8163
9688
  head,
8164
9689
  E('div', { className: 'dig-changes-body' },
8165
- E('div', { className: 'dig-changes-list' },
9690
+ E('div', { className: 'dig-changes-list', ref: changesScrollRef },
8166
9691
  collapsed ? null : [
8167
9692
  total === 0 && ignored.length === 0 ? E('div', { className: 'dig-empty', key: 'empty' }, t('changes.empty')) : null,
8168
- group('conflicted', fill(t('counts.conflicted'), { n: conflicted.length }), conflicted),
8169
- group('staged', fill(t('counts.staged'), { n: staged.length }), staged),
8170
- group('unstaged', fill(t('counts.unstaged'), { n: unstaged.length }), unstaged),
8171
- group('untracked', fill(t('counts.untracked'), { n: untracked.length }), untracked),
8172
- group('ignored', fill(t('counts.ignored'), { n: ignored.length }), ignored),
9693
+ /* Windowed slice: spacers above/below stand in for the unmounted
9694
+ rows (fixed heights, see ITEM_HEIGHTS). */
9695
+ winFirst > 0 ? E('div', { key: 'pad-top', style: { height: String(changeOffsets[winFirst]) + 'px' } }) : null,
9696
+ visibleItems.map((item) => E(Fragment, { key: item.key }, item.node)),
9697
+ winLast < changeItems.length ? E('div', { key: 'pad-bottom', style: { height: String(changeOffsets[changeItems.length] - changeOffsets[winLast]) + 'px' } }) : null,
8173
9698
  ]),
8174
9699
  composer))
8175
9700
  }
@@ -8193,6 +9718,144 @@ window.__ModuleLoader__.load({
8193
9718
  return { name: value, kind: value.indexOf('/') >= 0 ? 'remote' : 'local' }
8194
9719
  }
8195
9720
 
9721
+ /* Display form of a decoration: a HEAD decoration names the branch it points
9722
+ at, so the badge says "HEAD → main" — where HEAD sits must be readable at
9723
+ a glance (GitLens-style), not only inferable from the hollow dot. */
9724
+ function refLabel(decoration) {
9725
+ if (decoration.kind !== 'head') return decoration.name
9726
+ return decoration.name === 'HEAD' ? 'HEAD' : 'HEAD → ' + decoration.name
9727
+ }
9728
+
9729
+ /* ---- list windowing (issue #11 follow-up: hundreds of rows stay smooth) ----
9730
+ Both long lists (history rows, change rows) render only what the viewport
9731
+ can see plus an overscan skirt; spacer divs keep the scrollbar honest.
9732
+ Row heights are FIXED constants — the CSS pins each row kind to an exact
9733
+ height, so a prefix-sum index plus binary search locates the first
9734
+ visible item without measuring. */
9735
+ const HISTORY_ROW_H = 28 /* .dig-commit: height 26 + 2×1px padding */
9736
+ const ITEM_HEIGHTS = { group: 21, folder: 16, row: 24 }
9737
+ const WINDOW_OVERSCAN = 6
9738
+
9739
+ /* Tracks a scroll container's scrollTop (rAF-throttled) and clientHeight
9740
+ (ResizeObserver). SSR-safe: the effect never runs on the server. */
9741
+ function useViewportBox(scrollRef) {
9742
+ const [scrollTop, setScrollTop] = useState(0)
9743
+ const [viewH, setViewH] = useState(360)
9744
+ useEffect(() => {
9745
+ const el = scrollRef.current
9746
+ if (el === null || typeof ResizeObserver !== 'function') return undefined
9747
+ const observer = new ResizeObserver(() => setViewH(el.clientHeight))
9748
+ observer.observe(el)
9749
+ setViewH(el.clientHeight)
9750
+ let frame = 0
9751
+ const onScroll = () => {
9752
+ if (frame !== 0) return
9753
+ frame = requestAnimationFrame(() => { frame = 0; setScrollTop(el.scrollTop) })
9754
+ }
9755
+ el.addEventListener('scroll', onScroll, { passive: true })
9756
+ return () => {
9757
+ observer.disconnect()
9758
+ el.removeEventListener('scroll', onScroll)
9759
+ if (frame !== 0) cancelAnimationFrame(frame)
9760
+ }
9761
+ }, [scrollRef])
9762
+ return { scrollTop: scrollTop, viewH: viewH }
9763
+ }
9764
+
9765
+ /* First index whose cumulative offset exceeds `at` (binary search over the
9766
+ prefix-sum array; offsets.length === items.length + 1). */
9767
+ function windowStart(offsets, at) {
9768
+ let low = 0
9769
+ let high = offsets.length - 1
9770
+ while (low < high) {
9771
+ const mid = (low + high) >> 1
9772
+ if (offsets[mid] <= at) low = mid + 1
9773
+ else high = mid
9774
+ }
9775
+ return Math.max(0, low - 1)
9776
+ }
9777
+
9778
+ /* One history row, memoised on its data (commit + graph row + selection):
9779
+ filter keystrokes re-render the list shell without touching unchanged
9780
+ rows, and the +N overflow badge opens the hidden refs in a menu instead
9781
+ of hiding them behind a bare count. */
9782
+ const REF_ICONS = { head: 'star', local: 'branch', remote: 'fetch', tag: 'tag' }
9783
+ const REF_TONES = { head: 'success', local: 'accent', remote: 'violet', tag: 'warn' }
9784
+ const CommitRow = memo(function CommitRow(props) {
9785
+ const commit = props.commit
9786
+ const decorations = (Array.isArray(commit.refs) ? commit.refs : []).map(refInfo)
9787
+ /* Remote folding (GitLens-style dedupe): a remote whose short name
9788
+ (origin/main → main) matches a LOCAL or HEAD badge on the same commit
9789
+ stops taking a badge of its own — the local badge's title gains a
9790
+ "⇅ origin/main" line and the overflow menu still lists it. Nothing is
9791
+ lost, the row just stops saying "main" and "origin/main" twice. */
9792
+ const localNames = new Set(decorations.filter((d) => d.kind === 'local' || d.kind === 'head').map((d) => d.name))
9793
+ const foldedBy = new Map()
9794
+ const kept = []
9795
+ for (const decoration of decorations) {
9796
+ if (decoration.kind === 'remote') {
9797
+ const slash = decoration.name.indexOf('/')
9798
+ const short = slash >= 0 ? decoration.name.slice(slash + 1) : decoration.name
9799
+ if (localNames.has(short) === true) {
9800
+ if (foldedBy.has(short) === false) foldedBy.set(short, [])
9801
+ foldedBy.get(short).push(decoration.name)
9802
+ continue
9803
+ }
9804
+ }
9805
+ kept.push(decoration)
9806
+ }
9807
+ const folded = []
9808
+ for (const list of foldedBy.values()) folded.push(...list)
9809
+ const shown = kept.slice(0, 3)
9810
+ const hidden = kept.length - shown.length
9811
+ const badgeTitle = (decoration) => {
9812
+ const label = refLabel(decoration)
9813
+ if (decoration.kind !== 'local' && decoration.kind !== 'head') return label
9814
+ const folds = foldedBy.get(decoration.name)
9815
+ return folds === undefined ? label : label + '\n⇅ ' + folds.join(', ')
9816
+ }
9817
+ return E('div', {
9818
+ /* data-lane feeds the hover highlight: the scroll container copies the
9819
+ hovered row's lane into data-hover-lane, and generated CSS dims every
9820
+ graph stroke whose data-lane differs — zero React re-renders. */
9821
+ 'data-lane': String(props.row.lane),
9822
+ className: 'dig-commit' + (props.selected === true ? ' dig-commit-selected' : ''),
9823
+ onClick: () => props.onSelect(commit),
9824
+ onContextMenu: (event) => { event.preventDefault(); props.onMenu(event, commit) },
9825
+ },
9826
+ E('span', { className: 'dig-commit-date', title: shortDate(commit.date) }, relativeTime(commit.date)),
9827
+ E('span', { className: 'dig-commit-author', title: commit.author }, commit.author),
9828
+ E(GraphCell, { row: props.row, height: 26 }),
9829
+ shown.map((decoration, index) => E('span', {
9830
+ key: decoration.kind + ':' + decoration.name + ':' + index,
9831
+ className: 'dig-ref dig-ref-' + decoration.kind,
9832
+ title: badgeTitle(decoration),
9833
+ }, refLabel(decoration))),
9834
+ hidden > 0 || folded.length > 0 ? E('span', {
9835
+ className: 'dig-ref dig-ref-more',
9836
+ title: kept.slice(3).map(refLabel).concat(folded.map((name) => '⇅ ' + name)).join('\n'),
9837
+ onClick: (event) => {
9838
+ event.stopPropagation()
9839
+ if (props.openMenuAt === undefined) return
9840
+ const entries = kept.slice(3).map((decoration, at) => ({
9841
+ id: 'ref:' + at,
9842
+ icon: REF_ICONS[decoration.kind] === undefined ? 'branch' : REF_ICONS[decoration.kind],
9843
+ tone: REF_TONES[decoration.kind] === undefined ? 'secondary' : REF_TONES[decoration.kind],
9844
+ label: refLabel(decoration),
9845
+ run: () => { copyText(refLabel(decoration)) },
9846
+ })).concat(folded.map((name, at) => ({
9847
+ id: 'fold:' + at,
9848
+ icon: 'fetch', tone: 'violet',
9849
+ label: '⇅ ' + name,
9850
+ run: () => { copyText(name) },
9851
+ })))
9852
+ props.openMenuAt(event, entries)
9853
+ },
9854
+ }, '+' + String(hidden + folded.length)) : null,
9855
+ E('span', { className: 'dig-commit-subject', title: commit.subject }, commit.subject),
9856
+ E('span', { className: 'dig-commit-fill' }))
9857
+ }, (prev, next) => prev.commit === next.commit && prev.row === next.row && prev.selected === next.selected && prev.t === next.t)
9858
+
8196
9859
  function sinceThreshold(key) {
8197
9860
  if (key === 'today') { const start = new Date(); start.setHours(0, 0, 0, 0); return start.getTime() }
8198
9861
  if (key === 'week') return Date.now() - 7 * DAY_MS
@@ -8204,6 +9867,8 @@ window.__ModuleLoader__.load({
8204
9867
  function HistoryList(props) {
8205
9868
  const t = props.t
8206
9869
  const commits = props.commits
9870
+ const historyScrollRef = useRef(null)
9871
+ const viewport = useViewportBox(historyScrollRef)
8207
9872
  const [text, setText] = useState('')
8208
9873
  const [author, setAuthor] = useState('')
8209
9874
  const [refName, setRefName] = useState('')
@@ -8326,32 +9991,40 @@ window.__ModuleLoader__.load({
8326
9991
  onClick: clearAll,
8327
9992
  }, E(Icon, { name: 'close', size: 12 })) : null)
8328
9993
 
9994
+ /* Windowed rendering: only the visible slice (± overscan) is mounted —
9995
+ "load more" into the hundreds still scrolls at frame rate. Spacers
9996
+ keep the scrollbar and the load-more button at honest positions. */
9997
+ const winStart = Math.max(0, Math.floor(viewport.scrollTop / HISTORY_ROW_H) - WINDOW_OVERSCAN)
9998
+ const winEnd = Math.min(rows.length, winStart + Math.ceil(viewport.viewH / HISTORY_ROW_H) + WINDOW_OVERSCAN * 2)
9999
+ const visibleRows = rows.slice(winStart, winEnd)
10000
+
8329
10001
  return E('div', { className: 'dig-history' }, filterBar,
8330
- E('div', { className: 'dig-history-scroll' },
10002
+ E('div', {
10003
+ className: 'dig-history-scroll',
10004
+ ref: historyScrollRef,
10005
+ /* Lane hover highlight (GitLens-style trace): copy the hovered row's
10006
+ lane into data-hover-lane; generated CSS dims every stroke whose
10007
+ data-lane differs. Direct DOM dataset writes on purpose — the
10008
+ alternative is re-rendering every row on each hover change. */
10009
+ onMouseOver: (event) => {
10010
+ const host = event.currentTarget
10011
+ const row = event.target instanceof Element ? event.target.closest('.dig-commit') : null
10012
+ const lane = row === null ? null : row.getAttribute('data-lane')
10013
+ if (lane === host.getAttribute('data-hover-lane')) return
10014
+ if (lane === null) host.removeAttribute('data-hover-lane')
10015
+ else host.setAttribute('data-hover-lane', lane)
10016
+ },
10017
+ onMouseLeave: (event) => event.currentTarget.removeAttribute('data-hover-lane'),
10018
+ },
8331
10019
  rows.length === 0 ? E('div', { className: 'dig-empty' }, dirtyFilter ? t('filter.none') : t('history.empty')) : null,
8332
- rows.map((row) => {
8333
- const commit = row.commit
8334
- const decorations = (Array.isArray(commit.refs) ? commit.refs : []).map(refInfo)
8335
- const shown = decorations.slice(0, 3)
8336
- const hidden = decorations.length - shown.length
8337
- return E('div', {
8338
- key: commit.hash,
8339
- className: 'dig-commit' + (props.selectedHash === commit.hash ? ' dig-commit-selected' : ''),
8340
- onClick: () => props.onSelect(commit),
8341
- onContextMenu: (event) => { event.preventDefault(); props.onMenu(event, commit) },
8342
- },
8343
- E('span', { className: 'dig-commit-date', title: shortDate(commit.date) }, relativeTime(commit.date)),
8344
- E('span', { className: 'dig-commit-author', title: commit.author }, commit.author),
8345
- E(GraphCell, { row: row, height: 26 }),
8346
- shown.map((decoration, index) => E('span', {
8347
- key: decoration.kind + ':' + decoration.name + ':' + index,
8348
- className: 'dig-ref dig-ref-' + decoration.kind,
8349
- title: decoration.name,
8350
- }, decoration.name)),
8351
- hidden > 0 ? E('span', { className: 'dig-ref dig-ref-more' }, '+' + hidden) : null,
8352
- E('span', { className: 'dig-commit-subject', title: commit.subject }, commit.subject),
8353
- E('span', { className: 'dig-commit-fill' }))
8354
- }),
10020
+ winStart > 0 ? E('div', { key: 'pad-top', style: { height: String(winStart * HISTORY_ROW_H) + 'px' } }) : null,
10021
+ visibleRows.map((row) => E(CommitRow, {
10022
+ key: row.commit.hash,
10023
+ commit: row.commit, row: row, t: t,
10024
+ selected: props.selectedHash === row.commit.hash,
10025
+ onSelect: props.onSelect, onMenu: props.onMenu, openMenuAt: props.openMenuAt,
10026
+ })),
10027
+ winEnd < rows.length ? E('div', { key: 'pad-bottom', style: { height: String((rows.length - winEnd) * HISTORY_ROW_H) + 'px' } }) : null,
8355
10028
  props.hasMore === true
8356
10029
  ? E('button', { type: 'button', className: 'dig-load-more', disabled: props.busy === true, onClick: props.onLoadMore }, t('history.loadMore'))
8357
10030
  : null))
@@ -8734,9 +10407,78 @@ window.__ModuleLoader__.load({
8734
10407
  return () => { cancelled = true }
8735
10408
  }, [cwd, sessionId, tick])
8736
10409
 
10410
+ /* Changes view state, lifted out of ChangesPanel so the "..." menu (a
10411
+ rail action, invariant 14) can drive the same groupBy and the sort
10412
+ order it introduces — one source of truth, wherever the click came
10413
+ from. sortKey 'git' keeps git's own status order. */
10414
+ const [groupBy, setGroupBy] = useState('flat')
10415
+ const [sortKey, setSortKey] = useState('git')
10416
+ /* The Git-output drawer (VS Code showOutput): null = closed, an array =
10417
+ the entries it currently shows. Opened on demand, refreshed by hand or
10418
+ by reopening — it is a debugging view, not a live stream. */
10419
+ const [gitOutput, setGitOutput] = useState(null)
10420
+ const openGitOutput = useCallback(async () => {
10421
+ try {
10422
+ const data = await request('gitLogs', {})
10423
+ setGitOutput(Array.isArray(data.entries) ? data.entries : [])
10424
+ } catch (caught) {
10425
+ setError(caught instanceof Error ? caught.message : String(caught))
10426
+ }
10427
+ }, [request])
10428
+
10429
+
10430
+ /* Phase-two merge (issue #11): fold the [path, additions, deletions,
10431
+ binary] rows from the `stats` method into a deferred summary. Pure and
10432
+ immutable — untouched rows keep their object reference so the memoised
10433
+ ChangeRow skips them, and a group with no numeric change keeps its
10434
+ array reference so React reconciles nothing. Declared before use
10435
+ (hooks dependency TDZ rule). */
10436
+ const applyStats = (data, statsPayload) => {
10437
+ if (data === null || typeof data !== 'object' || statsPayload === null || typeof statsPayload !== 'object') return data
10438
+ const build = (entries, rows) => {
10439
+ if (Array.isArray(entries) !== true || entries.length === 0 || Array.isArray(rows) !== true) return entries
10440
+ const map = new Map()
10441
+ for (const row of rows) {
10442
+ if (Array.isArray(row) === true && row.length >= 4 && typeof row[0] === 'string') {
10443
+ map.set(row[0], { additions: row[1] | 0, deletions: row[2] | 0, binary: row[3] === true })
10444
+ }
10445
+ }
10446
+ let touched = false
10447
+ const next = entries.map((entry) => {
10448
+ const stat = map.get(entry.path)
10449
+ if (stat === undefined) {
10450
+ if (entry.additions === 0 && entry.deletions === 0 && entry.binary !== true) return entry
10451
+ touched = true
10452
+ return Object.assign({}, entry, { additions: 0, deletions: 0, binary: false })
10453
+ }
10454
+ if (entry.additions === stat.additions && entry.deletions === stat.deletions && entry.binary === stat.binary) return entry
10455
+ touched = true
10456
+ return Object.assign({}, entry, stat)
10457
+ })
10458
+ return touched ? next : entries
10459
+ }
10460
+ const changes = data.changes
10461
+ const staged = build(changes.staged, statsPayload.staged)
10462
+ const unstaged = build(changes.unstaged, statsPayload.unstaged)
10463
+ if (staged === changes.staged && unstaged === changes.unstaged) return data
10464
+ return Object.assign({}, data, {
10465
+ statsDeferred: false,
10466
+ changes: Object.assign({}, changes, { staged: staged, unstaged: unstaged }),
10467
+ })
10468
+ }
10469
+
8737
10470
  const loadSummary = useCallback(async () => {
8738
- const data = await request('summary', Object.assign({}, base, { ignored: showIgnored }))
10471
+ /* Two-phase (issue #11): the rows land first (`stats: false` skips the
10472
+ two numstat passes), then the +/- numbers merge in behind them. The
10473
+ panel is usable the moment the rows arrive; the numbers are exact
10474
+ when they arrive — nothing is approximated or dropped. */
10475
+ const data = await request('summary', Object.assign({}, base, { ignored: showIgnored, stats: false }))
8739
10476
  setSummary(data)
10477
+ if (data.statsDeferred === true) {
10478
+ const merged = applyStats(data, await request('stats', base))
10479
+ setSummary(merged)
10480
+ return merged
10481
+ }
8740
10482
  return data
8741
10483
  }, [base, showIgnored])
8742
10484
 
@@ -8756,18 +10498,40 @@ window.__ModuleLoader__.load({
8756
10498
  return data
8757
10499
  }, [base, pathFilter])
8758
10500
 
8759
- const refresh = useCallback(async () => {
10501
+ /* `fetchRemote` turns the refresh into what the rail button means: "see
10502
+ whether the remote has anything new to pull" — a `git fetch --prune`
10503
+ runs first so ahead/behind land with the fresh summary. Only the
10504
+ MANUAL refresh passes it: the network call is the slow part, so the
10505
+ automatic refreshes (12s poll, post-action, mount) stay off it, and a
10506
+ failed fetch degrades silently — a refresh must not turn into an
10507
+ error banner just because the network is down. While it runs the rail
10508
+ icon spins: fetching is allowed to take a moment, and the spin says
10509
+ the wait is real while blocking a second click. */
10510
+ /* Declared before refresh (hooks dep TDZ rule): the manual refresh
10511
+ fetches only when a remote exists. */
10512
+ const remoteReady = branches !== null && Array.isArray(branches.remotes) && branches.remotes.length > 0
10513
+ const [refreshSpinning, setRefreshSpinning] = useState(false)
10514
+ const refresh = useCallback(async (opts) => {
8760
10515
  if (repoRoot === null) return
8761
- await guard(async () => {
8762
- const [summaryData, branchesData, page] = await Promise.all([loadSummary(), loadBranches(), loadCommits(0)])
8763
- /* The write side of the first-paint cache: exactly the FIRST page
8764
- (a paged fetch never reaches this path), replaced on every
8765
- refresh — the auto-refresh included — so a session switch through
8766
- the same workspace seeds fresh data, not the mount-time
8767
- snapshot. */
8768
- writePaintPage(repoRoot, { summary: summaryData, branches: branchesData, commits: page.commits, hasMore: page.hasMore })
8769
- })
8770
- }, [guard, loadSummary, loadBranches, loadCommits, repoRoot])
10516
+ const withFetch = opts !== null && typeof opts === 'object' && opts.fetchRemote === true && remoteReady === true
10517
+ if (withFetch === true) setRefreshSpinning(true)
10518
+ try {
10519
+ await guard(async () => {
10520
+ if (withFetch === true) {
10521
+ try { await request('fetch', Object.assign({}, base, { prune: true })) } catch (error) { void error }
10522
+ }
10523
+ const [summaryData, branchesData, page] = await Promise.all([loadSummary(), loadBranches(), loadCommits(0)])
10524
+ /* The write side of the first-paint cache: exactly the FIRST page
10525
+ (a paged fetch never reaches this path), replaced on every
10526
+ refresh — the auto-refresh included — so a session switch through
10527
+ the same workspace seeds fresh data, not the mount-time
10528
+ snapshot. */
10529
+ writePaintPage(repoRoot, { summary: summaryData, branches: branchesData, commits: page.commits, hasMore: page.hasMore })
10530
+ })
10531
+ } finally {
10532
+ if (withFetch === true) setRefreshSpinning(false)
10533
+ }
10534
+ }, [guard, loadSummary, loadBranches, loadCommits, repoRoot, remoteReady, base])
8771
10535
 
8772
10536
  useEffect(() => {
8773
10537
  if (repoRoot === null) return undefined
@@ -8790,9 +10554,18 @@ window.__ModuleLoader__.load({
8790
10554
 
8791
10555
  useEffect(() => {
8792
10556
  if (props.visible !== true || repoRoot === null) return undefined
10557
+ /* In-flight gate (issue #11): a summary over a tree with thousands of
10558
+ changes can outlast AUTO_REFRESH_MS, and re-entering just piles
10559
+ queued scans behind the repo lock (the interval deliberately does
10560
+ not run through `guard` — a background refresh must not flash the
10561
+ busy UI). Skip while the previous round is still running; the next
10562
+ tick re-checks. */
10563
+ let inFlight = false
8793
10564
  const timer = setInterval(() => {
10565
+ if (inFlight === true) return
10566
+ inFlight = true
8794
10567
  void (async () => {
8795
- try { await loadSummary() } catch (error) { void error }
10568
+ try { await loadSummary() } catch (error) { void error } finally { inFlight = false }
8796
10569
  })()
8797
10570
  }, AUTO_REFRESH_MS)
8798
10571
  return () => clearInterval(timer)
@@ -9222,6 +10995,17 @@ window.__ModuleLoader__.load({
9222
10995
  if (state.kind === 'newBranch') { await run('checkout', { branch: value, create: true, startPoint: state.from }); return }
9223
10996
  if (state.kind === 'renameBranch') { await run('branchRename', { from: state.from, to: value }); return }
9224
10997
  if (state.kind === 'newTag') { await run('tagCreate', { name: value, hash: state.hash }); return }
10998
+ if (state.kind === 'deleteTag') { await run('tagDelete', { name: value }); return }
10999
+ /* SCM ▸ Commit ▸ variants: one dialog, four (all/staged × amend/sign)
11000
+ shapes. `all` stages every visible change first — the paths go in
11001
+ explicitly, never '.', so the host's root guard stays shut. */
11002
+ if (state.kind === 'commitMsg') {
11003
+ const paths = state.all === true
11004
+ ? summary === null ? [] : summary.changes.staged.concat(summary.changes.unstaged, summary.changes.untracked).map((entry) => entry.path)
11005
+ : undefined
11006
+ await run('commit', { message: value, paths: paths, amend: state.amend === true, signoff: state.sign === true })
11007
+ return
11008
+ }
9225
11009
  }, [run])
9226
11010
 
9227
11011
  /* ---------- toasts + undo ---------- */
@@ -9264,6 +11048,26 @@ window.__ModuleLoader__.load({
9264
11048
  })
9265
11049
  }, [run, pushToast, undoAction, t])
9266
11050
 
11051
+ /* SCM ▸ Changes ▸ Discard All Changes: tracked paths go through
11052
+ `checkout --`, untracked through `clean -fd`. Conflicted entries are
11053
+ deliberately NOT touched — a merge conflict needs a decision, not a
11054
+ silent side-taking; the dialog says so. */
11055
+ const discardAllChanges = useCallback(async () => {
11056
+ const changes = summary === null ? null : summary.changes
11057
+ if (changes === null) return
11058
+ const tracked = changes.unstaged.map((entry) => entry.path)
11059
+ const untracked = changes.untracked.map((entry) => entry.path)
11060
+ if (tracked.length > 0) await run('discard', { paths: tracked, confirm: true })
11061
+ if (untracked.length > 0) await run('discard', { paths: untracked, untracked: true, confirm: true })
11062
+ }, [run, summary])
11063
+
11064
+ /* SCM ▸ Commit ▸ Undo Last Commit: a soft reset to HEAD~1 keeps the
11065
+ commit's work staged, exactly like VS Code's undo. The commit leaves
11066
+ the branch but stays in the reflog — no undo handle needed. */
11067
+ const undoLastCommit = useCallback(async () => {
11068
+ await run('reset', { hash: 'HEAD~1', mode: 'soft', confirm: true })
11069
+ }, [run])
11070
+
9267
11071
  // A discard really overwrote the working tree, so it hands back the same
9268
11072
  // kind of handle a delete does: the host snapshotted the bytes first.
9269
11073
  const discardChanges = useCallback(async (item, group) => {
@@ -9343,7 +11147,7 @@ window.__ModuleLoader__.load({
9343
11147
  loading: repoState === null,
9344
11148
  onOpenMenu: openRepoMenu,
9345
11149
  }),
9346
- branches === null || branches.local.length === 0 ? null : E('span', { className: 'dig-select-wrap', title: t('branch.switch') },
11150
+ branches === null || branches.local.length === 0 ? null : E('span', { className: 'dig-select-wrap', title: branches.branch },
9347
11151
  E(Icon, { name: 'branch', size: 12 }),
9348
11152
  E('button', {
9349
11153
  type: 'button',
@@ -9371,26 +11175,6 @@ window.__ModuleLoader__.load({
9371
11175
  E('span', { className: 'dig-banner-text' }, note),
9372
11176
  E('button', { type: 'button', className: 'dig-link', onClick: () => setNote(null) }, t('error.dismiss')))
9373
11177
 
9374
- const commitSettings = readCommitSettings()
9375
- const changesPane = E(ChangesPanel, {
9376
- t: t, summary: summary, busy: busy, compact: compact, hideHeader: compact,
9377
- request: request, cwd: cwd, sessionId: sessionId, commitSettings: commitSettings, base: base,
9378
- showIgnored: showIgnored,
9379
- onToggleIgnored: () => setShowIgnored((value) => !value),
9380
- // Commit-and-push carries the confirm flag with it: the button already
9381
- // says exactly what it will publish, so the host guard is satisfied by
9382
- // the click itself rather than by a second dialog.
9383
- onCommit: (message, amend, push) => {
9384
- void run('commit', { message: message, amend: amend, push: push === true, confirm: push === true })
9385
- },
9386
- onStage: (item) => { void run('stage', { paths: [item.path] }) },
9387
- onUnstage: (item) => { void run('unstage', { paths: [item.path] }) },
9388
- onStageAll: (entries) => { void run('stage', { paths: entries.map((entry) => entry.path) }) },
9389
- onUnstageAll: () => { void run('unstage', { paths: (summary === null ? [] : summary.changes.staged).map((entry) => entry.path) }) },
9390
- onDiscard: (item, group) => setDialog({ kind: 'discard', item: item, group: group }),
9391
- onDiff: (item, group) => { showDiff({ path: item.path, staged: group === 'staged', untracked: group === 'untracked' }) },
9392
- onChangeMenu: changeMenu,
9393
- })
9394
11178
 
9395
11179
  const historyPane = view === 'detail'
9396
11180
  ? E(CommitDetail, {
@@ -9438,11 +11222,34 @@ window.__ModuleLoader__.load({
9438
11222
  run: () => {
9439
11223
  if (kind === 'checkout') { void run('checkout', { branch: entry.name }); return }
9440
11224
  if (kind === 'delete') { setDialog({ kind: 'deleteBranch', name: entry.name }); return }
11225
+ if (kind === 'merge') { void run('merge', { branch: entry.name }); return }
11226
+ if (kind === 'rebase') { void run('rebase', { onto: entry.name }); return }
9441
11227
  void compareWith(current, entry.name)
9442
11228
  },
9443
11229
  })))
9444
11230
  }
9445
11231
 
11232
+ /* Remote branches (origin/main…): a flat menu, each entry pre-split into
11233
+ its remote and branch halves for the destructive confirm. */
11234
+ const remoteBranches = branches === null ? [] : branches.remote
11235
+ const pickRemoteBranchMenu = (event) => {
11236
+ if (remoteBranches.length === 0) { setNote(t('note.noBranches')); return }
11237
+ openMenuAt(event, remoteBranches.map((entry) => {
11238
+ const slash = entry.name.indexOf('/')
11239
+ const remote = slash >= 0 ? entry.name.slice(0, slash) : entry.name
11240
+ const branch = slash >= 0 ? entry.name.slice(slash + 1) : entry.name
11241
+ return {
11242
+ id: 'remote-delete:' + entry.name,
11243
+ icon: 'trash', tone: 'danger', danger: true,
11244
+ label: entry.name,
11245
+ run: () => setDialog({ kind: 'deleteRemoteBranch', remote: remote, branch: branch }),
11246
+ }
11247
+ }))
11248
+ }
11249
+
11250
+ /* Remotes themselves, for "pull from" / "push to" style entries. */
11251
+ const remoteNames = branches === null ? [] : branches.remotes.map((entry) => entry.name === undefined ? String(entry) : entry.name)
11252
+
9446
11253
  const showWorkingDiff = () => {
9447
11254
  const changes = summary === null ? null : summary.changes
9448
11255
  if (changes === null) return
@@ -9471,18 +11278,178 @@ window.__ModuleLoader__.load({
9471
11278
 
9472
11279
  /* favorites/toggleFavoriteBranch live above branchMenu: a useCallback dep array is evaluated during render. */
9473
11280
  const headBranch = branches === null ? '' : branches.branch
9474
- const remoteReady = branches !== null && Array.isArray(branches.remotes) && branches.remotes.length > 0
9475
11281
  const tracked = summary !== null && summary.upstream !== null
9476
11282
  const stashCount = summary === null ? 0 : summary.stashCount
9477
11283
  const reason = t('action.unavailable')
9478
11284
 
11285
+ /* VS Code-style "..." menu — the SCM title menu: view mode, sort, sync,
11286
+ changes, branch/stash/tag shortcuts, and the Git-output view at the
11287
+ bottom. Every entry reuses the very handlers the rail rows and group
11288
+ heads already run, so the menu can never disagree with them. */
11289
+ /* The ⋯ menu, restructured to the FULL VS Code SCM ▸ tree (issue: the
11290
+ flat 22-item list ignored the submenu anatomy the user asked for).
11291
+ Groups are real submenus now (ContextMenu.items[]); every entry maps
11292
+ onto an action that already exists elsewhere in the panel, and the
11293
+ few new backend verbs (rebaseAbort / remoteAdd / remoteRemove /
11294
+ branchDeleteRemote / tagDeleteRemote / pushTags / stashClear) back
11295
+ their VS Code counterparts one-to-one. */
11296
+ const moreMenu = (event) => {
11297
+ const tagCount = branches === null || Array.isArray(branches.tags) !== true ? 0 : branches.tags.length
11298
+ const stageable = summary === null ? [] : summary.changes.unstaged.concat(summary.changes.untracked)
11299
+ const stagedPaths = summary === null ? [] : summary.changes.staged.map((entry) => entry.path)
11300
+ const allChangePaths = stagedPaths.concat(stageable.map((entry) => entry.path))
11301
+ const sort = (key) => () => setSortKey(key)
11302
+ const operation = summary === null ? null : summary.operation
11303
+ const pickRemote = (run) => (ev) => openMenuAt(ev, remoteNames.map((name) => ({
11304
+ id: 'remote:' + name, icon: 'fetch', tone: 'violet', label: name,
11305
+ run: () => run(name),
11306
+ })))
11307
+ const pickStash = (verb) => async (ev) => {
11308
+ try {
11309
+ const data = await request('stashList', base)
11310
+ const list = Array.isArray(data.stashes) ? data.stashes : []
11311
+ if (list.length === 0) { setNote(t('stash.none')); return }
11312
+ openMenuAt(ev, list.map((entry) => ({
11313
+ id: 'stash:' + entry.ref, icon: 'stash', tone: 'violet',
11314
+ label: entry.ref + ' · ' + entry.subject,
11315
+ run: () => { void run('stashApply', { ref: entry.ref, pop: verb === 'pop' }) },
11316
+ })))
11317
+ } catch (error) { void error }
11318
+ }
11319
+ openMenuAt(event, [
11320
+ /* —— view (VS Code: 以列表形式查看 / 查看和排序 ▸) —— */
11321
+ { id: 'view-flat', icon: 'commit', tone: 'secondary', label: t('changes.groupFlat'), active: groupBy === 'flat', run: () => setGroupBy('flat') },
11322
+ { id: 'view-dir', icon: 'folder', tone: 'secondary', label: t('changes.groupDir'), active: groupBy === 'dir', run: () => setGroupBy('dir') },
11323
+ { id: 'view-sort', icon: 'compare', tone: 'secondary', label: t('more.sort'), items: [
11324
+ { id: 'sort-git', label: t('more.sortGit'), active: sortKey === 'git', run: sort('git') },
11325
+ { id: 'sort-path', label: t('more.sortPath'), active: sortKey === 'path', run: sort('path') },
11326
+ { id: 'sort-name', label: t('more.sortName'), active: sortKey === 'name', run: sort('name') },
11327
+ { id: 'sort-status', label: t('more.sortStatus'), active: sortKey === 'status', run: sort('status') },
11328
+ ] },
11329
+ null,
11330
+ /* —— top-level sync (VS Code header row) —— */
11331
+ { id: 'mm-pull', icon: 'pull', tone: 'accent', label: t('toolbar.pull'), disabled: remoteReady !== true || tracked !== true || blocked, reason, run: () => { void run('pull', { mode: 'ff-only' }) } },
11332
+ { id: 'mm-push', icon: 'push', tone: 'success', label: t('toolbar.push'), disabled: remoteReady !== true || blocked, reason, run: () => setDialog({ kind: 'push' }) },
11333
+ { id: 'mm-clone', icon: 'plus', tone: 'secondary', label: t('more.clone'), run: () => setDialog({ kind: 'clone' }) },
11334
+ { id: 'mm-checkout', icon: 'checkout', tone: 'accent', label: t('action.checkout') + '...', disabled: otherBranches.length === 0 || blocked, reason, run: (ev) => pickBranchMenu(ev, 'checkout') },
11335
+ { id: 'mm-fetch', icon: 'fetch', tone: 'cyan', label: t('toolbar.fetch'), disabled: remoteReady !== true, reason, run: () => { void run('fetch', { prune: true }) } },
11336
+ null,
11337
+ /* —— SCM ▸ Commit ▸ —— */
11338
+ { id: 'sub-commit', icon: 'commit', tone: 'accent', label: t('more.commitGroup'), items: [
11339
+ { id: 'c-staged', label: t('more.commitStaged'), disabled: stagedPaths.length === 0, reason, run: () => setDialog({ kind: 'commitMsg', all: false, amend: false, sign: false }) },
11340
+ { id: 'c-all', label: t('more.commitAll'), disabled: allChangePaths.length === 0, reason, run: () => setDialog({ kind: 'commitMsg', all: true, amend: false, sign: false }) },
11341
+ null,
11342
+ { id: 'c-undo', label: t('more.undoCommit'), disabled: commits.length === 0 || blocked, reason, run: () => setDialog({ kind: 'undoCommit' }) },
11343
+ { id: 'c-abort', label: t('more.rebaseAbort'), disabled: operation !== 'rebase', reason, run: () => { void run('rebaseAbort', {}) } },
11344
+ null,
11345
+ { id: 'c-staged-amend', label: t('more.commitStagedAmend'), disabled: stagedPaths.length === 0 || commits.length === 0, reason, run: () => setDialog({ kind: 'commitMsg', all: false, amend: true, sign: false }) },
11346
+ { id: 'c-all-amend', label: t('more.commitAllAmend'), disabled: allChangePaths.length === 0 || commits.length === 0, reason, run: () => setDialog({ kind: 'commitMsg', all: true, amend: true, sign: false }) },
11347
+ null,
11348
+ { id: 'c-staged-sign', label: t('more.commitStagedSign'), disabled: stagedPaths.length === 0, reason, run: () => setDialog({ kind: 'commitMsg', all: false, amend: false, sign: true }) },
11349
+ { id: 'c-all-sign', label: t('more.commitAllSign'), disabled: allChangePaths.length === 0, reason, run: () => setDialog({ kind: 'commitMsg', all: true, amend: false, sign: true }) },
11350
+ ] },
11351
+ /* —— SCM ▸ Changes ▸ —— */
11352
+ { id: 'sub-changes', icon: 'file', tone: 'secondary', label: t('more.changesGroup'), items: [
11353
+ { id: 'ch-stage', label: t('changes.stageAll'), disabled: stageable.length === 0, reason, run: () => { void run('stage', { paths: stageable.map((entry) => entry.path) }) } },
11354
+ { id: 'ch-unstage', label: t('changes.unstageAll'), disabled: stagedPaths.length === 0, reason, run: () => { void run('unstage', { paths: stagedPaths }) } },
11355
+ null,
11356
+ { id: 'ch-discard', label: t('more.discardAll'), danger: true, disabled: dirty === 0, reason, run: () => setDialog({ kind: 'discardAll' }) },
11357
+ ] },
11358
+ /* —— SCM ▸ Pull, Push ▸ —— */
11359
+ { id: 'sub-pullpush', icon: 'pull', tone: 'accent', label: t('more.pullpushGroup'), items: [
11360
+ { id: 'pp-sync', label: t('more.sync'), disabled: remoteReady !== true || tracked !== true || blocked, reason, run: () => { void (async () => { await run('pull', { mode: 'ff-only' }); await run('push', { confirm: true }) })() } },
11361
+ { id: 'pp-sync-rebase', label: t('more.syncRebase'), disabled: remoteReady !== true || tracked !== true || blocked, reason, run: () => { void (async () => { await run('pull', { mode: 'rebase' }); await run('push', { confirm: true }) })() } },
11362
+ null,
11363
+ { id: 'pp-pull', label: t('toolbar.pull'), disabled: remoteReady !== true || tracked !== true || blocked, reason, run: () => { void run('pull', { mode: 'ff-only' }) } },
11364
+ { id: 'pp-pull-rebase', label: t('more.pullRebase'), disabled: remoteReady !== true || tracked !== true || blocked, reason, run: () => { void run('pull', { mode: 'rebase' }) } },
11365
+ { id: 'pp-pull-from', label: t('more.pullFrom'), disabled: remoteNames.length === 0, reason, run: pickRemote((name) => { void run('pull', { mode: 'ff-only', remote: name }) }) },
11366
+ null,
11367
+ { id: 'pp-push', label: t('toolbar.push'), disabled: remoteReady !== true || blocked, reason, run: () => setDialog({ kind: 'push' }) },
11368
+ { id: 'pp-push-force', label: t('more.pushForce'), danger: true, disabled: remoteReady !== true || blocked, reason, run: () => setDialog({ kind: 'pushForce' }) },
11369
+ { id: 'pp-push-to', label: t('more.pushTo'), disabled: remoteNames.length === 0 || blocked, reason, run: pickRemote((name) => { void run('push', { remote: name, confirm: true }) }) },
11370
+ null,
11371
+ { id: 'pp-fetch', label: t('toolbar.fetch'), disabled: remoteReady !== true, reason, run: () => { void run('fetch', { prune: true }) } },
11372
+ { id: 'pp-fetch-all', label: t('more.fetchAll'), disabled: remoteNames.length === 0, reason, run: () => { void run('fetch', { all: true, prune: true }) } },
11373
+ ] },
11374
+ /* —— SCM ▸ Branch ▸ —— */
11375
+ { id: 'sub-branch', icon: 'branch', tone: 'secondary', label: t('more.branchGroup'), items: [
11376
+ { id: 'br-merge', label: t('action.mergeIntoCurrent'), disabled: otherBranches.length === 0 || blocked, reason, run: (ev) => pickBranchMenu(ev, 'merge') },
11377
+ { id: 'br-rebase', label: t('action.rebaseCurrentOnto'), disabled: otherBranches.length === 0 || blocked, reason, run: (ev) => pickBranchMenu(ev, 'rebase') },
11378
+ null,
11379
+ { id: 'br-new', label: t('toolbar.newBranch'), disabled: blocked, reason, run: () => setDialog({ kind: 'newBranch' }) },
11380
+ null,
11381
+ { id: 'br-rename', label: t('action.rename'), disabled: headBranch === '' || blocked, reason, run: () => setDialog({ kind: 'renameBranch', from: headBranch }) },
11382
+ { id: 'br-delete', label: t('action.delete'), danger: true, disabled: otherBranches.length === 0, reason, run: (ev) => pickBranchMenu(ev, 'delete') },
11383
+ { id: 'br-delete-remote', label: t('more.deleteRemoteBranch'), danger: true, disabled: remoteBranches.length === 0 || blocked, reason, run: (ev) => pickRemoteBranchMenu(ev) },
11384
+ null,
11385
+ { id: 'br-publish', label: t('more.publish'), disabled: remoteReady !== true || tracked === true || blocked, reason, run: () => { void run('push', { setUpstream: true, confirm: true }) } },
11386
+ ] },
11387
+ /* —— SCM ▸ Remote ▸ —— */
11388
+ { id: 'sub-remote', icon: 'fetch', tone: 'violet', label: t('more.remoteGroup'), items: [
11389
+ { id: 'rm-add', label: t('more.addRemote'), run: () => setDialog({ kind: 'remoteAdd' }) },
11390
+ { id: 'rm-remove', label: t('more.removeRemote'), danger: true, disabled: remoteNames.length === 0, reason, run: (ev) => openMenuAt(ev, remoteNames.map((name) => ({
11391
+ id: 'rm-del:' + name, icon: 'trash', tone: 'danger', danger: true, label: name,
11392
+ run: () => setDialog({ kind: 'remoteRemove', name: name }),
11393
+ }))) },
11394
+ ] },
11395
+ /* —— SCM ▸ Stash ▸ —— */
11396
+ { id: 'sub-stash', icon: 'stash', tone: 'violet', label: t('more.stashGroup'), items: [
11397
+ { id: 'st-push', label: t('more.stashTracked'), disabled: dirty === 0, reason, run: () => { void run('stashPush', {}) } },
11398
+ { id: 'st-push-all', label: t('stash.push'), disabled: dirty === 0, reason, run: () => { void run('stashPush', { includeUntracked: true }) } },
11399
+ { id: 'st-push-staged', label: t('more.stashStaged'), disabled: stagedPaths.length === 0, reason, run: () => { void run('stashPush', { staged: true }) } },
11400
+ null,
11401
+ { id: 'st-apply', label: t('stash.apply'), disabled: stashCount === 0, reason, run: () => { void run('stashApply', {}) } },
11402
+ { id: 'st-apply-pick', label: t('more.stashApplyPick'), disabled: stashCount === 0, reason, run: pickStash('apply') },
11403
+ null,
11404
+ { id: 'st-pop', label: t('more.stashPop'), disabled: stashCount === 0, reason, run: () => { void run('stashApply', { pop: true }) } },
11405
+ { id: 'st-pop-pick', label: t('more.stashPopPick'), disabled: stashCount === 0, reason, run: pickStash('pop') },
11406
+ null,
11407
+ { id: 'st-drop', label: t('more.stashDropAny'), danger: true, disabled: stashCount === 0, reason, run: (ev) => pickStash('drop')(ev) },
11408
+ { id: 'st-clear', label: t('more.stashClear'), danger: true, disabled: stashCount === 0, reason, run: () => setDialog({ kind: 'stashClear' }) },
11409
+ ] },
11410
+ /* —— SCM ▸ Tags ▸ —— */
11411
+ { id: 'sub-tags', icon: 'tag', tone: 'warn', label: t('more.tagGroup'), items: [
11412
+ { id: 'tg-new', label: t('action.newTagHere'), disabled: commits.length === 0, reason, run: () => setDialog({ kind: 'newTag' }) },
11413
+ { id: 'tg-delete', label: t('more.deleteTag'), danger: true, disabled: tagCount === 0, reason, run: () => setDialog({ kind: 'deleteTag' }) },
11414
+ { id: 'tg-delete-remote', label: t('more.tagDeleteRemote'), danger: true, disabled: tagCount === 0 || remoteNames.length === 0 || blocked, reason, run: () => setDialog({ kind: 'deleteRemoteTag' }) },
11415
+ null,
11416
+ { id: 'tg-push', label: t('more.pushTags'), disabled: tagCount === 0 || remoteNames.length === 0 || blocked, reason, run: () => setDialog({ kind: 'pushTags' }) },
11417
+ ] },
11418
+ null,
11419
+ { id: 'mm-git-output', icon: 'commit', tone: 'secondary', label: t('more.gitOutput'), run: () => { void openGitOutput() } },
11420
+ ])
11421
+ }
11422
+ const commitSettings = readCommitSettings()
11423
+ const changesPane = E(ChangesPanel, {
11424
+ t: t, summary: summary, busy: busy, compact: compact, hideHeader: compact,
11425
+ groupBy: groupBy, sortKey: sortKey, onGroupBy: setGroupBy,
11426
+ onMoreMenu: moreMenu,
11427
+ request: request, cwd: cwd, sessionId: sessionId, commitSettings: commitSettings, base: base,
11428
+ showIgnored: showIgnored,
11429
+ onToggleIgnored: () => setShowIgnored((value) => !value),
11430
+ // Commit-and-push carries the confirm flag with it: the button already
11431
+ // says exactly what it will publish, so the host guard is satisfied by
11432
+ // the click itself rather than by a second dialog.
11433
+ onCommit: (message, amend, push) => {
11434
+ void run('commit', { message: message, amend: amend, push: push === true, confirm: push === true })
11435
+ },
11436
+ onStage: (item) => { void run('stage', { paths: [item.path] }) },
11437
+ onUnstage: (item) => { void run('unstage', { paths: [item.path] }) },
11438
+ onStageAll: (entries) => { void run('stage', { paths: entries.map((entry) => entry.path) }) },
11439
+ onUnstageAll: () => { void run('unstage', { paths: (summary === null ? [] : summary.changes.staged).map((entry) => entry.path) }) },
11440
+ onDiscard: (item, group) => setDialog({ kind: 'discard', item: item, group: group }),
11441
+ onDiff: (item, group) => { showDiff({ path: item.path, staged: group === 'staged', untracked: group === 'untracked' }) },
11442
+ onChangeMenu: changeMenu,
11443
+ })
11444
+
11445
+
9479
11446
  /* One descriptor per action, with the availability IDEA would use: delete /
9480
11447
  checkout / compare need another local branch, fetch and push need a remote,
9481
11448
  pull needs an upstream, diff needs pending changes, tag needs a commit.
9482
11449
  The rail renders a user-chosen subset in a user-chosen order. */
9483
11450
  const railActions = RAIL_SPECS.map((spec) => {
9484
11451
  const entry = { id: spec.id, icon: spec.icon, tone: spec.tone, label: t(spec.key), disabled: false, active: false, run: () => {} }
9485
- if (spec.id === 'refresh') entry.run = () => setTick((value) => value + 1)
11452
+ if (spec.id === 'refresh') { entry.disabled = refreshSpinning === true; entry.spinning = refreshSpinning === true; entry.run = () => { void refresh({ fetchRemote: true }) } }
9486
11453
  else if (spec.id === 'newBranch') entry.run = () => setDialog({ kind: 'newBranch' })
9487
11454
  else if (spec.id === 'checkout') { entry.disabled = otherBranches.length === 0 || blocked; entry.run = (event) => pickBranchMenu(event, 'checkout') }
9488
11455
  else if (spec.id === 'delete') { entry.disabled = otherBranches.length === 0; entry.run = (event) => pickBranchMenu(event, 'delete') }
@@ -9590,7 +11557,7 @@ window.__ModuleLoader__.load({
9590
11557
  const children = railVisible.slice(0, slots).map((action) => E('button', {
9591
11558
  key: action.id,
9592
11559
  type: 'button',
9593
- className: 'dig-rail-btn dig-tone-' + action.tone + (action.active === true ? ' dig-rail-btn-active' : ''),
11560
+ className: 'dig-rail-btn dig-tone-' + action.tone + (action.active === true ? ' dig-rail-btn-active' : '') + (action.spinning === true ? ' dig-spin' : ''),
9594
11561
  'data-action': action.id,
9595
11562
  'aria-pressed': action.active === true ? 'true' : 'false',
9596
11563
  title: action.disabled === true ? action.label + ' · ' + reason : action.label,
@@ -9725,8 +11692,8 @@ window.__ModuleLoader__.load({
9725
11692
  onClose: () => setRailSettings(false),
9726
11693
  }))
9727
11694
  }
9728
- if (dialog !== null && (dialog.kind === 'newBranch' || dialog.kind === 'renameBranch' || dialog.kind === 'newTag')) {
9729
- const titles = { newBranch: t('prompt.newBranch'), renameBranch: t('prompt.renameBranch'), newTag: t('prompt.newTag') }
11695
+ if (dialog !== null && (dialog.kind === 'newBranch' || dialog.kind === 'renameBranch' || dialog.kind === 'newTag' || dialog.kind === 'deleteTag' || dialog.kind === 'commitMsg')) {
11696
+ const titles = { newBranch: t('prompt.newBranch'), renameBranch: t('prompt.renameBranch'), newTag: t('prompt.newTag'), deleteTag: t('prompt.deleteTag'), commitMsg: t('prompt.commitMessage') }
9730
11697
  overlays.push(E(PromptDialog, {
9731
11698
  key: 'prompt',
9732
11699
  title: titles[dialog.kind],
@@ -9737,6 +11704,112 @@ window.__ModuleLoader__.load({
9737
11704
  onSubmit: (value) => { void submitDialog(dialog, value) },
9738
11705
  }))
9739
11706
  }
11707
+ if (dialog !== null && dialog.kind === 'clone') {
11708
+ overlays.push(E(CloneDialog, {
11709
+ key: 'clone',
11710
+ title: t('clone.title'),
11711
+ urlPlaceholder: t('clone.url'),
11712
+ dirPlaceholder: t('clone.dir'),
11713
+ okLabel: t('clone.run'), cancelLabel: t('confirm.cancel'),
11714
+ onCancel: () => setDialog(null),
11715
+ onSubmit: (url, dir) => { setDialog(null); void run('clone', { url: url, dir: dir }) },
11716
+ }))
11717
+ }
11718
+ /* SCM ▸ additions (v0.15): each destructive one is a ConfirmDialog whose
11719
+ text names the exact target; the tag/remote ones carry the picked
11720
+ remote through. */
11721
+ if (dialog !== null && dialog.kind === 'undoCommit') {
11722
+ overlays.push(E(ConfirmDialog, {
11723
+ key: 'undoCommit', t: t,
11724
+ title: t('confirm.title'),
11725
+ text: t('confirm.undoCommit'),
11726
+ okLabel: t('confirm.ok'), cancelLabel: t('confirm.cancel'),
11727
+ onCancel: () => setDialog(null),
11728
+ onConfirm: () => { setDialog(null); void undoLastCommit() },
11729
+ }))
11730
+ }
11731
+ if (dialog !== null && dialog.kind === 'discardAll') {
11732
+ overlays.push(E(ConfirmDialog, {
11733
+ key: 'discardAll', t: t,
11734
+ title: t('confirm.title'),
11735
+ text: t('confirm.discardAll'),
11736
+ okLabel: t('confirm.ok'), cancelLabel: t('confirm.cancel'),
11737
+ onCancel: () => setDialog(null),
11738
+ onConfirm: () => { setDialog(null); void discardAllChanges() },
11739
+ }))
11740
+ }
11741
+ if (dialog !== null && dialog.kind === 'pushForce') {
11742
+ overlays.push(E(ConfirmDialog, {
11743
+ key: 'pushForce', t: t,
11744
+ title: t('confirm.title'),
11745
+ text: t('confirm.pushForce'),
11746
+ okLabel: t('confirm.ok'), cancelLabel: t('confirm.cancel'),
11747
+ onCancel: () => setDialog(null),
11748
+ onConfirm: () => { setDialog(null); void run('push', { force: true, confirm: true }) },
11749
+ }))
11750
+ }
11751
+ if (dialog !== null && dialog.kind === 'deleteRemoteBranch') {
11752
+ overlays.push(E(ConfirmDialog, {
11753
+ key: 'deleteRemoteBranch', t: t,
11754
+ title: t('confirm.title'),
11755
+ text: fill(t('confirm.deleteRemoteBranch'), { remote: dialog.remote, branch: dialog.branch }),
11756
+ okLabel: t('confirm.ok'), cancelLabel: t('confirm.cancel'),
11757
+ onCancel: () => setDialog(null),
11758
+ onConfirm: () => { const r = dialog.remote; const b = dialog.branch; setDialog(null); void run('branchDeleteRemote', { remote: r, branch: b, confirm: true }) },
11759
+ }))
11760
+ }
11761
+ if (dialog !== null && dialog.kind === 'remoteAdd') {
11762
+ overlays.push(E(CloneDialog, {
11763
+ key: 'remoteAdd',
11764
+ title: t('more.addRemote'),
11765
+ urlPlaceholder: t('prompt.remoteName'),
11766
+ dirPlaceholder: t('prompt.remoteUrl'),
11767
+ okLabel: t('confirm.ok'), cancelLabel: t('confirm.cancel'),
11768
+ onCancel: () => setDialog(null),
11769
+ onSubmit: (name, url) => { setDialog(null); void run('remoteAdd', { name: name, url: url }) },
11770
+ }))
11771
+ }
11772
+ if (dialog !== null && dialog.kind === 'remoteRemove') {
11773
+ overlays.push(E(ConfirmDialog, {
11774
+ key: 'remoteRemove', t: t,
11775
+ title: t('confirm.title'),
11776
+ text: fill(t('confirm.remoteRemove'), { name: dialog.name }),
11777
+ okLabel: t('confirm.ok'), cancelLabel: t('confirm.cancel'),
11778
+ onCancel: () => setDialog(null),
11779
+ onConfirm: () => { const n = dialog.name; setDialog(null); void run('remoteRemove', { name: n, confirm: true }) },
11780
+ }))
11781
+ }
11782
+ if (dialog !== null && dialog.kind === 'stashClear') {
11783
+ overlays.push(E(ConfirmDialog, {
11784
+ key: 'stashClear', t: t,
11785
+ title: t('confirm.title'),
11786
+ text: t('confirm.stashClear'),
11787
+ okLabel: t('confirm.ok'), cancelLabel: t('confirm.cancel'),
11788
+ onCancel: () => setDialog(null),
11789
+ onConfirm: () => { setDialog(null); void run('stashClear', { confirm: true }) },
11790
+ }))
11791
+ }
11792
+ if (dialog !== null && dialog.kind === 'deleteRemoteTag') {
11793
+ overlays.push(E(PromptDialog, {
11794
+ key: 'deleteRemoteTag',
11795
+ title: t('more.tagDeleteRemote'),
11796
+ placeholder: fill(t('prompt.deleteRemoteTag'), { remote: remoteNames[0] === undefined ? 'origin' : remoteNames[0] }),
11797
+ initialValue: '',
11798
+ okLabel: t('confirm.ok'), cancelLabel: t('confirm.cancel'),
11799
+ onCancel: () => setDialog(null),
11800
+ onSubmit: (name) => { setDialog(null); void run('tagDeleteRemote', { remote: remoteNames[0] === undefined ? 'origin' : remoteNames[0], name: name, confirm: true }) },
11801
+ }))
11802
+ }
11803
+ if (dialog !== null && dialog.kind === 'pushTags') {
11804
+ overlays.push(E(ConfirmDialog, {
11805
+ key: 'pushTags', t: t,
11806
+ title: t('confirm.title'),
11807
+ text: fill(t('confirm.pushTags'), { remote: remoteNames[0] === undefined ? 'origin' : remoteNames[0] }),
11808
+ okLabel: t('confirm.ok'), cancelLabel: t('confirm.cancel'),
11809
+ onCancel: () => setDialog(null),
11810
+ onConfirm: () => { setDialog(null); void run('pushTags', { remote: remoteNames[0] === undefined ? 'origin' : remoteNames[0], confirm: true }) },
11811
+ }))
11812
+ }
9740
11813
  if (dialog !== null && dialog.kind === 'deleteBranch') {
9741
11814
  const guarded = isProtectedBranch(dialog.name)
9742
11815
  overlays.push(E(ConfirmDialog, {
@@ -9830,6 +11903,11 @@ window.__ModuleLoader__.load({
9830
11903
  E('div', { className: 'dig-shell' },
9831
11904
  columns === true && repoRoot !== null ? renderRail(true) : null,
9832
11905
  body),
11906
+ gitOutput === null ? null : E(GitOutputDrawer, {
11907
+ t: t, entries: gitOutput,
11908
+ onRefresh: () => { void openGitOutput() },
11909
+ onClose: () => setGitOutput(null),
11910
+ }),
9833
11911
  overlays,
9834
11912
  E(ToastStack, { t: t, toasts: toasts }))
9835
11913
  }
@@ -9844,23 +11922,49 @@ window.__ModuleLoader__.load({
9844
11922
  /* Tokens only (--dsw-alias-*): transparent themes and background plugins
9845
11923
  (dsh-any-background) keep showing through, like the host's own panels. */
9846
11924
 
11925
+ /* Lane-hover trace, generated: one rule per lane (lanes are capped at
11926
+ GRAPH_MAX_LANES, so this is a fixed handful). When the scroll container
11927
+ carries data-hover-lane="N", every graph stroke whose data-lane differs
11928
+ fades — the hovered lane reads as one continuous thread down the graph
11929
+ with zero React involvement. */
11930
+ const LANE_TRACE_RULES = []
11931
+ for (let lane = 0; lane < GRAPH_MAX_LANES; lane += 1) {
11932
+ LANE_TRACE_RULES.push(
11933
+ '.dig-history-scroll[data-hover-lane="' + lane + '"] .dig-graph line:not([data-lane="' + lane + '"])'
11934
+ + ',.dig-history-scroll[data-hover-lane="' + lane + '"] .dig-graph path:not([data-lane="' + lane + '"])'
11935
+ + ',.dig-history-scroll[data-hover-lane="' + lane + '"] .dig-graph circle:not([data-lane="' + lane + '"])'
11936
+ + '{stroke-opacity:.18;fill-opacity:.18}',
11937
+ )
11938
+ }
11939
+ LANE_TRACE_RULES.push('.dig-graph line,.dig-graph path,.dig-graph circle{transition:stroke-opacity .15s,fill-opacity .15s}')
11940
+ /* The rail refresh button spins while its fetch+refresh round-trip runs. */
11941
+ LANE_TRACE_RULES.push('.dig-spin svg{animation:dig-spin-rotate .9s linear infinite}')
11942
+ LANE_TRACE_RULES.push('@keyframes dig-spin-rotate{to{transform:rotate(360deg)}}')
11943
+
9847
11944
  const CSS = [
9848
11945
  '.dig-root{position:relative;display:flex;flex-direction:column;height:100%;min-height:0;background:transparent;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family,system-ui,sans-serif);font-size:13px;line-height:1.5;font-weight:600;overflow:hidden}',
9849
11946
  '.dig-topbar{display:flex;align-items:center;gap:6px;padding:4px 6px;flex:none;min-width:0;border-bottom:1px solid var(--dsw-alias-border-l2);overflow:hidden}',
9850
11947
  '.dig-topbar-spacer{flex:1;min-width:4px}',
9851
11948
  '.dig-busy{color:var(--dsw-alias-label-tertiary);flex:none;white-space:nowrap}',
9852
11949
  '.dig-track{color:var(--dsw-alias-label-secondary);flex:none;font-variant-numeric:tabular-nums;white-space:nowrap}',
9853
- '.dig-select-wrap{display:inline-flex;align-items:center;gap:4px;min-width:0;color:var(--dsw-alias-label-secondary)}',
9854
- '.dig-select{appearance:none;-webkit-appearance:none;background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-sm,8px);color:var(--dsw-alias-label-primary);font:inherit;font-weight:500;padding:1px 6px;height:22px;max-width:190px;min-width:0;cursor:pointer;text-overflow:ellipsis}',
11950
+ '.dig-select-wrap{display:inline-flex;align-items:center;gap:4px;min-width:0;flex:0 1 auto;max-width:90%;color:var(--dsw-alias-label-secondary)}',
11951
+ '.dig-select{appearance:none;-webkit-appearance:none;background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-sm,8px);color:var(--dsw-alias-label-primary);font:inherit;font-weight:500;padding:1px 6px;height:22px;max-width:min(280px,42%);min-width:0;cursor:pointer;text-overflow:ellipsis}',
9855
11952
  '.dig-select:hover{background:var(--dsw-alias-interactive-bg-hover)}',
9856
11953
  '.dig-select:focus-visible{outline:1px solid var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary));outline-offset:-1px}',
9857
11954
  '.dig-select-btn{display:inline-flex;align-items:center;gap:4px;text-align:left;white-space:nowrap;overflow:hidden}',
9858
11955
  '.dig-select-btn .dig-repo-name{flex:1;min-width:0;max-width:none}',
9859
- '.dig-select-repo{max-width:200px}',
11956
+ '.dig-select-repo{max-width:none}',
9860
11957
  '.dig-select option,.dig-filter-select option{background:var(--dsw-alias-bg-layer-1,#22262e);color:var(--dsw-alias-label-primary,#e6e8ee)}',
9861
- '.dig-select-branch{max-width:150px}',
11958
+ /* Long branch names (the topbar picker): the cap lives on the WRAP (a
11959
+ direct topbar child, so the percentage resolves against the bar). The
11960
+ button grows a SECOND LINE instead of ellipsising — a sidebar is
11961
+ ~400px wide, and a 600px branch name simply cannot fit on one line,
11962
+ but two lines hold it (the user-facing rule: show the WHOLE name).
11963
+ The full name also rides on the wrapper's title. */
11964
+ '.dig-select-branch{max-width:none;height:auto;min-height:22px;align-items:center}',
11965
+ '.dig-select-branch .dig-repo-name{white-space:normal;overflow-wrap:anywhere;word-break:break-word;line-height:1.3}',
9862
11966
  '.dig-repo-static{display:inline-flex;align-items:center;gap:5px;min-width:0;color:var(--dsw-alias-label-secondary)}',
9863
- '.dig-repo-name{color:var(--dsw-alias-label-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:200px}',
11967
+ '.dig-repo-name{color:var(--dsw-alias-label-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:none}',
9864
11968
  '.dig-icon-btn{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border:none;border-radius:var(--dsw-radius-sm,8px);background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;padding:0;flex:none}',
9865
11969
  '.dig-icon-btn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}',
9866
11970
  '.dig-icon-btn:disabled{opacity:.4;cursor:default}',
@@ -10009,7 +12113,7 @@ window.__ModuleLoader__.load({
10009
12113
  '.dig-folder{display:flex;flex-direction:column}',
10010
12114
  '.dig-branch-folder{display:flex;flex-direction:column}',
10011
12115
  '.dig-row-picked{background:var(--dsw-alias-interactive-bg-active,var(--dsw-alias-interactive-bg-hover));color:var(--dsw-alias-label-primary)}',
10012
- '.dig-folder-head{display:flex;align-items:center;gap:6px;width:100%;padding:1px 8px 1px 4px;border:none;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:11px;cursor:pointer;text-align:left}',
12116
+ '.dig-folder-head{display:flex;align-items:center;gap:6px;width:100%;padding:1px 8px 1px 4px;height:14px;border:none;background:transparent;color:var(--dsw-alias-label-secondary);font:inherit;font-size:11px;cursor:pointer;text-align:left}',
10013
12117
  '.dig-folder-head:hover{color:var(--dsw-alias-label-primary)}',
10014
12118
  '.dig-folder-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
10015
12119
  '.dig-count{margin-left:auto;opacity:.6;font-weight:400}',
@@ -10044,7 +12148,7 @@ window.__ModuleLoader__.load({
10044
12148
  '.dig-ref-local{color:var(--dsw-alias-brand-primary)}',
10045
12149
  '.dig-ref-remote{color:#b083f0}',
10046
12150
  '.dig-ref-tag{color:var(--dsw-alias-state-warn-primary,var(--dsw-alias-brand-primary))}',
10047
- '.dig-ref-more{color:var(--dsw-alias-label-tertiary)}',
12151
+ '.dig-ref-more{color:var(--dsw-alias-label-tertiary);cursor:pointer}',
10048
12152
  '.dig-filters{display:flex;align-items:center;gap:4px;padding:4px 6px;flex:none;min-width:0;overflow-x:auto;overflow-y:hidden;border-bottom:1px solid var(--dsw-alias-border-l2)}',
10049
12153
  '.dig-filter-text{flex:1 1 90px;min-width:80px;width:auto}',
10050
12154
  '.dig-filter-select{flex:none;max-width:118px;appearance:none;-webkit-appearance:none;background:transparent;border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-sm,8px);color:var(--dsw-alias-label-secondary);font:inherit;font-weight:500;height:22px;padding:0 4px;cursor:pointer;text-overflow:ellipsis}',
@@ -10060,10 +12164,10 @@ window.__ModuleLoader__.load({
10060
12164
  '.dig-changes-body{display:flex;flex-direction:column;min-height:0;flex:1}',
10061
12165
  '.dig-changes-list{flex:1;overflow:auto;min-height:0}',
10062
12166
  '.dig-group{display:flex;flex-direction:column}',
10063
- '.dig-group-head{display:flex;align-items:center;gap:8px;padding:2px 8px;color:var(--dsw-alias-label-secondary);font-size:11px;font-weight:600}',
12167
+ '.dig-group-head{display:flex;align-items:center;gap:8px;padding:2px 8px;height:17px;color:var(--dsw-alias-label-secondary);font-size:11px;font-weight:600}',
10064
12168
  '.dig-link{margin-left:auto;border:none;background:transparent;color:var(--dsw-alias-brand-primary);font:inherit;cursor:pointer;padding:0;flex:none}',
10065
12169
  '.dig-link:hover{text-decoration:underline}',
10066
- '.dig-row-file{gap:6px}',
12170
+ '.dig-row-file{gap:6px;height:20px}',
10067
12171
  '.dig-file-status{flex:none;width:14px;text-align:center;font-weight:700;font-size:10px;color:var(--dsw-alias-state-warn-primary,var(--dsw-alias-label-secondary))}',
10068
12172
  '.dig-file-status-A{color:var(--dsw-alias-state-success-primary)}',
10069
12173
  '.dig-file-status-D{color:var(--dsw-alias-state-error-primary)}',
@@ -10118,15 +12222,37 @@ window.__ModuleLoader__.load({
10118
12222
  '.dig-file-icon{flex:none;opacity:.95}',
10119
12223
  '.dig-overlay{position:absolute;inset:0;padding:8px;box-sizing:border-box;background:var(--dsw-alias-bg-mask-1,rgba(0,0,0,.42));-webkit-backdrop-filter:var(--dsw-mask-blur,none);backdrop-filter:var(--dsw-mask-blur,none);display:flex;align-items:center;justify-content:center;z-index:60}',
10120
12224
  '.dig-dialog{box-sizing:border-box;min-width:min(240px,100%);max-width:min(420px,94%);max-height:100%;overflow:auto;background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-layer-1));border:0;border-radius:var(--dsw-radius-panel,28px);padding:14px;display:flex;flex-direction:column;gap:10px;box-shadow:var(--dsw-elevation-prominent,0 12px 32px rgba(0,0,0,.35))}',
12225
+ /* Git-output drawer (VS Code showOutput): panel-internal absolute bottom
12226
+ sheet (invariant 13). */
12227
+ '.dig-gitlog{position:absolute;left:0;right:0;bottom:0;z-index:55;height:46%;min-height:132px;display:flex;flex-direction:column;background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-layer-1));border-radius:var(--dsw-radius-panel,28px) var(--dsw-radius-panel,28px) 0 0;box-shadow:var(--dsw-elevation-prominent,0 -8px 28px rgba(0,0,0,.3))}',
12228
+ '.dig-gitlog-head{display:flex;align-items:center;gap:4px;padding:8px 14px 6px;flex:none}',
12229
+ '.dig-gitlog-title{font-weight:600}',
12230
+ '.dig-gitlog-empty{padding:10px 14px;color:var(--dsw-alias-label-tertiary)}',
12231
+ '.dig-gitlog-body{flex:1;min-height:0;overflow:auto;padding:0 14px 10px;font-weight:400}',
12232
+ '.dig-gitlog-row{padding:6px 0;border-top:1px solid var(--dsw-alias-border-l1,transparent)}',
12233
+ '.dig-gitlog-row-bad .dig-gitlog-code{color:var(--dsw-alias-state-danger,#e5484d);font-weight:700}',
12234
+ '.dig-gitlog-meta{display:flex;gap:8px;align-items:baseline;flex-wrap:wrap}',
12235
+ '.dig-gitlog-code{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums}',
12236
+ '.dig-gitlog-ms{color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums}',
12237
+ '.dig-gitlog-at{color:var(--dsw-alias-label-tertiary);font-size:11px}',
12238
+ '.dig-gitlog-argv{font-weight:600;word-break:break-all}',
12239
+ '.dig-gitlog-pre{margin:4px 0 0;padding:6px 8px;border-radius:var(--dsw-radius-md,12px);background:var(--dsw-alias-bg-layer-3,var(--dsw-alias-bg-layer-2));overflow:auto;max-height:160px;white-space:pre-wrap;word-break:break-all;font-size:11px}',
10121
12240
  '.dig-dialog-wide{min-width:min(360px,92%)}',
10122
12241
  '.dig-dialog-title{font-weight:600}',
10123
12242
  '.dig-dialog-text{color:var(--dsw-alias-label-secondary);white-space:pre-wrap}',
10124
12243
  '.dig-dialog-actions{display:flex;justify-content:flex-end;gap:8px}',
10125
12244
  '.dig-dialog-actions .dig-btn-primary{margin-left:0}',
10126
- '.dig-menu{position:absolute;z-index:70;box-sizing:border-box;min-width:min(200px,calc(100% - 8px));max-width:calc(100% - 8px);max-height:calc(100% - 8px);padding:4px;border:0;border-radius:var(--dsw-radius-md,12px);background:transparent;box-shadow:var(--dsw-elevation-prominent,0 10px 28px rgba(0,0,0,.35));--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);isolation:isolate;display:flex;flex-direction:column}',
12245
+ /* The wrapper carries the panel-relative anchor and the outside-click ref;
12246
+ the card(s) are plain children. The submenu is a second card offset to
12247
+ the parent card's edge, flipping left when the panel runs out of room
12248
+ (see ContextMenu.openSub). */
12249
+ '.dig-menu-wrap{position:absolute;z-index:70}',
12250
+ '.dig-menu{box-sizing:border-box;min-width:min(200px,calc(100% - 8px));max-width:calc(100% - 8px);max-height:calc(100% - 8px);padding:4px;border:0;border-radius:var(--dsw-radius-md,12px);background:transparent;box-shadow:var(--dsw-elevation-prominent,0 10px 28px rgba(0,0,0,.35));--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);isolation:isolate;display:flex;flex-direction:column}',
12251
+ '.dig-menu-sub{position:absolute;min-width:180px;max-width:260px;z-index:71}',
10127
12252
  '.dig-menu::before{content:"";position:absolute;inset:0;z-index:-1;border-radius:inherit;background:var(--dsw-specific-menu,var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-layer-1)));-webkit-backdrop-filter:var(--dsw-menu-backdrop-filter,blur(14px) saturate(1.2));backdrop-filter:var(--dsw-menu-backdrop-filter,blur(14px) saturate(1.2));pointer-events:none}',
10128
12253
  '.dig-menu-scroll{flex:1;min-height:0;overflow:auto;overscroll-behavior:contain;display:flex;flex-direction:column}',
10129
12254
  '.dig-menu-item{display:flex;align-items:center;gap:8px;padding:4px 8px;border:none;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-weight:500;text-align:left;border-radius:var(--dsw-radius-sm,8px);cursor:pointer;white-space:nowrap;overflow:hidden}',
12255
+ '.dig-menu-chevron{margin-left:auto;opacity:.6;flex:none;display:inline-flex}',
10130
12256
  '.dig-menu-icon{display:inline-flex;flex:none}',
10131
12257
  '.dig-menu-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis}',
10132
12258
  '.dig-menu-item:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}',
@@ -10219,7 +12345,7 @@ window.__ModuleLoader__.load({
10219
12345
  '.dig-lf-tool svg{width:15px;height:15px}',
10220
12346
  '.dig-lf-tool:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}',
10221
12347
  '.dig-lf-tool[aria-pressed=true] .dig-lf-compareIcon{transform:rotate(90deg)}',
10222
- ].join('\n')
12348
+ ].concat(LANE_TRACE_RULES).join('\n')
10223
12349
 
10224
12350
  /* ============================== plugin ============================== */
10225
12351