addons/vscode/extension/VSCodeRepo.tsblame
View source
b69ab311/**
b69ab312 * Copyright (c) Meta Platforms, Inc. and affiliates.
b69ab313 *
b69ab314 * This source code is licensed under the MIT license found in the
b69ab315 * LICENSE file in the root directory of this source tree.
b69ab316 */
b69ab317
b69ab318import type {RepositoryReference} from 'isl-server/src/RepositoryCache';
b69ab319import type {ServerSideTracker} from 'isl-server/src/analytics/serverSideTracker';
b69ab3110import type {Logger} from 'isl-server/src/logger';
b69ab3111import type {RepoRelativePath} from 'isl/src/types';
b69ab3112import {GeneratedStatus, type ChangedFile} from 'isl/src/types';
b69ab3113import type {Comparison} from 'shared/Comparison';
b69ab3114import type {Writable} from 'shared/typeUtils';
b69ab3115import type {
b69ab3116 SaplingChangedFile,
b69ab3117 SaplingCommandOutput,
b69ab3118 SaplingCommitInfo,
b69ab3119 SaplingComparison,
b69ab3120 SaplingConflictContext,
b69ab3121 SaplingCurrentCommitDiff,
b69ab3122 SaplingRepository,
b69ab3123} from './api/types';
b69ab3124import type {EnabledSCMApiFeature} from './types';
b69ab3125
b69ab3126import {generatedFilesDetector} from 'isl-server/src/GeneratedFiles';
b69ab3127import {Repository} from 'isl-server/src/Repository';
b69ab3128import {repositoryCache} from 'isl-server/src/RepositoryCache';
b69ab3129import type {TrackEventName} from 'isl-server/src/analytics/eventNames';
b69ab3130import {getMainFetchTemplate, parseCommitInfoOutput} from 'isl-server/src/templates';
b69ab3131import {ResolveOperation, ResolveTool} from 'isl/src/operations/ResolveOperation';
b69ab3132import {diffCurrentCommit} from 'isl/src/stackEdit/diffSplit';
b69ab3133import type {DiffCommit} from 'isl/src/stackEdit/diffSplitTypes';
b69ab3134import * as path from 'path';
b69ab3135import {beforeRevsetForComparison, ComparisonType} from 'shared/Comparison';
b69ab3136import {filterFilesFromPatch, parsePatch} from 'shared/patch/parse';
b69ab3137import {notEmpty} from 'shared/utils';
b69ab3138import * as vscode from 'vscode';
b69ab3139import {encodeSaplingDiffUri} from './DiffContentProvider';
5ffddc640import IgnoredFileDecorationProvider from './IgnoredFileDecorationProvider';
b69ab3141import SaplingFileDecorationProvider from './SaplingFileDecorationProvider';
b69ab3142import {executeVSCodeCommand} from './commands';
b69ab3143import {getCLICommand} from './config';
b69ab3144import {t} from './i18n';
b69ab3145
b69ab3146const mergeConflictStartRegex = new RegExp('<{7}|>{7}|[|]{7}');
b69ab3147
b69ab3148export class VSCodeReposList {
b69ab3149 private knownRepos = new Map</* attached folder root */ string, RepositoryReference>();
b69ab3150 private vscodeRepos = new Map</* repo root path */ string, VSCodeRepo>();
b69ab3151 private disposables: Array<vscode.Disposable> = [];
b69ab3152
b69ab3153 private reposByPath = new Map</* arbitrary subpath of repo */ string, VSCodeRepo>();
b69ab3154
b69ab3155 constructor(
b69ab3156 private logger: Logger,
b69ab3157 private tracker: ServerSideTracker,
b69ab3158 private enabledFeatures: Set<EnabledSCMApiFeature>,
b69ab3159 ) {
b69ab3160 if (vscode.workspace.workspaceFolders) {
b69ab3161 this.updateRepos(vscode.workspace.workspaceFolders, []);
b69ab3162 }
b69ab3163 this.disposables.push(
b69ab3164 vscode.workspace.onDidChangeWorkspaceFolders(e => {
b69ab3165 this.updateRepos(e.added, e.removed);
b69ab3166 }),
b69ab3167 );
b69ab3168 // TODO: consider also listening for vscode.workspace.onDidOpenTextDocument to support repos
b69ab3169 // for ad-hoc non-workspace-folder files
b69ab3170 }
b69ab3171
b69ab3172 private updateRepos(
b69ab3173 added: ReadonlyArray<vscode.WorkspaceFolder>,
b69ab3174 removed: ReadonlyArray<vscode.WorkspaceFolder>,
b69ab3175 ) {
b69ab3176 for (const add of added) {
b69ab3177 const {fsPath} = add.uri;
b69ab3178 if (this.knownRepos.has(fsPath)) {
b69ab3179 throw new Error(`Attempted to add workspace folder path twice: ${fsPath}`);
b69ab3180 }
b69ab3181 const repoReference = repositoryCache.getOrCreate({
b69ab3182 cwd: fsPath,
b69ab3183 cmd: getCLICommand(),
b69ab3184 logger: this.logger,
b69ab3185 tracker: this.tracker,
b69ab3186 });
b69ab3187 this.knownRepos.set(fsPath, repoReference);
b69ab3188 repoReference.promise.then(repo => {
b69ab3189 if (repo instanceof Repository) {
b69ab3190 const root = repo?.info.repoRoot;
b69ab3191 const existing = this.vscodeRepos.get(root);
b69ab3192 if (existing) {
b69ab3193 return;
b69ab3194 }
b69ab3195 const vscodeRepo = new VSCodeRepo(repo, this.logger, this.enabledFeatures);
b69ab3196 this.vscodeRepos.set(root, vscodeRepo);
b69ab3197 repo.onDidDispose(() => {
b69ab3198 vscodeRepo.dispose();
b69ab3199 this.vscodeRepos.delete(root);
b69ab31100 });
b69ab31101
b69ab31102 this.emitActiveRepos();
b69ab31103 }
b69ab31104 });
b69ab31105 }
b69ab31106 for (const remove of removed) {
b69ab31107 const {fsPath} = remove.uri;
b69ab31108 const repo = this.knownRepos.get(fsPath);
b69ab31109 repo?.unref();
b69ab31110 this.knownRepos.delete(fsPath);
b69ab31111 }
b69ab31112
b69ab31113 executeVSCodeCommand('setContext', 'sapling:hasRepo', this.knownRepos.size > 0);
b69ab31114
b69ab31115 Promise.all(Array.from(this.knownRepos.values()).map(repo => repo.promise)).then(repos => {
b69ab31116 const hasRemoteLinkRepo = repos.some(
b69ab31117 repo => repo instanceof Repository && repo.codeReviewProvider?.getRemoteFileURL,
b69ab31118 );
b69ab31119 executeVSCodeCommand('setContext', 'sapling:hasRemoteLinkRepo', hasRemoteLinkRepo);
b69ab31120 });
b69ab31121 }
b69ab31122
b69ab31123 /** return the VSCodeRepo that contains the given path */
b69ab31124 public repoForPath(path: string): VSCodeRepo | undefined {
b69ab31125 if (this.reposByPath.has(path)) {
b69ab31126 return this.reposByPath.get(path);
b69ab31127 }
b69ab31128 for (const value of this.vscodeRepos.values()) {
b69ab31129 if (path.startsWith(value.rootPath)) {
b69ab31130 return value;
b69ab31131 }
b69ab31132 }
b69ab31133 return undefined;
b69ab31134 }
b69ab31135
b69ab31136 public repoForPhabricatorCallsign(callsign: string): VSCodeRepo | undefined {
b69ab31137 for (const repo of this.vscodeRepos.values()) {
b69ab31138 const system = repo.repo.info.codeReviewSystem;
b69ab31139 if (system.type === 'phabricator' && system.callsign === callsign) {
b69ab31140 return repo;
b69ab31141 }
b69ab31142 }
b69ab31143 return undefined;
b69ab31144 }
b69ab31145
b69ab31146 private emitActiveRepos() {
b69ab31147 for (const cb of this.updateCallbacks) {
b69ab31148 cb(Array.from(this.vscodeRepos.values()));
b69ab31149 }
b69ab31150 }
b69ab31151
b69ab31152 private updateCallbacks: Array<(repos: Array<VSCodeRepo>) => void> = [];
b69ab31153 /** Subscribe to the list of active repositories */
b69ab31154 public observeActiveRepos(cb: (repos: Array<VSCodeRepo>) => void): vscode.Disposable {
b69ab31155 this.updateCallbacks.push(cb);
b69ab31156 return {
b69ab31157 dispose: () => {
b69ab31158 this.updateCallbacks = this.updateCallbacks.filter(c => c !== cb);
b69ab31159 },
b69ab31160 };
b69ab31161 }
b69ab31162
b69ab31163 public getCurrentActiveRepos(): Array<VSCodeRepo> {
b69ab31164 return Array.from(this.vscodeRepos.values());
b69ab31165 }
b69ab31166
b69ab31167 public dispose() {
b69ab31168 for (const disposable of this.disposables) {
b69ab31169 disposable.dispose();
b69ab31170 }
b69ab31171 }
b69ab31172}
b69ab31173
b69ab31174type SaplingResourceState = vscode.SourceControlResourceState & {
b69ab31175 status?: string;
b69ab31176};
b69ab31177export type SaplingResourceGroup = vscode.SourceControlResourceGroup & {
b69ab31178 resourceStates: SaplingResourceState[];
b69ab31179};
b69ab31180/**
b69ab31181 * vscode-API-compatible repository.
b69ab31182 * This handles vscode-api integrations, but defers to Repository for any actual work.
b69ab31183 */
b69ab31184export class VSCodeRepo implements vscode.QuickDiffProvider, SaplingRepository {
b69ab31185 private disposables: Array<vscode.Disposable> = [];
b69ab31186 private sourceControl?: vscode.SourceControl;
b69ab31187 private resourceGroups?: Record<
b69ab31188 'changes' | 'untracked' | 'unresolved' | 'resolved',
b69ab31189 SaplingResourceGroup
b69ab31190 >;
b69ab31191 public rootUri: vscode.Uri;
b69ab31192 public rootPath: string;
b69ab31193
b69ab31194 constructor(
b69ab31195 public repo: Repository,
b69ab31196 private logger: Logger,
b69ab31197 private enabledFeatures: Set<EnabledSCMApiFeature>,
b69ab31198 ) {
b69ab31199 repo.onDidDispose(() => this.dispose());
b69ab31200 this.rootUri = vscode.Uri.file(repo.info.repoRoot);
b69ab31201 this.rootPath = repo.info.repoRoot;
b69ab31202
b69ab31203 this.autoResolveFilesOnSave();
b69ab31204
b69ab31205 if (!this.enabledFeatures.has('sidebar')) {
b69ab31206 // if sidebar is not enabled, VSCodeRepo is mostly useless, but still used for checking which paths can be used for ISL and blame.
b69ab31207 return;
b69ab31208 }
b69ab31209
b69ab31210 this.sourceControl = vscode.scm.createSourceControl(
b69ab31211 'sapling',
b69ab31212 t('Sapling'),
b69ab31213 vscode.Uri.file(repo.info.repoRoot),
b69ab31214 );
b69ab31215 this.sourceControl.quickDiffProvider = this;
b69ab31216 this.sourceControl.inputBox.enabled = false;
b69ab31217 this.sourceControl.inputBox.visible = false;
b69ab31218 this.resourceGroups = {
b69ab31219 changes: this.sourceControl.createResourceGroup('changes', t('Uncommitted Changes')),
b69ab31220 untracked: this.sourceControl.createResourceGroup('untracked', t('Untracked Changes')),
b69ab31221 unresolved: this.sourceControl.createResourceGroup(
b69ab31222 'unresolved',
b69ab31223 t('Unresolved Merge Conflicts'),
b69ab31224 ),
b69ab31225 resolved: this.sourceControl.createResourceGroup('resolved', t('Resolved Merge Conflicts')),
b69ab31226 };
b69ab31227 for (const group of Object.values(this.resourceGroups)) {
b69ab31228 group.hideWhenEmpty = true;
b69ab31229 }
b69ab31230
b69ab31231 const fileDecorationProvider = new SaplingFileDecorationProvider(this, logger);
5ffddc6232 const ignoredFileDecorationProvider = new IgnoredFileDecorationProvider(this, logger);
b69ab31233 this.disposables.push(
b69ab31234 repo.subscribeToUncommittedChanges(() => {
b69ab31235 this.updateResourceGroups();
b69ab31236 }),
b69ab31237 repo.onChangeConflictState(() => {
b69ab31238 this.updateResourceGroups();
b69ab31239 }),
b69ab31240 fileDecorationProvider,
5ffddc6241 ignoredFileDecorationProvider,
b69ab31242 );
b69ab31243 this.updateResourceGroups();
b69ab31244 }
b69ab31245
b69ab31246 /** If this uri is for file inside the repo or not */
b69ab31247 public containsUri(uri: vscode.Uri): boolean {
b69ab31248 return (
b69ab31249 uri.scheme === this.rootUri.scheme &&
b69ab31250 uri.authority === this.rootUri.authority &&
b69ab31251 uri.fsPath.startsWith(this.rootPath)
b69ab31252 );
b69ab31253 }
b69ab31254
b69ab31255 /** If this uri is for a file inside the repo, return the repo-relative path. Otherwise, return undefined. */
b69ab31256 public repoRelativeFsPath(uri: vscode.Uri): string | undefined {
b69ab31257 return this.containsUri(uri) ? path.relative(this.rootPath, uri.fsPath) : undefined;
b69ab31258 }
b69ab31259
b69ab31260 private autoResolveFilesOnSave(): vscode.Disposable {
b69ab31261 return vscode.workspace.onDidSaveTextDocument(document => {
b69ab31262 const repoRelativePath = this.repoRelativeFsPath(document.uri);
b69ab31263 const conflicts = this.repo.getMergeConflicts();
b69ab31264 if (conflicts == null || repoRelativePath == null) {
b69ab31265 return;
b69ab31266 }
b69ab31267 const filesWithConflicts = conflicts.files?.map(file => file.path);
b69ab31268 if (filesWithConflicts?.includes(repoRelativePath) !== true) {
b69ab31269 return;
b69ab31270 }
b69ab31271 const autoResolveEnabled = vscode.workspace
b69ab31272 .getConfiguration('sapling')
b69ab31273 .get<boolean>('markConflictingFilesResolvedOnSave');
b69ab31274 if (!autoResolveEnabled) {
b69ab31275 return;
b69ab31276 }
b69ab31277 const allConflictsThisFileResolved = !mergeConflictStartRegex.test(document.getText());
b69ab31278 if (!allConflictsThisFileResolved) {
b69ab31279 return;
b69ab31280 }
b69ab31281 this.logger.info(
b69ab31282 'auto marking file with no remaining conflicts as resolved:',
b69ab31283 repoRelativePath,
b69ab31284 );
b69ab31285
b69ab31286 this.repo.runOrQueueOperation(
b69ab31287 this.repo.initialConnectionContext,
b69ab31288 {
b69ab31289 ...new ResolveOperation(repoRelativePath, ResolveTool.mark).getRunnableOperation(),
b69ab31290 // Distinguish in analytics from manually resolving
b69ab31291 trackEventName: 'AutoMarkResolvedOperation',
b69ab31292 },
b69ab31293 () => null,
b69ab31294 );
b69ab31295 });
b69ab31296 }
b69ab31297
b69ab31298 private updateResourceGroups() {
b69ab31299 if (this.resourceGroups == null || this.sourceControl == null) {
b69ab31300 return;
b69ab31301 }
b69ab31302 const data = this.repo.getUncommittedChanges();
b69ab31303 const conflicts = this.repo.getMergeConflicts()?.files;
b69ab31304
b69ab31305 // only show merge conflicts if they are given
b69ab31306 const fileChanges = conflicts ?? data?.files?.value ?? [];
b69ab31307
b69ab31308 const changes: Array<SaplingResourceState> = [];
b69ab31309 const untracked: Array<SaplingResourceState> = [];
b69ab31310 const unresolved: Array<SaplingResourceState> = [];
b69ab31311 const resolved: Array<SaplingResourceState> = [];
b69ab31312
b69ab31313 for (const change of fileChanges) {
b69ab31314 const uri = vscode.Uri.joinPath(this.rootUri, change.path);
b69ab31315 const resource: SaplingResourceState = {
b69ab31316 command: {
b69ab31317 command: 'vscode.open',
b69ab31318 title: 'Open',
b69ab31319 arguments: [uri],
b69ab31320 },
b69ab31321 resourceUri: uri,
b69ab31322 decorations: this.decorationForChange(change),
b69ab31323 status: change.status,
b69ab31324 };
b69ab31325 switch (change.status) {
b69ab31326 case '?':
b69ab31327 case '!':
b69ab31328 untracked.push(resource);
b69ab31329 break;
b69ab31330 case 'U':
b69ab31331 unresolved.push(resource);
b69ab31332 break;
b69ab31333 case 'Resolved':
b69ab31334 resolved.push(resource);
b69ab31335 break;
b69ab31336 default:
b69ab31337 changes.push(resource);
b69ab31338 break;
b69ab31339 }
b69ab31340 }
b69ab31341 this.resourceGroups.changes.resourceStates = changes;
b69ab31342 this.resourceGroups.untracked.resourceStates = untracked;
b69ab31343 this.resourceGroups.unresolved.resourceStates = unresolved;
b69ab31344 this.resourceGroups.resolved.resourceStates = resolved;
b69ab31345
b69ab31346 // don't include resolved files in count
b69ab31347 this.sourceControl.count = changes.length + untracked.length + unresolved.length;
b69ab31348 }
b69ab31349
b69ab31350 public getResourceGroups() {
b69ab31351 return this.resourceGroups;
b69ab31352 }
b69ab31353
b69ab31354 public dispose() {
b69ab31355 this.disposables.forEach(d => d?.dispose());
b69ab31356 }
b69ab31357
b69ab31358 private decorationForChange(change: ChangedFile): vscode.SourceControlResourceDecorations {
b69ab31359 const decoration: Writable<vscode.SourceControlResourceDecorations> = {};
b69ab31360 switch (change.status) {
b69ab31361 case 'M':
b69ab31362 decoration.iconPath = new vscode.ThemeIcon('diff-modified', themeColors.modified);
b69ab31363 break;
b69ab31364 case 'A':
b69ab31365 decoration.iconPath = new vscode.ThemeIcon('diff-added', themeColors.added);
b69ab31366 break;
b69ab31367 case 'R':
b69ab31368 decoration.iconPath = new vscode.ThemeIcon('diff-removed', themeColors.deleted);
b69ab31369 break;
b69ab31370 case '?':
b69ab31371 decoration.faded = true;
b69ab31372 decoration.iconPath = new vscode.ThemeIcon('question', themeColors.untracked);
b69ab31373 break;
b69ab31374 case '!':
b69ab31375 decoration.faded = true;
b69ab31376 decoration.iconPath = new vscode.ThemeIcon('warning', themeColors.untracked);
b69ab31377 break;
b69ab31378 case 'U':
b69ab31379 decoration.iconPath = new vscode.ThemeIcon('diff-ignored', themeColors.conflicting);
b69ab31380 break;
b69ab31381 case 'Resolved':
b69ab31382 decoration.faded = true;
b69ab31383 decoration.iconPath = new vscode.ThemeIcon('pass', themeColors.added);
b69ab31384 break;
b69ab31385 default:
b69ab31386 break;
b69ab31387 }
b69ab31388 return decoration;
b69ab31389 }
b69ab31390
b69ab31391 /**
b69ab31392 * Use ContentProvider + encodeSaplingDiffUri
b69ab31393 */
b69ab31394 provideOriginalResource(uri: vscode.Uri): vscode.Uri | undefined {
b69ab31395 if (uri.scheme !== 'file') {
b69ab31396 return;
b69ab31397 }
b69ab31398 // TODO: make this configurable via vscode setting to allow
b69ab31399 // diff gutters to be either uncommitted changes / head changes / stack changes
b69ab31400 const comparison = {type: ComparisonType.UncommittedChanges} as Comparison;
b69ab31401
b69ab31402 return encodeSaplingDiffUri(uri, beforeRevsetForComparison(comparison));
b69ab31403 }
b69ab31404
b69ab31405 ////////////////////////////////////////////////////////////////////////////////////
b69ab31406
b69ab31407 get info() {
b69ab31408 return this.repo.info;
b69ab31409 }
b69ab31410
b69ab31411 getDotCommit(): SaplingCommitInfo | undefined {
b69ab31412 return this.repo.getHeadCommit();
b69ab31413 }
b69ab31414 onChangeDotCommit(callback: (commit: SaplingCommitInfo | undefined) => void): vscode.Disposable {
b69ab31415 return this.repo.subscribeToHeadCommit(callback);
b69ab31416 }
b69ab31417 getUncommittedChanges(): ReadonlyArray<SaplingChangedFile> {
b69ab31418 return this.repo.getUncommittedChanges()?.files?.value ?? [];
b69ab31419 }
b69ab31420 onChangeUncommittedChanges(
b69ab31421 callback: (changes: ReadonlyArray<SaplingChangedFile>) => void,
b69ab31422 ): vscode.Disposable {
b69ab31423 return this.repo.subscribeToUncommittedChanges(result => {
b69ab31424 callback(result.files?.value ?? []);
b69ab31425 });
b69ab31426 }
b69ab31427
b69ab31428 runSlCommand(
b69ab31429 args: Array<string>,
b69ab31430 eventName: TrackEventName | undefined = undefined,
b69ab31431 ): Promise<SaplingCommandOutput> {
b69ab31432 return this.repo.runCommand(args, eventName, this.repo.initialConnectionContext);
b69ab31433 }
b69ab31434
b69ab31435 async getCurrentStack(): Promise<ReadonlyArray<SaplingCommitInfo>> {
b69ab31436 const revset = 'sort(draft() and ancestors(.), topo)';
b69ab31437 const result = await this.runSlCommand(
b69ab31438 ['log', '--rev', revset, '--template', getMainFetchTemplate(this.info.codeReviewSystem)],
b69ab31439 'GetCurrentStack',
b69ab31440 );
b69ab31441 if (result.exitCode === 0) {
b69ab31442 return parseCommitInfoOutput(this.logger, result.stdout, this.repo.info.codeReviewSystem);
b69ab31443 } else {
b69ab31444 throw new Error(result.stderr);
b69ab31445 }
b69ab31446 }
b69ab31447
b69ab31448 async getFullFocusedBranch(): Promise<ReadonlyArray<SaplingCommitInfo>> {
b69ab31449 const revset = 'sort(focusedbranch(.), topo)';
b69ab31450 const result = await this.runSlCommand(
b69ab31451 ['log', '--rev', revset, '--template', getMainFetchTemplate(this.info.codeReviewSystem)],
b69ab31452 'GetFullFocusedBranch',
b69ab31453 );
b69ab31454 if (result.exitCode === 0) {
b69ab31455 return parseCommitInfoOutput(this.logger, result.stdout, this.repo.info.codeReviewSystem);
b69ab31456 } else {
b69ab31457 throw new Error(result.stderr);
b69ab31458 }
b69ab31459 }
b69ab31460
b69ab31461 /** @deprecated - prefer `diff({type: 'Commit', hash: commit || '.'})` */
b69ab31462 async getDiff(commit?: string): Promise<string> {
b69ab31463 const result = await this.runSlCommand(['diff', '-c', commit || '.']);
b69ab31464
b69ab31465 if (result.exitCode === 0) {
b69ab31466 return result.stdout;
b69ab31467 } else {
b69ab31468 throw new Error(result.stderr);
b69ab31469 }
b69ab31470 }
b69ab31471
b69ab31472 async diff(
b69ab31473 comparison: Comparison | SaplingComparison,
b69ab31474 options?: {excludeGenerated?: boolean},
b69ab31475 ): Promise<string> {
b69ab31476 const output = await this.repo.runDiff(
b69ab31477 this.repo.initialConnectionContext,
b69ab31478 comparison as Comparison,
b69ab31479 undefined,
b69ab31480 );
b69ab31481
b69ab31482 if (options?.excludeGenerated === true) {
b69ab31483 const filenames = parsePatch(output)
b69ab31484 .map(diff => diff.newFileName ?? diff.oldFileName)
b69ab31485 .filter(notEmpty);
b69ab31486 const generatedFiles = await this.getGeneratedPaths(filenames);
b69ab31487 if (generatedFiles) {
b69ab31488 return filterFilesFromPatch(output, generatedFiles);
b69ab31489 }
b69ab31490 }
b69ab31491 return output;
b69ab31492 }
b69ab31493
b69ab31494 async getGeneratedPaths(paths: Array<RepoRelativePath>): Promise<Array<RepoRelativePath>> {
b69ab31495 const generatedMap = await generatedFilesDetector.queryFilesGenerated(
b69ab31496 this.repo,
b69ab31497 this.repo.initialConnectionContext,
b69ab31498 this.repo.info.repoRoot,
b69ab31499 paths,
b69ab31500 );
b69ab31501 return paths.filter(
b69ab31502 path =>
b69ab31503 generatedMap[path] === GeneratedStatus.Generated ||
b69ab31504 generatedMap[path] === GeneratedStatus.PartiallyGenerated,
b69ab31505 );
b69ab31506 }
b69ab31507
b69ab31508 async commit(title: string, commitMessage: string): Promise<void> {
b69ab31509 const message = `${title}\n\n${commitMessage}`;
b69ab31510 const result = await this.runSlCommand(['commit', '-m', message]);
b69ab31511
b69ab31512 if (result.exitCode !== 0) {
b69ab31513 throw new Error(result.stderr);
b69ab31514 }
b69ab31515 }
b69ab31516
b69ab31517 async getMergeConflictContext(): Promise<SaplingConflictContext[]> {
b69ab31518 const result = await this.runSlCommand(['debugconflictcontext']);
b69ab31519 if (result.exitCode !== 0) {
b69ab31520 throw new Error(result.stderr);
b69ab31521 }
b69ab31522
b69ab31523 return JSON.parse(result.stdout) as SaplingConflictContext[];
b69ab31524 }
b69ab31525
b69ab31526 async getCurrentCommitDiff(): Promise<SaplingCurrentCommitDiff> {
b69ab31527 const diff = (await diffCurrentCommit(
b69ab31528 this.repo,
b69ab31529 this.repo.initialConnectionContext,
b69ab31530 )) as DiffCommit;
b69ab31531
b69ab31532 return {message: diff.message, files: diff.files};
b69ab31533 }
b69ab31534}
b69ab31535
b69ab31536const themeColors = {
b69ab31537 deleted: new vscode.ThemeColor('gitDecoration.deletedResourceForeground'),
b69ab31538 modified: new vscode.ThemeColor('gitDecoration.modifiedResourceForeground'),
b69ab31539 added: new vscode.ThemeColor('gitDecoration.addedResourceForeground'),
b69ab31540 untracked: new vscode.ThemeColor('gitDecoration.untrackedResourceForeground'),
b69ab31541 conflicting: new vscode.ThemeColor('gitDecoration.conflictingResourceForeground'),
b69ab31542};