Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/git/src/postCommitCommands.ts
3316 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 { Command, commands, Disposable, Event, EventEmitter, Memento, Uri, workspace, l10n } from 'vscode';
7
import { PostCommitCommandsProvider } from './api/git';
8
import { IRepositoryResolver, Repository } from './repository';
9
import { ApiRepository } from './api/api1';
10
import { dispose } from './util';
11
import { OperationKind } from './operation';
12
13
export interface IPostCommitCommandsProviderRegistry {
14
readonly onDidChangePostCommitCommandsProviders: Event<void>;
15
16
getPostCommitCommandsProviders(): PostCommitCommandsProvider[];
17
registerPostCommitCommandsProvider(provider: PostCommitCommandsProvider): Disposable;
18
}
19
20
export class GitPostCommitCommandsProvider implements PostCommitCommandsProvider {
21
constructor(private readonly _repositoryResolver: IRepositoryResolver) { }
22
23
getCommands(apiRepository: ApiRepository): Command[] {
24
const repository = this._repositoryResolver.getRepository(apiRepository.rootUri);
25
if (!repository) {
26
return [];
27
}
28
29
const config = workspace.getConfiguration('git', Uri.file(repository.root));
30
31
// Branch protection
32
const isBranchProtected = repository.isBranchProtected();
33
const branchProtectionPrompt = config.get<'alwaysCommit' | 'alwaysCommitToNewBranch' | 'alwaysPrompt'>('branchProtectionPrompt')!;
34
const alwaysPrompt = isBranchProtected && branchProtectionPrompt === 'alwaysPrompt';
35
const alwaysCommitToNewBranch = isBranchProtected && branchProtectionPrompt === 'alwaysCommitToNewBranch';
36
37
// Icon
38
const isCommitInProgress = repository.operations.isRunning(OperationKind.Commit) || repository.operations.isRunning(OperationKind.PostCommitCommand);
39
const icon = isCommitInProgress ? '$(sync~spin)' : alwaysPrompt ? '$(lock)' : alwaysCommitToNewBranch ? '$(git-branch)' : undefined;
40
41
// Tooltip (default)
42
let pushCommandTooltip = !alwaysCommitToNewBranch ?
43
l10n.t('Commit & Push Changes') :
44
l10n.t('Commit to New Branch & Push Changes');
45
46
let syncCommandTooltip = !alwaysCommitToNewBranch ?
47
l10n.t('Commit & Sync Changes') :
48
l10n.t('Commit to New Branch & Synchronize Changes');
49
50
// Tooltip (in progress)
51
if (isCommitInProgress) {
52
pushCommandTooltip = !alwaysCommitToNewBranch ?
53
l10n.t('Committing & Pushing Changes...') :
54
l10n.t('Committing to New Branch & Pushing Changes...');
55
56
syncCommandTooltip = !alwaysCommitToNewBranch ?
57
l10n.t('Committing & Synchronizing Changes...') :
58
l10n.t('Committing to New Branch & Synchronizing Changes...');
59
}
60
61
return [
62
{
63
command: 'git.push',
64
title: l10n.t('{0} Commit & Push', icon ?? '$(arrow-up)'),
65
tooltip: pushCommandTooltip
66
},
67
{
68
command: 'git.sync',
69
title: l10n.t('{0} Commit & Sync', icon ?? '$(sync)'),
70
tooltip: syncCommandTooltip
71
},
72
];
73
}
74
}
75
76
export class CommitCommandsCenter {
77
78
private _onDidChange = new EventEmitter<void>();
79
get onDidChange(): Event<void> { return this._onDidChange.event; }
80
81
private disposables: Disposable[] = [];
82
83
set postCommitCommand(command: string | null | undefined) {
84
if (command === undefined) {
85
// Commit WAS NOT initiated using the action button
86
// so there is no need to store the post-commit command
87
return;
88
}
89
90
this.globalState.update(this.getGlobalStateKey(), command)
91
.then(() => this._onDidChange.fire());
92
}
93
94
constructor(
95
private readonly globalState: Memento,
96
private readonly repository: Repository,
97
private readonly postCommitCommandsProviderRegistry: IPostCommitCommandsProviderRegistry
98
) {
99
const root = Uri.file(repository.root);
100
101
// Migrate post commit command storage
102
this.migratePostCommitCommandStorage()
103
.then(() => {
104
const onRememberPostCommitCommandChange = async () => {
105
const config = workspace.getConfiguration('git', root);
106
if (!config.get<boolean>('rememberPostCommitCommand')) {
107
await this.globalState.update(this.getGlobalStateKey(), undefined);
108
}
109
};
110
this.disposables.push(workspace.onDidChangeConfiguration(e => {
111
if (e.affectsConfiguration('git.rememberPostCommitCommand', root)) {
112
onRememberPostCommitCommandChange();
113
}
114
}));
115
onRememberPostCommitCommandChange();
116
117
this.disposables.push(postCommitCommandsProviderRegistry.onDidChangePostCommitCommandsProviders(() => this._onDidChange.fire()));
118
});
119
}
120
121
getPrimaryCommand(): Command {
122
const allCommands = this.getSecondaryCommands().map(c => c).flat();
123
const commandFromStorage = allCommands.find(c => c.arguments?.length === 2 && c.arguments[1] === this.getPostCommitCommandStringFromStorage());
124
const commandFromSetting = allCommands.find(c => c.arguments?.length === 2 && c.arguments[1] === this.getPostCommitCommandStringFromSetting());
125
126
return commandFromStorage ?? commandFromSetting ?? this.getCommitCommands()[0];
127
}
128
129
getSecondaryCommands(): Command[][] {
130
const commandGroups: Command[][] = [];
131
132
for (const provider of this.postCommitCommandsProviderRegistry.getPostCommitCommandsProviders()) {
133
const commands = provider.getCommands(new ApiRepository(this.repository));
134
commandGroups.push((commands ?? []).map(c => {
135
return { command: 'git.commit', title: c.title, tooltip: c.tooltip, arguments: [this.repository.sourceControl, c.command] };
136
}));
137
}
138
139
if (commandGroups.length > 0) {
140
commandGroups.splice(0, 0, this.getCommitCommands());
141
}
142
143
return commandGroups;
144
}
145
146
async executePostCommitCommand(command: string | null | undefined): Promise<void> {
147
try {
148
if (command === null) {
149
// No post-commit command
150
return;
151
}
152
153
if (command === undefined) {
154
// Commit WAS NOT initiated using the action button (ex: keybinding, toolbar action,
155
// command palette) so we have to honour the default post commit command (memento/setting).
156
const primaryCommand = this.getPrimaryCommand();
157
command = primaryCommand.arguments?.length === 2 ? primaryCommand.arguments[1] : null;
158
}
159
160
if (command !== null) {
161
await commands.executeCommand(command!.toString(), new ApiRepository(this.repository));
162
}
163
} catch (err) {
164
throw err;
165
}
166
finally {
167
if (!this.isRememberPostCommitCommandEnabled()) {
168
await this.globalState.update(this.getGlobalStateKey(), undefined);
169
this._onDidChange.fire();
170
}
171
}
172
}
173
174
private getGlobalStateKey(): string {
175
return `postCommitCommand:${this.repository.root}`;
176
}
177
178
private getCommitCommands(): Command[] {
179
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
180
181
// Branch protection
182
const isBranchProtected = this.repository.isBranchProtected();
183
const branchProtectionPrompt = config.get<'alwaysCommit' | 'alwaysCommitToNewBranch' | 'alwaysPrompt'>('branchProtectionPrompt')!;
184
const alwaysPrompt = isBranchProtected && branchProtectionPrompt === 'alwaysPrompt';
185
const alwaysCommitToNewBranch = isBranchProtected && branchProtectionPrompt === 'alwaysCommitToNewBranch';
186
187
// Icon
188
const icon = alwaysPrompt ? '$(lock)' : alwaysCommitToNewBranch ? '$(git-branch)' : undefined;
189
190
// Tooltip (default)
191
const branch = this.repository.HEAD?.name;
192
let tooltip = alwaysCommitToNewBranch ?
193
l10n.t('Commit Changes to New Branch') :
194
branch ?
195
l10n.t('Commit Changes on "{0}"', branch) :
196
l10n.t('Commit Changes');
197
198
// Tooltip (in progress)
199
if (this.repository.operations.isRunning(OperationKind.Commit)) {
200
tooltip = !alwaysCommitToNewBranch ?
201
l10n.t('Committing Changes...') :
202
l10n.t('Committing Changes to New Branch...');
203
}
204
205
return [
206
{ command: 'git.commit', title: l10n.t('{0} Commit', icon ?? '$(check)'), tooltip, arguments: [this.repository.sourceControl, null] },
207
{ command: 'git.commitAmend', title: l10n.t('{0} Commit (Amend)', icon ?? '$(check)'), tooltip, arguments: [this.repository.sourceControl, null] },
208
];
209
}
210
211
private getPostCommitCommandStringFromSetting(): string | undefined {
212
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
213
const postCommitCommandSetting = config.get<string>('postCommitCommand');
214
215
return postCommitCommandSetting === 'push' || postCommitCommandSetting === 'sync' ? `git.${postCommitCommandSetting}` : undefined;
216
}
217
218
private getPostCommitCommandStringFromStorage(): string | null | undefined {
219
return this.globalState.get<string | null>(this.getGlobalStateKey());
220
}
221
222
private async migratePostCommitCommandStorage(): Promise<void> {
223
const postCommitCommandString = this.globalState.get<string | null>(this.repository.root);
224
225
if (postCommitCommandString !== undefined) {
226
await this.globalState.update(this.getGlobalStateKey(), postCommitCommandString);
227
await this.globalState.update(this.repository.root, undefined);
228
}
229
}
230
231
private isRememberPostCommitCommandEnabled(): boolean {
232
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
233
return config.get<boolean>('rememberPostCommitCommand') === true;
234
}
235
236
dispose(): void {
237
this.disposables = dispose(this.disposables);
238
}
239
}
240
241