@theia/scm 1.71.0-next.8 → 1.71.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/lib/browser/dirty-diff/dirty-diff-widget.js +1 -1
- package/lib/browser/dirty-diff/dirty-diff-widget.js.map +1 -1
- package/lib/browser/merge-editor/merge-editor-module.d.ts +6 -0
- package/lib/browser/merge-editor/merge-editor-module.d.ts.map +1 -1
- package/lib/browser/merge-editor/merge-editor-module.js +21 -2
- package/lib/browser/merge-editor/merge-editor-module.js.map +1 -1
- package/lib/browser/scm-context-key-service.d.ts +8 -0
- package/lib/browser/scm-context-key-service.d.ts.map +1 -1
- package/lib/browser/scm-context-key-service.js +16 -0
- package/lib/browser/scm-context-key-service.js.map +1 -1
- package/lib/browser/scm-contribution.d.ts.map +1 -1
- package/lib/browser/scm-contribution.js +143 -1
- package/lib/browser/scm-contribution.js.map +1 -1
- package/lib/browser/scm-frontend-module.d.ts.map +1 -1
- package/lib/browser/scm-frontend-module.js +14 -0
- package/lib/browser/scm-frontend-module.js.map +1 -1
- package/lib/browser/scm-history-graph-helpers.d.ts +39 -0
- package/lib/browser/scm-history-graph-helpers.d.ts.map +1 -0
- package/lib/browser/scm-history-graph-helpers.js +167 -0
- package/lib/browser/scm-history-graph-helpers.js.map +1 -0
- package/lib/browser/scm-history-graph-lanes.d.ts +59 -0
- package/lib/browser/scm-history-graph-lanes.d.ts.map +1 -0
- package/lib/browser/scm-history-graph-lanes.js +183 -0
- package/lib/browser/scm-history-graph-lanes.js.map +1 -0
- package/lib/browser/scm-history-graph-lanes.spec.d.ts +2 -0
- package/lib/browser/scm-history-graph-lanes.spec.d.ts.map +1 -0
- package/lib/browser/scm-history-graph-lanes.spec.js +554 -0
- package/lib/browser/scm-history-graph-lanes.spec.js.map +1 -0
- package/lib/browser/scm-history-graph-model.d.ts +46 -0
- package/lib/browser/scm-history-graph-model.d.ts.map +1 -0
- package/lib/browser/scm-history-graph-model.js +184 -0
- package/lib/browser/scm-history-graph-model.js.map +1 -0
- package/lib/browser/scm-history-graph-model.spec.d.ts +2 -0
- package/lib/browser/scm-history-graph-model.spec.d.ts.map +1 -0
- package/lib/browser/scm-history-graph-model.spec.js +131 -0
- package/lib/browser/scm-history-graph-model.spec.js.map +1 -0
- package/lib/browser/scm-history-graph-tooltip.d.ts +14 -0
- package/lib/browser/scm-history-graph-tooltip.d.ts.map +1 -0
- package/lib/browser/scm-history-graph-tooltip.js +190 -0
- package/lib/browser/scm-history-graph-tooltip.js.map +1 -0
- package/lib/browser/scm-history-graph-widget.d.ts +77 -0
- package/lib/browser/scm-history-graph-widget.d.ts.map +1 -0
- package/lib/browser/scm-history-graph-widget.js +490 -0
- package/lib/browser/scm-history-graph-widget.js.map +1 -0
- package/lib/browser/scm-provider.d.ts +61 -0
- package/lib/browser/scm-provider.d.ts.map +1 -1
- package/lib/browser/scm-provider.js.map +1 -1
- package/lib/browser/scm-repositories-widget.d.ts +6 -1
- package/lib/browser/scm-repositories-widget.d.ts.map +1 -1
- package/lib/browser/scm-repositories-widget.js +11 -4
- package/lib/browser/scm-repositories-widget.js.map +1 -1
- package/lib/browser/scm-repositories-widget.spec.js +1 -1
- package/lib/browser/scm-repositories-widget.spec.js.map +1 -1
- package/lib/browser/scm-service.d.ts.map +1 -1
- package/lib/browser/scm-service.js +4 -1
- package/lib/browser/scm-service.js.map +1 -1
- package/lib/browser/scm-service.spec.d.ts +2 -0
- package/lib/browser/scm-service.spec.d.ts.map +1 -0
- package/lib/browser/scm-service.spec.js +77 -0
- package/lib/browser/scm-service.spec.js.map +1 -0
- package/package.json +11 -11
- package/src/browser/dirty-diff/dirty-diff-widget.ts +1 -1
- package/src/browser/merge-editor/merge-editor-module.ts +24 -6
- package/src/browser/scm-context-key-service.ts +24 -0
- package/src/browser/scm-contribution.ts +157 -0
- package/src/browser/scm-frontend-module.ts +15 -0
- package/src/browser/scm-history-graph-helpers.ts +175 -0
- package/src/browser/scm-history-graph-lanes.spec.ts +635 -0
- package/src/browser/scm-history-graph-lanes.ts +258 -0
- package/src/browser/scm-history-graph-model.spec.ts +171 -0
- package/src/browser/scm-history-graph-model.ts +207 -0
- package/src/browser/scm-history-graph-tooltip.ts +213 -0
- package/src/browser/scm-history-graph-widget.tsx +712 -0
- package/src/browser/scm-provider.ts +68 -0
- package/src/browser/scm-repositories-widget.spec.ts +1 -1
- package/src/browser/scm-repositories-widget.tsx +10 -3
- package/src/browser/scm-service.spec.ts +91 -0
- package/src/browser/scm-service.ts +4 -1
- package/src/browser/style/index.css +12 -13
- package/src/browser/style/scm-history-graph.css +313 -0
|
@@ -28,6 +28,11 @@ export class ScmContextKeyService {
|
|
|
28
28
|
return this._scmProvider;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
protected _scmProviderContext: ContextKey<string | undefined>;
|
|
32
|
+
get scmProviderContext(): ContextKey<string | undefined> {
|
|
33
|
+
return this._scmProviderContext;
|
|
34
|
+
}
|
|
35
|
+
|
|
31
36
|
protected _scmResourceGroup: ContextKey<string | undefined>;
|
|
32
37
|
get scmResourceGroup(): ContextKey<string | undefined> {
|
|
33
38
|
return this._scmResourceGroup;
|
|
@@ -43,12 +48,31 @@ export class ScmContextKeyService {
|
|
|
43
48
|
return this._scmProviderCount;
|
|
44
49
|
}
|
|
45
50
|
|
|
51
|
+
protected _scmHistoryItemRef: ContextKey<string | undefined>;
|
|
52
|
+
get scmHistoryItemRef(): ContextKey<string | undefined> {
|
|
53
|
+
return this._scmHistoryItemRef;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
protected _scmCurrentHistoryItemRefHasRemote: ContextKey<boolean>;
|
|
57
|
+
get scmCurrentHistoryItemRefHasRemote(): ContextKey<boolean> {
|
|
58
|
+
return this._scmCurrentHistoryItemRefHasRemote;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
protected _scmCurrentHistoryItemRefHasBase: ContextKey<boolean>;
|
|
62
|
+
get scmCurrentHistoryItemRefHasBase(): ContextKey<boolean> {
|
|
63
|
+
return this._scmCurrentHistoryItemRefHasBase;
|
|
64
|
+
}
|
|
65
|
+
|
|
46
66
|
@postConstruct()
|
|
47
67
|
protected init(): void {
|
|
48
68
|
this._scmProvider = this.contextKeyService.createKey<string | undefined>('scmProvider', undefined);
|
|
69
|
+
this._scmProviderContext = this.contextKeyService.createKey<string | undefined>('scmProviderContext', undefined);
|
|
49
70
|
this._scmResourceGroup = this.contextKeyService.createKey<string | undefined>('scmResourceGroup', undefined);
|
|
50
71
|
this._scmResourceGroupState = this.contextKeyService.createKey<string | undefined>('scmResourceGroupState', undefined);
|
|
51
72
|
this._scmProviderCount = this.contextKeyService.createKey<number>('scm.providerCount', 0);
|
|
73
|
+
this._scmHistoryItemRef = this.contextKeyService.createKey<string | undefined>('scmHistoryItemRef', undefined);
|
|
74
|
+
this._scmCurrentHistoryItemRefHasRemote = this.contextKeyService.createKey<boolean>('scmCurrentHistoryItemRefHasRemote', false);
|
|
75
|
+
this._scmCurrentHistoryItemRefHasBase = this.contextKeyService.createKey<boolean>('scmCurrentHistoryItemRefHasBase', false);
|
|
52
76
|
}
|
|
53
77
|
|
|
54
78
|
match(expression: string | undefined): boolean {
|
|
@@ -411,6 +411,147 @@ export class ScmContribution extends AbstractViewContribution<ScmWidget> impleme
|
|
|
411
411
|
*/
|
|
412
412
|
registerColors(colors: ColorRegistry): void {
|
|
413
413
|
colors.register(
|
|
414
|
+
// SCM Graph lane colors (matching VS Code's scm.graph.* color IDs)
|
|
415
|
+
{
|
|
416
|
+
id: 'scmGraph.historyItemHoverDefaultLabelForeground',
|
|
417
|
+
defaults: {
|
|
418
|
+
dark: '#0078d4',
|
|
419
|
+
light: '#0078d4',
|
|
420
|
+
hcDark: '#0078d4',
|
|
421
|
+
hcLight: '#0078d4'
|
|
422
|
+
},
|
|
423
|
+
description: 'Default foreground color for history item labels in the SCM history graph on hover.'
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
id: 'scmGraph.historyItemHoverAdditionsForeground',
|
|
427
|
+
defaults: {
|
|
428
|
+
dark: '#81b88b',
|
|
429
|
+
light: '#388a34',
|
|
430
|
+
hcDark: '#81b88b',
|
|
431
|
+
hcLight: '#388a34'
|
|
432
|
+
},
|
|
433
|
+
description: 'Foreground color for additions in the SCM history graph on hover.'
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
id: 'scmGraph.historyItemHoverDeletionsForeground',
|
|
437
|
+
defaults: {
|
|
438
|
+
dark: '#c74e39',
|
|
439
|
+
light: '#a1260d',
|
|
440
|
+
hcDark: '#c74e39',
|
|
441
|
+
hcLight: '#a1260d'
|
|
442
|
+
},
|
|
443
|
+
description: 'Foreground color for deletions in the SCM history graph on hover.'
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
id: 'scmGraph.historyItemHoverLabelForeground',
|
|
447
|
+
defaults: {
|
|
448
|
+
dark: '#e2e2e2',
|
|
449
|
+
light: '#3b3b3b',
|
|
450
|
+
hcDark: '#ffffff',
|
|
451
|
+
hcLight: '#000000'
|
|
452
|
+
},
|
|
453
|
+
description: 'Foreground color for labels in the SCM history graph on hover.'
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
id: 'scmGraph.historyItemRefForeground',
|
|
457
|
+
defaults: {
|
|
458
|
+
dark: '#ffffff',
|
|
459
|
+
light: '#ffffff',
|
|
460
|
+
hcDark: '#ffffff',
|
|
461
|
+
hcLight: '#ffffff'
|
|
462
|
+
},
|
|
463
|
+
description: 'Foreground color for ref badge labels in the SCM history graph.'
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
id: 'scmGraph.historyItemRefColor',
|
|
467
|
+
defaults: {
|
|
468
|
+
dark: '#0078d4',
|
|
469
|
+
light: '#0078d4',
|
|
470
|
+
hcDark: '#0078d4',
|
|
471
|
+
hcLight: '#0078d4'
|
|
472
|
+
},
|
|
473
|
+
description: 'Color for ref labels in the SCM history graph.'
|
|
474
|
+
},
|
|
475
|
+
{
|
|
476
|
+
id: 'scmGraph.historyItemRemoteRefColor',
|
|
477
|
+
defaults: {
|
|
478
|
+
dark: '#b267e6',
|
|
479
|
+
light: '#8b009b',
|
|
480
|
+
hcDark: '#b267e6',
|
|
481
|
+
hcLight: '#8b009b'
|
|
482
|
+
},
|
|
483
|
+
description: 'Color for remote ref labels in the SCM history graph.'
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
id: 'scmGraph.historyItemTagRefColor',
|
|
487
|
+
defaults: {
|
|
488
|
+
dark: '#d7ba7d',
|
|
489
|
+
light: '#8d6914',
|
|
490
|
+
hcDark: '#d7ba7d',
|
|
491
|
+
hcLight: '#8d6914'
|
|
492
|
+
},
|
|
493
|
+
description: 'Color for tag ref labels in the SCM history graph.'
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
id: 'scmGraph.historyItemBaseRefColor',
|
|
497
|
+
defaults: {
|
|
498
|
+
dark: '#a1260d',
|
|
499
|
+
light: '#a1260d',
|
|
500
|
+
hcDark: '#a1260d',
|
|
501
|
+
hcLight: '#a1260d'
|
|
502
|
+
},
|
|
503
|
+
description: 'Color for base ref labels in the SCM history graph.'
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
id: 'scmGraph.foreground1',
|
|
507
|
+
defaults: {
|
|
508
|
+
dark: '#ffb000',
|
|
509
|
+
light: '#ffb000',
|
|
510
|
+
hcDark: '#ffb000',
|
|
511
|
+
hcLight: '#ffb000'
|
|
512
|
+
},
|
|
513
|
+
description: 'Foreground color 1 for additional lanes in the SCM history graph.'
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
id: 'scmGraph.foreground2',
|
|
517
|
+
defaults: {
|
|
518
|
+
dark: '#dc267f',
|
|
519
|
+
light: '#dc267f',
|
|
520
|
+
hcDark: '#dc267f',
|
|
521
|
+
hcLight: '#dc267f'
|
|
522
|
+
},
|
|
523
|
+
description: 'Foreground color 2 for additional lanes in the SCM history graph.'
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
id: 'scmGraph.foreground3',
|
|
527
|
+
defaults: {
|
|
528
|
+
dark: '#994f00',
|
|
529
|
+
light: '#994f00',
|
|
530
|
+
hcDark: '#994f00',
|
|
531
|
+
hcLight: '#994f00'
|
|
532
|
+
},
|
|
533
|
+
description: 'Foreground color 3 for additional lanes in the SCM history graph.'
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
id: 'scmGraph.foreground4',
|
|
537
|
+
defaults: {
|
|
538
|
+
dark: '#40b0a6',
|
|
539
|
+
light: '#40b0a6',
|
|
540
|
+
hcDark: '#40b0a6',
|
|
541
|
+
hcLight: '#40b0a6'
|
|
542
|
+
},
|
|
543
|
+
description: 'Foreground color 4 for additional lanes in the SCM history graph.'
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
id: 'scmGraph.foreground5',
|
|
547
|
+
defaults: {
|
|
548
|
+
dark: '#b66dff',
|
|
549
|
+
light: '#b66dff',
|
|
550
|
+
hcDark: '#b66dff',
|
|
551
|
+
hcLight: '#b66dff'
|
|
552
|
+
},
|
|
553
|
+
description: 'Foreground color 5 for additional lanes in the SCM history graph.'
|
|
554
|
+
},
|
|
414
555
|
{
|
|
415
556
|
id: ScmColors.editorGutterModifiedBackground, defaults: {
|
|
416
557
|
dark: '#1B81A8',
|
|
@@ -482,6 +623,22 @@ export class ScmContribution extends AbstractViewContribution<ScmWidget> impleme
|
|
|
482
623
|
hcDark: Color.transparent(ScmColors.editorGutterDeletedBackground, 0.6),
|
|
483
624
|
hcLight: Color.transparent(ScmColors.editorGutterDeletedBackground, 0.6)
|
|
484
625
|
}, description: 'Overview ruler marker color for deleted content.'
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
id: 'scmGraph.historyItemHoverAdditionsForeground', defaults: {
|
|
629
|
+
dark: '#81B88B',
|
|
630
|
+
light: '#587C0C',
|
|
631
|
+
hcDark: '#A1E3AD',
|
|
632
|
+
hcLight: '#374E06'
|
|
633
|
+
}, description: 'History item hover additions foreground color.'
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
id: 'scmGraph.historyItemHoverDeletionsForeground', defaults: {
|
|
637
|
+
dark: '#C74E39',
|
|
638
|
+
light: '#AD0707',
|
|
639
|
+
hcDark: '#C74E39',
|
|
640
|
+
hcLight: '#AD0707'
|
|
641
|
+
}, description: 'History item hover deletions foreground color.'
|
|
485
642
|
}
|
|
486
643
|
);
|
|
487
644
|
}
|
|
@@ -45,6 +45,8 @@ import { LabelProviderContribution } from '@theia/core/lib/browser/label-provide
|
|
|
45
45
|
import { bindScmPreferences } from '../common/scm-preferences';
|
|
46
46
|
import { bindMergeEditor } from './merge-editor/merge-editor-module';
|
|
47
47
|
import { ScmRepositoriesWidget } from './scm-repositories-widget';
|
|
48
|
+
import { ScmHistoryGraphWidget } from './scm-history-graph-widget';
|
|
49
|
+
import { ScmHistoryGraphModel } from './scm-history-graph-model';
|
|
48
50
|
|
|
49
51
|
export default new ContainerModule(bind => {
|
|
50
52
|
bind(ScmContextKeyService).toSelf().inSingletonScope();
|
|
@@ -88,6 +90,13 @@ export default new ContainerModule(bind => {
|
|
|
88
90
|
createWidget: () => container.get(ScmRepositoriesWidget)
|
|
89
91
|
})).inSingletonScope();
|
|
90
92
|
|
|
93
|
+
bind(ScmHistoryGraphModel).toSelf().inSingletonScope();
|
|
94
|
+
bind(ScmHistoryGraphWidget).toSelf();
|
|
95
|
+
bind(WidgetFactory).toDynamicValue(({ container }) => ({
|
|
96
|
+
id: ScmHistoryGraphWidget.ID,
|
|
97
|
+
createWidget: () => container.get(ScmHistoryGraphWidget)
|
|
98
|
+
})).inSingletonScope();
|
|
99
|
+
|
|
91
100
|
bind(WidgetFactory).toDynamicValue(({ container }) => ({
|
|
92
101
|
id: SCM_VIEW_CONTAINER_ID,
|
|
93
102
|
createWidget: async () => {
|
|
@@ -110,6 +119,12 @@ export default new ContainerModule(bind => {
|
|
|
110
119
|
canHide: false,
|
|
111
120
|
initiallyCollapsed: false
|
|
112
121
|
});
|
|
122
|
+
const graphWidget = await widgetManager.getOrCreateWidget(ScmHistoryGraphWidget.ID);
|
|
123
|
+
viewContainer.addWidget(graphWidget, {
|
|
124
|
+
order: 2,
|
|
125
|
+
canHide: true,
|
|
126
|
+
initiallyCollapsed: true
|
|
127
|
+
});
|
|
113
128
|
return viewContainer;
|
|
114
129
|
}
|
|
115
130
|
})).inSingletonScope();
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 EclipseSource GmbH and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { ScmHistoryItemRef, ScmHistoryItemChange } from './scm-provider';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Returns the CSS color variable for the given lane index.
|
|
21
|
+
* Uses Theia's `--theia-scmGraph-*` variables, mirroring the VS Code
|
|
22
|
+
* scm graph color scheme:
|
|
23
|
+
* lane 0 (current ref) → historyItemRefColor
|
|
24
|
+
* lane 1 (remote ref) → historyItemRemoteRefColor
|
|
25
|
+
* lane 2 (base ref) → historyItemBaseRefColor
|
|
26
|
+
* lane 3–7 → foreground1–5
|
|
27
|
+
*/
|
|
28
|
+
export function laneColor(index: number): string {
|
|
29
|
+
switch (index % 8) {
|
|
30
|
+
case 0: return 'var(--theia-scmGraph-historyItemRefColor)';
|
|
31
|
+
case 1: return 'var(--theia-scmGraph-historyItemRemoteRefColor)';
|
|
32
|
+
case 2: return 'var(--theia-scmGraph-historyItemBaseRefColor)';
|
|
33
|
+
case 3: return 'var(--theia-scmGraph-foreground1)';
|
|
34
|
+
case 4: return 'var(--theia-scmGraph-foreground2)';
|
|
35
|
+
case 5: return 'var(--theia-scmGraph-foreground3)';
|
|
36
|
+
case 6: return 'var(--theia-scmGraph-foreground4)';
|
|
37
|
+
default: return 'var(--theia-scmGraph-foreground5)';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function getChangeStatus(change: ScmHistoryItemChange): string {
|
|
42
|
+
if (!change.originalUri) {
|
|
43
|
+
return 'A';
|
|
44
|
+
}
|
|
45
|
+
if (!change.modifiedUri) {
|
|
46
|
+
return 'D';
|
|
47
|
+
}
|
|
48
|
+
if (change.renameUri) {
|
|
49
|
+
return 'R';
|
|
50
|
+
}
|
|
51
|
+
return 'M';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function getFileName(uri: string): string {
|
|
55
|
+
const parts = uri.split('/');
|
|
56
|
+
return parts[parts.length - 1] || uri;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function getFilePath(uri: string): string {
|
|
60
|
+
try {
|
|
61
|
+
const u = new URL(uri);
|
|
62
|
+
return u.pathname;
|
|
63
|
+
} catch {
|
|
64
|
+
return uri;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Returns the repo-relative path of the given URI, stripping the rootUri prefix.
|
|
70
|
+
* Falls back to the full path if rootUri is unavailable or doesn't match.
|
|
71
|
+
*/
|
|
72
|
+
export function getRepoRelativePath(uri: string, rootUri: string | undefined): string {
|
|
73
|
+
const fullPath = getFilePath(uri);
|
|
74
|
+
if (!rootUri) {
|
|
75
|
+
return fullPath;
|
|
76
|
+
}
|
|
77
|
+
const rootPath = getFilePath(rootUri);
|
|
78
|
+
// Normalize: ensure rootPath ends with '/'
|
|
79
|
+
const rootPrefix = rootPath.endsWith('/') ? rootPath : rootPath + '/';
|
|
80
|
+
if (fullPath.startsWith(rootPrefix)) {
|
|
81
|
+
return fullPath.slice(rootPrefix.length);
|
|
82
|
+
}
|
|
83
|
+
return fullPath;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function getRefBadgeClass(ref: ScmHistoryItemRef): string {
|
|
87
|
+
const cat = (ref.category ?? '').toLowerCase();
|
|
88
|
+
if (cat === 'heads' || cat === 'head' || ref.id.startsWith('refs/heads/')) {
|
|
89
|
+
return 'head';
|
|
90
|
+
}
|
|
91
|
+
if (cat === 'remotes' || cat === 'remote' || ref.id.startsWith('refs/remotes/')) {
|
|
92
|
+
return 'remote';
|
|
93
|
+
}
|
|
94
|
+
if (cat === 'tags' || cat === 'tag' || ref.id.startsWith('refs/tags/')) {
|
|
95
|
+
return 'tag';
|
|
96
|
+
}
|
|
97
|
+
if (cat === 'base') {
|
|
98
|
+
return 'base';
|
|
99
|
+
}
|
|
100
|
+
return 'head';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function isTagRef(ref: ScmHistoryItemRef): boolean {
|
|
104
|
+
const cat = (ref.category ?? '').toLowerCase();
|
|
105
|
+
if (cat === 'tags' || cat === 'tag') {
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
// Fall back to checking the ref id prefix (e.g. 'refs/tags/v1.0')
|
|
109
|
+
return ref.id.startsWith('refs/tags/');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function isRemoteRef(ref: ScmHistoryItemRef): boolean {
|
|
113
|
+
const cat = (ref.category ?? '').toLowerCase();
|
|
114
|
+
if (cat === 'remotes' || cat === 'remote') {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
// Fall back to checking the ref id prefix (e.g. 'refs/remotes/origin/main')
|
|
118
|
+
return ref.id.startsWith('refs/remotes/');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Extracts the local branch name from a remote ref name like "origin/master" → "master".
|
|
123
|
+
* Falls back to the full name if no slash is found.
|
|
124
|
+
*/
|
|
125
|
+
export function getLocalNameFromRemote(remoteName: string): string {
|
|
126
|
+
const slashIdx = remoteName.indexOf('/');
|
|
127
|
+
return slashIdx >= 0 ? remoteName.slice(slashIdx + 1) : remoteName;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export interface DeduplicatedRef {
|
|
131
|
+
ref: ScmHistoryItemRef;
|
|
132
|
+
/** True when both a local and a remote ref for this branch exist on this commit. */
|
|
133
|
+
hasBoth: boolean;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Deduplicates refs: when a local branch (e.g. "master") and a remote branch
|
|
138
|
+
* (e.g. "origin/master") both appear, collapse them into one entry with `hasBoth=true`.
|
|
139
|
+
* Tags and other ref types are passed through unchanged.
|
|
140
|
+
*/
|
|
141
|
+
export function deduplicateRefs(refs: readonly ScmHistoryItemRef[]): DeduplicatedRef[] {
|
|
142
|
+
const localNames = new Set<string>();
|
|
143
|
+
for (const ref of refs) {
|
|
144
|
+
if (!isRemoteRef(ref) && !isTagRef(ref)) {
|
|
145
|
+
localNames.add(ref.name.toLowerCase());
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const result: DeduplicatedRef[] = [];
|
|
150
|
+
const suppressedRemotes = new Set<string>();
|
|
151
|
+
|
|
152
|
+
// First pass: identify remote refs that have a matching local branch
|
|
153
|
+
for (const ref of refs) {
|
|
154
|
+
if (isRemoteRef(ref)) {
|
|
155
|
+
const localName = getLocalNameFromRemote(ref.name).toLowerCase();
|
|
156
|
+
if (localNames.has(localName)) {
|
|
157
|
+
suppressedRemotes.add(ref.id);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Second pass: emit all refs except suppressed remote ones;
|
|
163
|
+
// mark local branches that have a matching remote with hasBoth=true
|
|
164
|
+
for (const ref of refs) {
|
|
165
|
+
if (suppressedRemotes.has(ref.id)) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const hasBoth = !isRemoteRef(ref) && !isTagRef(ref)
|
|
169
|
+
? refs.some(r => isRemoteRef(r) && getLocalNameFromRemote(r.name).toLowerCase() === ref.name.toLowerCase())
|
|
170
|
+
: false;
|
|
171
|
+
result.push({ ref, hasBoth });
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return result;
|
|
175
|
+
}
|