diff --git a/src/env/node/git/git.ts b/src/env/node/git/git.ts index 7e7ba2f..5a49ef2 100644 --- a/src/env/node/git/git.ts +++ b/src/env/node/git/git.ts @@ -16,6 +16,7 @@ import { GitBranchParser } from '../../../git/parsers/branchParser'; import { GitLogParser } from '../../../git/parsers/logParser'; import { GitReflogParser } from '../../../git/parsers/reflogParser'; import { GitTagParser } from '../../../git/parsers/tagParser'; +import { splitAt } from '../../../system/array'; import { join } from '../../../system/iterable'; import { Logger } from '../../../system/logger'; import { LogLevel, slowCallWarningThreshold } from '../../../system/logger.constants'; @@ -1258,6 +1259,7 @@ export class Git { ordering?: 'date' | 'author-date' | 'topo' | null; skip?: number; shas?: Set; + stdin?: string; }, ) { if (options?.shas != null) { @@ -1272,18 +1274,21 @@ export class Git { ); } + let files; + [search, files] = splitAt(search, search.indexOf('--')); + return this.git( - { cwd: repoPath, configs: ['-C', repoPath, ...gitLogDefaultConfigs] }, + { cwd: repoPath, configs: ['-C', repoPath, ...gitLogDefaultConfigs], stdin: options?.stdin }, 'log', + ...(options?.stdin ? ['--stdin'] : emptyArray), '--name-status', `--format=${GitLogParser.defaultFormat}`, '--use-mailmap', - '--full-history', - '-m', + ...search, + ...(options?.ordering ? [`--${options.ordering}-order`] : emptyArray), ...(options?.limit ? [`-n${options.limit + 1}`] : emptyArray), ...(options?.skip ? [`--skip=${options.skip}`] : emptyArray), - ...(options?.ordering ? [`--${options.ordering}-order`] : emptyArray), - ...search, + ...files, ); } diff --git a/src/env/node/git/localGitProvider.ts b/src/env/node/git/localGitProvider.ts index 4760a21..e7beeaa 100644 --- a/src/env/node/git/localGitProvider.ts +++ b/src/env/node/git/localGitProvider.ts @@ -2796,6 +2796,7 @@ export class LocalGitProvider implements GitProvider, Disposable { limit, false, undefined, + undefined, hasMoreOverride, ); @@ -4413,11 +4414,33 @@ export class LocalGitProvider implements GitProvider, Disposable { args.push(...files); } + let stashes: Map | undefined; + let stdin: string | undefined; + if (shas == null) { + const stash = await this.getStash(repoPath); + // TODO@eamodio this is insanity -- there *HAS* to be a better way to get git log to return stashes + if (stash?.commits.size) { + stashes = new Map(); + for (const commit of stash.commits.values()) { + stashes.set(commit.sha, commit); + for (const p of commit.parents) { + stashes.set(p, commit); + } + } + + stdin = join( + map(stash.commits.values(), c => c.sha.substring(0, 9)), + '\n', + ); + } + } + const data = await this.git.log__search(repoPath, shas?.size ? undefined : args, { ordering: configuration.get('advanced.commitOrdering'), ...options, limit: limit, shas: shas, + stdin: stdin, }); const log = GitLogParser.parse( this.container, @@ -4430,6 +4453,7 @@ export class LocalGitProvider implements GitProvider, Disposable { limit, false, undefined, + stashes, ); if (log != null) { @@ -4537,7 +4561,7 @@ export class LocalGitProvider implements GitProvider, Disposable { const stash = await this.getStash(repoPath); let stdin: string | undefined; // TODO@eamodio this is insanity -- there *HAS* to be a better way to get git log to return stashes - if (stash != null && stash.commits.size !== 0) { + if (stash?.commits.size) { stdin = join( map(stash.commits.values(), c => c.sha.substring(0, 9)), '\n', diff --git a/src/git/parsers/logParser.ts b/src/git/parsers/logParser.ts index edebb5a..f55c526 100644 --- a/src/git/parsers/logParser.ts +++ b/src/git/parsers/logParser.ts @@ -4,7 +4,7 @@ import { filterMap } from '../../system/array'; import { debug } from '../../system/decorators/log'; import { normalizePath, relative } from '../../system/path'; import { getLines } from '../../system/string'; -import type { GitCommitLine } from '../models/commit'; +import type { GitCommitLine, GitStashCommit } from '../models/commit'; import { GitCommit, GitCommitIdentity } from '../models/commit'; import { uncommitted } from '../models/constants'; import type { GitFile, GitFileChangeStats } from '../models/file'; @@ -431,6 +431,7 @@ export class GitLogParser { limit: number | undefined, reverse: boolean, range: Range | undefined, + stashes?: Map, hasMoreOverride?: boolean, ): GitLog | undefined { if (!data) return undefined; @@ -718,6 +719,7 @@ export class GitLogParser { relativeFileName, commits, currentUser, + stashes, ); break; @@ -746,6 +748,7 @@ export class GitLogParser { relativeFileName: string | undefined, commits: Map, currentUser: GitUser | undefined, + stashes: Map | undefined, ): void { if (commit == null) { if (entry.author != null) { @@ -776,24 +779,50 @@ export class GitLogParser { ); } - commit = new GitCommit( - container, - repoPath!, - entry.sha!, - new GitCommitIdentity(entry.author!, entry.authorEmail, new Date((entry.authorDate! as any) * 1000)), - new GitCommitIdentity( - entry.committer!, - entry.committerEmail, - new Date((entry.committedDate! as any) * 1000), - ), - entry.summary?.split('\n', 1)[0] ?? '', - entry.parentShas ?? [], - entry.summary ?? '', - files, - undefined, - entry.line != null ? [entry.line] : [], - entry.tips, - ); + const stash = stashes?.get(entry.sha!); + if (stash != null) { + commit = new GitCommit( + container, + repoPath!, + stash.sha, + stash.author, + stash.committer, + stash.summary, + stash.parents, + stash.message, + files, + undefined, + entry.line != null ? [entry.line] : [], + entry.tips, + stash.stashName, + stash.stashOnRef, + ); + } else { + commit = new GitCommit( + container, + repoPath!, + entry.sha!, + + new GitCommitIdentity( + entry.author!, + entry.authorEmail, + new Date((entry.authorDate! as any) * 1000), + ), + + new GitCommitIdentity( + entry.committer!, + entry.committerEmail, + new Date((entry.committedDate! as any) * 1000), + ), + entry.summary?.split('\n', 1)[0] ?? '', + entry.parentShas ?? [], + entry.summary ?? '', + files, + undefined, + entry.line != null ? [entry.line] : [], + entry.tips, + ); + } commits.set(entry.sha!, commit); } diff --git a/src/system/array.ts b/src/system/array.ts index 2bf8e43..f337df2 100644 --- a/src/system/array.ts +++ b/src/system/array.ts @@ -250,6 +250,10 @@ export function joinUnique(source: readonly T[], separator: string): string { return join(new Set(source), separator); } +export function splitAt(source: T[], index: number): [T[], T[]] { + return index < 0 ? [source, []] : [source.slice(0, index), source.slice(index)]; +} + export function uniqueBy( source: readonly TValue[], uniqueKey: (item: TValue) => TKey, diff --git a/src/views/nodes/resultsCommitsNode.ts b/src/views/nodes/resultsCommitsNode.ts index 8dd586c..aff0ece 100644 --- a/src/views/nodes/resultsCommitsNode.ts +++ b/src/views/nodes/resultsCommitsNode.ts @@ -1,5 +1,6 @@ import { TreeItem, TreeItemCollapsibleState } from 'vscode'; import { GitUri } from '../../git/gitUri'; +import { isStash } from '../../git/models/commit'; import type { GitLog } from '../../git/models/log'; import { configuration } from '../../system/configuration'; import { gate } from '../../system/decorators/gate'; @@ -13,6 +14,7 @@ import { LoadMoreNode } from './common'; import { insertDateMarkers } from './helpers'; import type { FilesQueryResults } from './resultsFilesNode'; import { ResultsFilesNode } from './resultsFilesNode'; +import { StashNode } from './stashNode'; import type { PageableViewNode } from './viewNode'; import { ContextValues, ViewNode } from './viewNode'; @@ -105,9 +107,10 @@ export class ResultsCommitsNode new CommitNode(this.view, this, c, undefined, undefined, getBranchAndTagTips, options), + map(log.commits.values(), c => + isStash(c) + ? new StashNode(this.view, this, c, { icon: true }) + : new CommitNode(this.view, this, c, undefined, undefined, getBranchAndTagTips, options), ), this, undefined, diff --git a/src/views/nodes/stashFileNode.ts b/src/views/nodes/stashFileNode.ts index 0740704..754ebe5 100644 --- a/src/views/nodes/stashFileNode.ts +++ b/src/views/nodes/stashFileNode.ts @@ -2,13 +2,19 @@ import type { GitStashCommit } from '../../git/models/commit'; import type { GitFile } from '../../git/models/file'; import type { RepositoriesView } from '../repositoriesView'; import type { StashesView } from '../stashesView'; +import type { ViewsWithCommits } from '../viewBase'; import { CommitFileNode } from './commitFileNode'; import type { ViewNode } from './viewNode'; import { ContextValues } from './viewNode'; -export class StashFileNode extends CommitFileNode { +export class StashFileNode extends CommitFileNode { // eslint-disable-next-line @typescript-eslint/no-useless-constructor - constructor(view: StashesView | RepositoriesView, parent: ViewNode, file: GitFile, commit: GitStashCommit) { + constructor( + view: ViewsWithCommits | StashesView | RepositoriesView, + parent: ViewNode, + file: GitFile, + commit: GitStashCommit, + ) { super(view, parent, file, commit); } diff --git a/src/views/nodes/stashNode.ts b/src/views/nodes/stashNode.ts index 4e1aee9..13d10d7 100644 --- a/src/views/nodes/stashNode.ts +++ b/src/views/nodes/stashNode.ts @@ -1,4 +1,4 @@ -import { TreeItem, TreeItemCollapsibleState } from 'vscode'; +import { ThemeIcon, TreeItem, TreeItemCollapsibleState } from 'vscode'; import { ViewFilesLayout } from '../../config'; import { CommitFormatter } from '../../git/formatters/commitFormatter'; import type { GitStashCommit } from '../../git/models/commit'; @@ -9,6 +9,7 @@ import { joinPaths, normalizePath } from '../../system/path'; import { sortCompare } from '../../system/string'; import type { RepositoriesView } from '../repositoriesView'; import type { StashesView } from '../stashesView'; +import type { ViewsWithCommits } from '../viewBase'; import type { FileNode } from './folderNode'; import { FolderNode } from './folderNode'; import { RepositoryNode } from './repositoryNode'; @@ -16,13 +17,18 @@ import { StashFileNode } from './stashFileNode'; import type { ViewNode } from './viewNode'; import { ContextValues, ViewRefNode } from './viewNode'; -export class StashNode extends ViewRefNode { +export class StashNode extends ViewRefNode { static key = ':stash'; static getId(repoPath: string, ref: string): string { return `${RepositoryNode.getId(repoPath)}${this.key}(${ref})`; } - constructor(view: StashesView | RepositoriesView, parent: ViewNode, public readonly commit: GitStashCommit) { + constructor( + view: ViewsWithCommits | StashesView | RepositoriesView, + parent: ViewNode, + public readonly commit: GitStashCommit, + private readonly options?: { icon?: boolean }, + ) { super(commit.getGitUri(), view, parent); } @@ -73,6 +79,9 @@ export class StashNode extends ViewRefNode