Skip to content

Commit

Permalink
Initial implementation (#240264)
Browse files Browse the repository at this point in the history
  • Loading branch information
lszomoru authored Feb 10, 2025
1 parent 732d06f commit 22c92fc
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 4 deletions.
27 changes: 27 additions & 0 deletions extensions/git/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3297,6 +3297,33 @@
"maximum": 40,
"markdownDescription": "%config.commitShortHashLength%",
"scope": "resource"
},
"git.diagnosticsCommitHook.Severity": {
"type": "string",
"enum": [
"error",
"warning",
"information",
"hint"
],
"enumDescriptions": [
"%config.diagnosticsCommitHook.Severity.error%",
"%config.diagnosticsCommitHook.Severity.warning%",
"%config.diagnosticsCommitHook.Severity.information%",
"%config.diagnosticsCommitHook.Severity.hint%"
],
"default": "error",
"markdownDescription": "%config.diagnosticsCommitHook.Severity%",
"scope": "resource"
},
"git.diagnosticsCommitHook.Source": {
"type": "array",
"items": {
"type": "string"
},
"default": [],
"markdownDescription": "%config.diagnosticsCommitHook.Source%",
"scope": "resource"
}
}
},
Expand Down
6 changes: 6 additions & 0 deletions extensions/git/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,12 @@
"config.blameStatusBarItem.enabled": "Controls whether to show blame information in the status bar.",
"config.blameStatusBarItem.template": "Template for the blame information status bar item. Supported variables:\n\n* `hash`: Commit hash\n\n* `hashShort`: First N characters of the commit hash according to `#git.commitShortHashLength#`\n\n* `subject`: First line of the commit message\n\n* `authorName`: Author name\n\n* `authorEmail`: Author email\n\n* `authorDate`: Author date\n\n* `authorDateAgo`: Time difference between now and the author date\n\n",
"config.commitShortHashLength": "Controls the length of the commit short hash.",
"config.diagnosticsCommitHook.Severity": "Controls the minimum diagnostics severity for which Git should check before committing.",
"config.diagnosticsCommitHook.Severity.error": "Errors only",
"config.diagnosticsCommitHook.Severity.warning": "Errors and warnings",
"config.diagnosticsCommitHook.Severity.information": "Errors, warnings, and information",
"config.diagnosticsCommitHook.Severity.hint": "Errors, warnings, information, and hints",
"config.diagnosticsCommitHook.Source": "Controls the list of diagnostics sources for which Git should check before committing.",
"submenu.explorer": "Git",
"submenu.commit": "Commit",
"submenu.commit.amend": "Amend",
Expand Down
72 changes: 69 additions & 3 deletions extensions/git/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import * as os from 'os';
import * as path from 'path';
import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl, InputBoxValidationMessage, Tab, TabInputNotebook, QuickInputButtonLocation } from 'vscode';
import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge, QuickPickItemKind, TextDocument, LogOutputChannel, l10n, Memento, UIKind, QuickInputButton, ThemeIcon, SourceControlHistoryItem, SourceControl, InputBoxValidationMessage, Tab, TabInputNotebook, QuickInputButtonLocation, languages } from 'vscode';
import TelemetryReporter from '@vscode/extension-telemetry';
import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator';
import { ForcePushMode, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourcePublisher, Remote } from './api/git';
Expand All @@ -14,7 +14,7 @@ import { Model } from './model';
import { GitResourceGroup, Repository, Resource, ResourceGroupType } from './repository';
import { DiffEditorSelectionHunkToolbarContext, applyLineChanges, getIndexDiffInformation, getModifiedRange, getWorkingTreeDiffInformation, intersectDiffWithRange, invertLineChange, toLineChanges, toLineRanges } from './staging';
import { fromGitUri, toGitUri, isGitUri, toMergeUris, toMultiFileDiffEditorUris } from './uri';
import { dispose, getCommitShortHash, grep, isDefined, isDescendant, pathEquals, relativePath, truncate } from './util';
import { DiagnosticSeverityConfig, dispose, getCommitShortHash, grep, isDefined, isDescendant, pathEquals, relativePath, toDiagnosticSeverity, truncate } from './util';
import { GitTimelineItem } from './timelineProvider';
import { ApiRepository } from './api/api1';
import { getRemoteSourceActions, pickRemoteSource } from './remoteSource';
Expand Down Expand Up @@ -617,6 +617,66 @@ class CommandErrorOutputTextDocumentContentProvider implements TextDocumentConte
}
}

