Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/sessions/contrib/applyCommitsToParentRepo/browser/applyChangesToParentRepo.ts
13579 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { toAction } from '../../../../base/common/actions.js';
7
import { Disposable } from '../../../../base/common/lifecycle.js';
8
import { Schemas } from '../../../../base/common/network.js';
9
import { autorun } from '../../../../base/common/observable.js';
10
import { Codicon } from '../../../../base/common/codicons.js';
11
import { localize, localize2 } from '../../../../nls.js';
12
import { Action2, MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js';
13
import { ICommandService } from '../../../../platform/commands/common/commands.js';
14
import { ContextKeyExpr, IContextKeyService, RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';
15
import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
16
import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js';
17
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
18
import { IProductService } from '../../../../platform/product/common/productService.js';
19
import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../../workbench/common/contributions.js';
20
import { IsSessionsWindowContext } from '../../../../workbench/common/contextkeys.js';
21
import { CHAT_CATEGORY } from '../../../../workbench/contrib/chat/browser/actions/chatActions.js';
22
import { ISessionsManagementService } from '../../../services/sessions/common/sessionsManagement.js';
23
import { ILogService } from '../../../../platform/log/common/log.js';
24
import { URI } from '../../../../base/common/uri.js';
25
26
const hasWorktreeAndRepositoryContextKey = new RawContextKey<boolean>('agentSessionHasWorktreeAndRepository', false, {
27
type: 'boolean',
28
description: localize('agentSessionHasWorktreeAndRepository', "True when the active agent session has both a worktree and a parent repository.")
29
});
30
31
class ApplyChangesToParentRepoContribution extends Disposable implements IWorkbenchContribution {
32
33
static readonly ID = 'sessions.contrib.applyChangesToParentRepo';
34
35
constructor(
36
@IContextKeyService contextKeyService: IContextKeyService,
37
@ISessionsManagementService sessionManagementService: ISessionsManagementService,
38
) {
39
super();
40
41
const worktreeAndRepoKey = hasWorktreeAndRepositoryContextKey.bindTo(contextKeyService);
42
43
this._register(autorun(reader => {
44
const activeSession = sessionManagementService.activeSession.read(reader);
45
const repo = activeSession?.workspace.read(reader)?.repositories[0];
46
const hasWorktreeAndRepo = !!repo?.workingDirectory && !!repo?.uri;
47
worktreeAndRepoKey.set(hasWorktreeAndRepo);
48
}));
49
}
50
}
51
52
class ApplyChangesToParentRepoAction extends Action2 {
53
static readonly ID = 'chatEditing.applyChangesToParentRepo';
54
55
constructor() {
56
super({
57
id: ApplyChangesToParentRepoAction.ID,
58
title: localize2('applyChangesToParentRepo', 'Apply Changes to Parent Repository'),
59
icon: Codicon.desktopDownload,
60
category: CHAT_CATEGORY,
61
precondition: ContextKeyExpr.and(
62
IsSessionsWindowContext,
63
hasWorktreeAndRepositoryContextKey,
64
),
65
menu: [
66
{
67
id: MenuId.ChatEditingSessionApplySubmenu,
68
group: 'navigation',
69
order: 2,
70
when: ContextKeyExpr.and(
71
ContextKeyExpr.false(),
72
IsSessionsWindowContext,
73
hasWorktreeAndRepositoryContextKey
74
),
75
},
76
],
77
});
78
}
79
80
override async run(accessor: ServicesAccessor): Promise<void> {
81
const sessionManagementService = accessor.get(ISessionsManagementService);
82
const commandService = accessor.get(ICommandService);
83
const notificationService = accessor.get(INotificationService);
84
const logService = accessor.get(ILogService);
85
const openerService = accessor.get(IOpenerService);
86
const productService = accessor.get(IProductService);
87
88
const activeSession = sessionManagementService.activeSession.get();
89
const repo = activeSession?.workspace.get()?.repositories[0];
90
if (!activeSession || !repo?.workingDirectory || !repo?.uri) {
91
return;
92
}
93
94
const worktreeRoot = repo.workingDirectory;
95
const repoRoot = repo.uri;
96
97
const openFolderAction = toAction({
98
id: 'applyChangesToParentRepo.openFolder',
99
label: localize('openInVSCode', "Open in VS Code"),
100
run: () => {
101
const scheme = productService.quality === 'stable'
102
? 'vscode'
103
: productService.quality === 'exploration'
104
? 'vscode-exploration'
105
: 'vscode-insiders';
106
107
const params = new URLSearchParams();
108
params.set('windowId', '_blank');
109
params.set('session', activeSession.resource.toString());
110
111
openerService.open(URI.from({
112
scheme,
113
authority: Schemas.file,
114
path: repoRoot.path,
115
query: params.toString(),
116
}), { openExternal: true });
117
}
118
});
119
120
try {
121
// Get the worktree branch name. Since the worktree and parent repo
122
// share the same git object store, the parent can directly reference
123
// this branch for a merge.
124
const worktreeBranch = await commandService.executeCommand<string>(
125
'_git.revParseAbbrevRef',
126
worktreeRoot.fsPath
127
);
128
129
if (!worktreeBranch) {
130
notificationService.notify({
131
severity: Severity.Warning,
132
message: localize('applyChangesNoBranch', "Could not determine worktree branch name."),
133
});
134
return;
135
}
136
137
// Merge the worktree branch into the parent repo.
138
// This is idempotent: if already merged, git says "Already up to date."
139
// If new commits exist, they're brought in. Handles partial applies naturally.
140
const result = await commandService.executeCommand('_git.mergeBranch', repoRoot.fsPath, worktreeBranch);
141
if (!result) {
142
logService.warn('[ApplyChangesToParentRepo] No result from merge command');
143
} else {
144
notificationService.notify({
145
severity: Severity.Info,
146
message: typeof result === 'string' && result.startsWith('Already up to date')
147
? localize('alreadyUpToDate', 'Parent repository is up to date with worktree.')
148
: localize('applyChangesSuccess', 'Applied changes to parent repository.'),
149
actions: { primary: [openFolderAction] }
150
});
151
}
152
} catch (err) {
153
logService.error('[ApplyChangesToParentRepo] Failed to apply changes', err);
154
notificationService.notify({
155
severity: Severity.Warning,
156
message: localize('applyChangesConflict', "Failed to apply changes to parent repo. The parent repo may have diverged — resolve conflicts manually."),
157
actions: { primary: [openFolderAction] }
158
});
159
}
160
}
161
}
162
163
registerAction2(ApplyChangesToParentRepoAction);
164
registerWorkbenchContribution2(ApplyChangesToParentRepoContribution.ID, ApplyChangesToParentRepoContribution, WorkbenchPhase.AfterRestored);
165
166
// Register the apply submenu in the session changes toolbar
167
MenuRegistry.appendMenuItem(MenuId.ChatEditingSessionChangesToolbar, {
168
submenu: MenuId.ChatEditingSessionApplySubmenu,
169
title: localize2('applyActions', 'Apply Actions'),
170
group: 'navigation',
171
order: 1,
172
when: IsSessionsWindowContext,
173
});
174
175