in patched-vscode/extensions/git/src/commands.ts [2051:2240]
private async smartCommit(
repository: Repository,
getCommitMessage: () => Promise<string | undefined>,
opts: CommitOptions
): Promise<void> {
const config = workspace.getConfiguration('git', Uri.file(repository.root));
let promptToSaveFilesBeforeCommit = config.get<'always' | 'staged' | 'never'>('promptToSaveFilesBeforeCommit');
// migration
if (promptToSaveFilesBeforeCommit as any === true) {
promptToSaveFilesBeforeCommit = 'always';
} else if (promptToSaveFilesBeforeCommit as any === false) {
promptToSaveFilesBeforeCommit = 'never';
}
const enableSmartCommit = config.get<boolean>('enableSmartCommit') === true;
const enableCommitSigning = config.get<boolean>('enableCommitSigning') === true;
let noStagedChanges = repository.indexGroup.resourceStates.length === 0;
let noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0;
if (promptToSaveFilesBeforeCommit !== 'never') {
let documents = workspace.textDocuments
.filter(d => !d.isUntitled && d.isDirty && isDescendant(repository.root, d.uri.fsPath));
if (promptToSaveFilesBeforeCommit === 'staged' || repository.indexGroup.resourceStates.length > 0) {
documents = documents
.filter(d => repository.indexGroup.resourceStates.some(s => pathEquals(s.resourceUri.fsPath, d.uri.fsPath)));
}
if (documents.length > 0) {
const message = documents.length === 1
? l10n.t('The following file has unsaved changes which won\'t be included in the commit if you proceed: {0}.\n\nWould you like to save it before committing?', path.basename(documents[0].uri.fsPath))
: l10n.t('There are {0} unsaved files.\n\nWould you like to save them before committing?', documents.length);
const saveAndCommit = l10n.t('Save All & Commit Changes');
const commit = l10n.t('Commit Changes');
const pick = await window.showWarningMessage(message, { modal: true }, saveAndCommit, commit);
if (pick === saveAndCommit) {
await Promise.all(documents.map(d => d.save()));
// After saving the dirty documents, if there are any documents that are part of the
// index group we have to add them back in order for the saved changes to be committed
documents = documents
.filter(d => repository.indexGroup.resourceStates.some(s => pathEquals(s.resourceUri.fsPath, d.uri.fsPath)));
await repository.add(documents.map(d => d.uri));
noStagedChanges = repository.indexGroup.resourceStates.length === 0;
noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0;
} else if (pick !== commit) {
return; // do not commit on cancel
}
}
}
// no changes, and the user has not configured to commit all in this case
if (!noUnstagedChanges && noStagedChanges && !enableSmartCommit && !opts.empty && !opts.all) {
const suggestSmartCommit = config.get<boolean>('suggestSmartCommit') === true;
if (!suggestSmartCommit) {
return;
}
// prompt the user if we want to commit all or not
const message = l10n.t('There are no staged changes to commit.\n\nWould you like to stage all your changes and commit them directly?');
const yes = l10n.t('Yes');
const always = l10n.t('Always');
const never = l10n.t('Never');
const pick = await window.showWarningMessage(message, { modal: true }, yes, always, never);
if (pick === always) {
config.update('enableSmartCommit', true, true);
} else if (pick === never) {
config.update('suggestSmartCommit', false, true);
return;
} else if (pick !== yes) {
return; // do not commit on cancel
}
}
if (opts.all === undefined) {
opts = { ...opts, all: noStagedChanges };
} else if (!opts.all && noStagedChanges && !opts.empty) {
opts = { ...opts, all: true };
}
// enable signing of commits if configured
opts.signCommit = enableCommitSigning;
if (config.get<boolean>('alwaysSignOff')) {
opts.signoff = true;
}
if (config.get<boolean>('useEditorAsCommitInput')) {
opts.useEditor = true;
if (config.get<boolean>('verboseCommit')) {
opts.verbose = true;
}
}
const smartCommitChanges = config.get<'all' | 'tracked'>('smartCommitChanges');
if (
(
// no changes
(noStagedChanges && noUnstagedChanges)
// or no staged changes and not `all`
|| (!opts.all && noStagedChanges)
// no staged changes and no tracked unstaged changes
|| (noStagedChanges && smartCommitChanges === 'tracked' && repository.workingTreeGroup.resourceStates.every(r => r.type === Status.UNTRACKED))
)
// amend allows changing only the commit message
&& !opts.amend
&& !opts.empty
// rebase not in progress
&& repository.rebaseCommit === undefined
) {
const commitAnyway = l10n.t('Create Empty Commit');
const answer = await window.showInformationMessage(l10n.t('There are no changes to commit.'), commitAnyway);
if (answer !== commitAnyway) {
return;
}
opts.empty = true;
}
if (opts.noVerify) {
if (!config.get<boolean>('allowNoVerifyCommit')) {
await window.showErrorMessage(l10n.t('Commits without verification are not allowed, please enable them with the "git.allowNoVerifyCommit" setting.'));
return;
}
if (config.get<boolean>('confirmNoVerifyCommit')) {
const message = l10n.t('You are about to commit your changes without verification, this skips pre-commit hooks and can be undesirable.\n\nAre you sure to continue?');
const yes = l10n.t('OK');
const neverAgain = l10n.t('OK, Don\'t Ask Again');
const pick = await window.showWarningMessage(message, { modal: true }, yes, neverAgain);
if (pick === neverAgain) {
config.update('confirmNoVerifyCommit', false, true);
} else if (pick !== yes) {
return;
}
}
}
const message = await getCommitMessage();
if (!message && !opts.amend && !opts.useEditor) {
return;
}
if (opts.all && smartCommitChanges === 'tracked') {
opts.all = 'tracked';
}
if (opts.all && config.get<'mixed' | 'separate' | 'hidden'>('untrackedChanges') !== 'mixed') {
opts.all = 'tracked';
}
// Branch protection
const branchProtectionPrompt = config.get<'alwaysCommit' | 'alwaysCommitToNewBranch' | 'alwaysPrompt'>('branchProtectionPrompt')!;
if (repository.isBranchProtected() && (branchProtectionPrompt === 'alwaysPrompt' || branchProtectionPrompt === 'alwaysCommitToNewBranch')) {
const commitToNewBranch = l10n.t('Commit to a New Branch');
let pick: string | undefined = commitToNewBranch;
if (branchProtectionPrompt === 'alwaysPrompt') {
const message = l10n.t('You are trying to commit to a protected branch and you might not have permission to push your commits to the remote.\n\nHow would you like to proceed?');
const commit = l10n.t('Commit Anyway');
pick = await window.showWarningMessage(message, { modal: true }, commitToNewBranch, commit);
}
if (!pick) {
return;
} else if (pick === commitToNewBranch) {
const branchName = await this.promptForBranchName(repository);
if (!branchName) {
return;
}
await repository.branch(branchName, true);
}
}
await repository.commit(message, opts);
}