async function evaluateDiagnosticsCommitHook(repository: Repository, options: CommitOptions): Promise<boolean> {
const config = workspace.getConfiguration('git', Uri.file(repository.root));
const diagnosticSource = config.get<string[]>('diagnosticsCommitHook.Source', []);
const diagnosticSeveritySetting = config.get<DiagnosticSeverityConfig>('diagnosticsCommitHook.Severity', 'error');
const diagnosticSeverity = toDiagnosticSeverity(diagnosticSeveritySetting);

if (diagnosticSource.length === 0) {
return true;
}

const changes: Uri[] = [];
if (repository.indexGroup.resourceStates.length > 0) {
// Staged files
changes.push(...repository.indexGroup.resourceStates.map(r => r.resourceUri));
} else if (options.all === 'tracked') {
// Tracked files
changes.push(...repository.workingTreeGroup.resourceStates
.filter(r => r.type !== Status.UNTRACKED && r.type !== Status.IGNORED)
.map(r => r.resourceUri));
} else {
// All files
changes.push(...repository.workingTreeGroup.resourceStates.map(r => r.resourceUri));
changes.push(...repository.untrackedGroup.resourceStates.map(r => r.resourceUri));
}

const diagnostics = languages.getDiagnostics();
const changesDiagnostics = diagnostics.filter(([uri, diags]) =>
// File
changes.some(u => uri.scheme === 'file' && pathEquals(u.fsPath, uri.fsPath)) &&
// Severity
diags.some(d => d.source && diagnosticSource.includes(d.source) && d.severity <= diagnosticSeverity)
);

if (changesDiagnostics.length === 0) {
return true;
}

// Show dialog
const commit = l10n.t('Commit Anyway');
const view = l10n.t('View Problems');

const message = changesDiagnostics.length === 1
? l10n.t('The following file has unresolved diagnostic information: {0}.\n\nHow would you like to proceed?', path.basename(changesDiagnostics[0][0].fsPath))
: l10n.t('There are {0} files that have unresolved diagnostic information.\n\nHow would you like to proceed?', changesDiagnostics.length);

const choice = await window.showWarningMessage(message, { modal: true }, commit, view);

// Commit Anyway
if (choice === commit) {
return true;
}

// View Problems
if (choice === view) {
commands.executeCommand('workbench.panel.markers.view.focus');
}

return false;
}

export class CommandCenter {

private disposables: Disposable[];
Expand Down Expand Up @@ -2273,7 +2333,13 @@ export class CommandCenter {
opts.all = 'tracked';
}

// Branch protection
// Diagnostics commit hook
const diagnosticsResult = await evaluateDiagnosticsCommitHook(repository, opts);
if (!diagnosticsResult) {
return;
}

// Branch protection commit hook
const branchProtectionPrompt = config.get<'alwaysCommit' | 'alwaysCommitToNewBranch' | 'alwaysPrompt'>('branchProtectionPrompt')!;
if (repository.isBranchProtected() && (branchProtectionPrompt === 'alwaysPrompt' || branchProtectionPrompt === 'alwaysCommitToNewBranch')) {
const commitToNewBranch = l10n.t('Commit to a New Branch');
Expand Down
14 changes: 13 additions & 1 deletion extensions/git/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Event, Disposable, EventEmitter, SourceControlHistoryItemRef, l10n, workspace, Uri } from 'vscode';
import { Event, Disposable, EventEmitter, SourceControlHistoryItemRef, l10n, workspace, Uri, DiagnosticSeverity } from 'vscode';
import { dirname, sep, relative } from 'path';
import { Readable } from 'stream';
import { promises as fs, createReadStream } from 'fs';
Expand Down Expand Up @@ -772,3 +772,15 @@ export function getCommitShortHash(scope: Uri, hash: string): string {
const shortHashLength = config.get<number>('commitShortHashLength', 7);
return hash.substring(0, shortHashLength);
}

export type DiagnosticSeverityConfig = 'error' | 'warning' | 'information' | 'hint';

export function toDiagnosticSeverity(value: DiagnosticSeverityConfig): DiagnosticSeverity {
return value === 'error'
? DiagnosticSeverity.Error
: value === 'warning'
? DiagnosticSeverity.Warning
: value === 'information'
? DiagnosticSeverity.Information
: DiagnosticSeverity.Hint;
}

0 comments on commit 22c92fc

Please sign in to comment.