Git - handle stashes that contain untracked files (#203572)

This commit is contained in:
Ladislau Szomoru 2024-01-26 23:28:45 +01:00 committed by GitHub
parent c0de87c441
commit fe0632cbb2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 51 additions and 20 deletions

View file

@ -3709,25 +3709,44 @@ export class CommandCenter {
}
const stashChanges = await repository.showStash(stash.index);
const stashParentCommit = stash.parents.length > 0 ? stash.parents[0] : `${stash.hash}^`;
if (!stashChanges || stashChanges.length === 0) {
return;
}
// A stash commit can have up to 3 parents:
// 1. The first parent is the commit that was HEAD when the stash was created.
// 2. The second parent is the commit that represents the index when the stash was created.
// 3. The third parent (when present) represents the untracked files when the stash was created.
const stashFirstParentCommit = stash.parents.length > 0 ? stash.parents[0] : `${stash.hash}^`;
const stashUntrackedFilesParentCommit = stash.parents.length === 3 ? stash.parents[2] : undefined;
const stashUntrackedFiles: string[] = [];
if (stashUntrackedFilesParentCommit) {
const untrackedFiles = await repository.getObjectFiles(stashUntrackedFilesParentCommit);
stashUntrackedFiles.push(...untrackedFiles.map(f => path.join(repository.root, f.file)));
}
const title = `Git Stash #${stash.index}: ${stash.description}`;
const multiDiffSourceUri = toGitUri(Uri.file(repository.root), `stash@{${stash.index}}`, { scheme: 'git-stash' });
const resources: { originalUri: Uri | undefined; modifiedUri: Uri | undefined }[] = [];
for (const change of stashChanges) {
if (change.status === Status.INDEX_ADDED) {
resources.push({ originalUri: undefined, modifiedUri: toGitUri(change.uri, stash.hash) });
} else if (change.status === Status.DELETED) {
resources.push({ originalUri: toGitUri(change.uri, stashParentCommit), modifiedUri: undefined });
} else if (change.status === Status.INDEX_RENAMED) {
resources.push({ originalUri: toGitUri(change.originalUri, stashParentCommit), modifiedUri: toGitUri(change.uri, stash.hash) });
} else {
resources.push({ originalUri: toGitUri(change.uri, stashParentCommit), modifiedUri: toGitUri(change.uri, stash.hash) });
const isChangeUntracked = !!stashUntrackedFiles.find(f => pathEquals(f, change.uri.fsPath));
const modifiedUriRef = !isChangeUntracked ? stash.hash : stashUntrackedFilesParentCommit ?? stash.hash;
switch (change.status) {
case Status.INDEX_ADDED:
resources.push({ originalUri: undefined, modifiedUri: toGitUri(change.uri, modifiedUriRef) });
break;
case Status.DELETED:
resources.push({ originalUri: toGitUri(change.uri, stashFirstParentCommit), modifiedUri: undefined });
break;
case Status.INDEX_RENAMED:
resources.push({ originalUri: toGitUri(change.originalUri, stashFirstParentCommit), modifiedUri: toGitUri(change.uri, modifiedUriRef) });
break;
default:
resources.push({ originalUri: toGitUri(change.uri, stashFirstParentCommit), modifiedUri: toGitUri(change.uri, modifiedUriRef) });
break;
}
}

View file

@ -937,7 +937,7 @@ function parseGitDiffShortStat(data: string): CommitShortStat {
return { files: parseInt(files), insertions: parseInt(insertions ?? '0'), deletions: parseInt(deletions ?? '0') };
}
interface LsTreeElement {
export interface LsTreeElement {
mode: string;
type: string;
object: string;
@ -1294,8 +1294,13 @@ export class Repository {
return { mode, object, size: parseInt(size) };
}
async lstree(treeish: string, path: string): Promise<LsTreeElement[]> {
const { stdout } = await this.exec(['ls-tree', '-l', treeish, '--', sanitizePath(path)]);
async lstree(treeish: string, path?: string): Promise<LsTreeElement[]> {
const args = ['ls-tree', '-l', treeish];
if (path) {
args.push('--', sanitizePath(path));
}
const { stdout } = await this.exec(args);
return parseLsTree(stdout);
}

View file

@ -28,6 +28,7 @@ export const enum OperationKind {
GetBranches = 'GetBranches',
GetCommitTemplate = 'GetCommitTemplate',
GetObjectDetails = 'GetObjectDetails',
GetObjectFiles = 'GetObjectFiles',
GetRefs = 'GetRefs',
GetRemoteRefs = 'GetRemoteRefs',
HashObject = 'HashObject',
@ -65,12 +66,12 @@ export const enum OperationKind {
export type Operation = AddOperation | ApplyOperation | BlameOperation | BranchOperation | CheckIgnoreOperation | CherryPickOperation |
CheckoutOperation | CheckoutTrackingOperation | CleanOperation | CommitOperation | ConfigOperation | DeleteBranchOperation |
DeleteRefOperation | DeleteRemoteTagOperation | DeleteTagOperation | DiffOperation | FetchOperation | FindTrackingBranchesOperation |
GetBranchOperation | GetBranchesOperation | GetCommitTemplateOperation | GetObjectDetailsOperation | GetRefsOperation | GetRemoteRefsOperation |
HashObjectOperation | IgnoreOperation | LogOperation | LogFileOperation | MergeOperation | MergeAbortOperation | MergeBaseOperation |
MoveOperation | PostCommitCommandOperation | PullOperation | PushOperation | RemoteOperation | RenameBranchOperation | RemoveOperation |
ResetOperation | RebaseOperation | RebaseAbortOperation | RebaseContinueOperation | RefreshOperation | RevertFilesOperation | RevListOperation |
RevParseOperation | SetBranchUpstreamOperation | ShowOperation | StageOperation | StatusOperation | StashOperation | SubmoduleUpdateOperation |
SyncOperation | TagOperation;
GetBranchOperation | GetBranchesOperation | GetCommitTemplateOperation | GetObjectDetailsOperation | GetObjectFilesOperation | GetRefsOperation |
GetRemoteRefsOperation | HashObjectOperation | IgnoreOperation | LogOperation | LogFileOperation | MergeOperation | MergeAbortOperation |
MergeBaseOperation | MoveOperation | PostCommitCommandOperation | PullOperation | PushOperation | RemoteOperation | RenameBranchOperation |
RemoveOperation | ResetOperation | RebaseOperation | RebaseAbortOperation | RebaseContinueOperation | RefreshOperation | RevertFilesOperation |
RevListOperation | RevParseOperation | SetBranchUpstreamOperation | ShowOperation | StageOperation | StatusOperation | StashOperation |
SubmoduleUpdateOperation | SyncOperation | TagOperation;
type BaseOperation = { kind: OperationKind; blocking: boolean; readOnly: boolean; remote: boolean; retry: boolean; showProgress: boolean };
export type AddOperation = BaseOperation & { kind: OperationKind.Add };
@ -95,6 +96,7 @@ export type GetBranchOperation = BaseOperation & { kind: OperationKind.GetBranch
export type GetBranchesOperation = BaseOperation & { kind: OperationKind.GetBranches };
export type GetCommitTemplateOperation = BaseOperation & { kind: OperationKind.GetCommitTemplate };
export type GetObjectDetailsOperation = BaseOperation & { kind: OperationKind.GetObjectDetails };
export type GetObjectFilesOperation = BaseOperation & { kind: OperationKind.GetObjectFiles };
export type GetRefsOperation = BaseOperation & { kind: OperationKind.GetRefs };
export type GetRemoteRefsOperation = BaseOperation & { kind: OperationKind.GetRemoteRefs };
export type HashObjectOperation = BaseOperation & { kind: OperationKind.HashObject };
@ -151,6 +153,7 @@ export const Operation = {
GetBranches: { kind: OperationKind.GetBranches, blocking: false, readOnly: true, remote: false, retry: false, showProgress: true } as GetBranchesOperation,
GetCommitTemplate: { kind: OperationKind.GetCommitTemplate, blocking: false, readOnly: true, remote: false, retry: false, showProgress: true } as GetCommitTemplateOperation,
GetObjectDetails: { kind: OperationKind.GetObjectDetails, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as GetObjectDetailsOperation,
GetObjectFiles: { kind: OperationKind.GetObjectFiles, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as GetObjectFilesOperation,
GetRefs: { kind: OperationKind.GetRefs, blocking: false, readOnly: true, remote: false, retry: false, showProgress: false } as GetRefsOperation,
GetRemoteRefs: { kind: OperationKind.GetRemoteRefs, blocking: false, readOnly: true, remote: true, retry: false, showProgress: false } as GetRemoteRefsOperation,
HashObject: { kind: OperationKind.HashObject, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as HashObjectOperation,

View file

@ -11,7 +11,7 @@ import TelemetryReporter from '@vscode/extension-telemetry';
import { Branch, Change, ForcePushMode, GitErrorCodes, LogOptions, Ref, Remote, Status, CommitOptions, BranchQuery, FetchOptions, RefQuery, RefType } from './api/git';
import { AutoFetcher } from './autofetch';
import { debounce, memoize, throttle } from './decorators';
import { Commit, GitError, Repository as BaseRepository, Stash, Submodule, LogFileOptions, PullOptions } from './git';
import { Commit, GitError, Repository as BaseRepository, Stash, Submodule, LogFileOptions, PullOptions, LsTreeElement } from './git';
import { StatusBarCommands } from './statusbar';
import { toGitUri } from './uri';
import { anyEvent, combinedDisposable, debounceEvent, dispose, EmptyDisposable, eventToPromise, filterEvent, find, IDisposable, isDescendant, onceEvent, pathEquals, relativePath } from './util';
@ -1955,6 +1955,10 @@ export class Repository implements Disposable {
});
}
getObjectFiles(ref: string): Promise<LsTreeElement[]> {
return this.run(Operation.GetObjectFiles, () => this.repository.lstree(ref));
}
getObjectDetails(ref: string, filePath: string): Promise<{ mode: string; object: string; size: number }> {
return this.run(Operation.GetObjectDetails, () => this.repository.getObjectDetails(ref, filePath));
}