From af29012b47eaaaac6c3188a3a06e7b25135bea29 Mon Sep 17 00:00:00 2001
From: Eric Amodio <eamodio@gmail.com>
Date: Tue, 25 Oct 2022 23:29:33 -0700
Subject: [PATCH] Adds @me support to commit search  - @me automatically
 searches for commits authored by the current user Allows (strips) `@`-prefix
 for author search on non-virtual repos

---
 src/env/node/git/localGitProvider.ts |  10 ++-
 src/git/search.ts                    |  19 ++++-
 src/plus/github/githubGitProvider.ts | 143 +++++++++++++++++------------------
 3 files changed, 94 insertions(+), 78 deletions(-)

diff --git a/src/env/node/git/localGitProvider.ts b/src/env/node/git/localGitProvider.ts
index 0cd6f92..7168e96 100644
--- a/src/env/node/git/localGitProvider.ts
+++ b/src/env/node/git/localGitProvider.ts
@@ -4242,7 +4242,9 @@ export class LocalGitProvider implements GitProvider, Disposable {
 			const limit = options?.limit ?? configuration.get('advanced.maxSearchItems') ?? 0;
 			const similarityThreshold = configuration.get('advanced.similarityThreshold');
 
-			const { args, files, shas } = getGitArgsFromSearchQuery(search);
+			const currentUser = await this.getCurrentUser(repoPath);
+
+			const { args, files, shas } = getGitArgsFromSearchQuery(search, currentUser);
 
 			args.push(`-M${similarityThreshold == null ? '' : `${similarityThreshold}%`}`, '--');
 			if (files.length !== 0) {
@@ -4262,7 +4264,7 @@ export class LocalGitProvider implements GitProvider, Disposable {
 				repoPath,
 				undefined,
 				undefined,
-				await this.getCurrentUser(repoPath),
+				currentUser,
 				limit,
 				false,
 				undefined,
@@ -4334,7 +4336,9 @@ export class LocalGitProvider implements GitProvider, Disposable {
 		try {
 			const refAndDateParser = getRefAndDateParser();
 
-			const { args: searchArgs, files, shas } = getGitArgsFromSearchQuery(search);
+			const currentUser = search.query.includes('@me') ? await this.getCurrentUser(repoPath) : undefined;
+
+			const { args: searchArgs, files, shas } = getGitArgsFromSearchQuery(search, currentUser);
 			if (shas?.size) {
 				const data = await this.git.show2(
 					repoPath,
diff --git a/src/git/search.ts b/src/git/search.ts
index 8f8625d..55a0d50 100644
--- a/src/git/search.ts
+++ b/src/git/search.ts
@@ -1,5 +1,6 @@
 import type { GitRevisionReference } from './models/reference';
 import { GitRevision } from './models/reference';
+import type { GitUser } from './models/user';
 
 export type SearchOperators =
 	| ''
@@ -132,7 +133,7 @@ export function parseSearchQuery(search: SearchQuery): Map<string, string[]> {
 		({ value, text } = match.groups);
 
 		if (text) {
-			op = GitRevision.isSha(text) ? 'commit:' : 'message:';
+			op = text === '@me' ? 'author:' : GitRevision.isSha(text) ? 'commit:' : 'message:';
 			value = text;
 		}
 
@@ -151,7 +152,10 @@ export function parseSearchQuery(search: SearchQuery): Map<string, string[]> {
 
 const doubleQuoteRegex = /"/g;
 
-export function getGitArgsFromSearchQuery(search: SearchQuery): {
+export function getGitArgsFromSearchQuery(
+	search: SearchQuery,
+	currentUser: GitUser | undefined,
+): {
 	args: string[];
 	files: string[];
 	shas?: Set<string> | undefined;
@@ -199,6 +203,17 @@ export function getGitArgsFromSearchQuery(search: SearchQuery): {
 						value = value.replace(doubleQuoteRegex, search.matchRegex ? '\\b' : '');
 						if (!value) continue;
 
+						if (value === '@me') {
+							if (currentUser?.name == null) continue;
+
+							value = currentUser.name;
+						}
+
+						if (value.startsWith('@')) {
+							searchArgs.add(`--author=${value.slice(1)}`);
+							continue;
+						}
+
 						searchArgs.add(`--author=${value}`);
 					}
 
diff --git a/src/plus/github/githubGitProvider.ts b/src/plus/github/githubGitProvider.ts
index a561d8c..5bb88b4 100644
--- a/src/plus/github/githubGitProvider.ts
+++ b/src/plus/github/githubGitProvider.ts
@@ -2563,8 +2563,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
 
 		const operations = parseSearchQuery(search);
 
-		let op;
-		let values = operations.get('commit:');
+		const values = operations.get('commit:');
 		if (values != null) {
 			const commit = await this.getCommit(repoPath, values[0]);
 			if (commit == null) return undefined;
@@ -2580,53 +2579,26 @@ export class GitHubGitProvider implements GitProvider, Disposable {
 			};
 		}
 
-		const query = [];
-
-		for ([op, values] of operations.entries()) {
-			switch (op) {
-				case 'message:':
-					query.push(...values.map(m => m.replace(/ /g, '+')));
-					break;
-
-				case 'author:':
-					query.push(
-						...values.map(a => {
-							a = a.replace(/ /g, '+');
-							if (a.startsWith('@')) return `author:${a.slice(1)}`;
-							if (a.startsWith('"@')) return `author:"${a.slice(2)}`;
-							if (a.includes('@')) return `author-email:${a}`;
-							return `author-name:${a}`;
-						}),
-					);
-					break;
-
-				// case 'change:':
-				// case 'file:':
-				// 	break;
-			}
-		}
-
-		if (query.length === 0) return undefined;
+		const queryArgs = await this.getQueryArgsFromSearchQuery(search, operations, repoPath);
+		if (queryArgs.length === 0) return undefined;
 
 		const limit = this.getPagingLimit(options?.limit);
 
 		try {
 			const { metadata, github, session } = await this.ensureRepositoryContext(repoPath);
 
-			const result = await github.searchCommits(
-				session.accessToken,
-				`repo:${metadata.repo.owner}/${metadata.repo.name}+${query.join('+').trim()}`,
-				{
-					cursor: options?.cursor,
-					limit: limit,
-					sort:
-						options?.ordering === 'date'
-							? 'committer-date'
-							: options?.ordering === 'author-date'
-							? 'author-date'
-							: undefined,
-				},
-			);
+			const query = `repo:${metadata.repo.owner}/${metadata.repo.name}+${queryArgs.join('+').trim()}`;
+
+			const result = await github.searchCommits(session.accessToken, query, {
+				cursor: options?.cursor,
+				limit: limit,
+				sort:
+					options?.ordering === 'date'
+						? 'committer-date'
+						: options?.ordering === 'author-date'
+						? 'author-date'
+						: undefined,
+			});
 			if (result == null) return undefined;
 
 			const commits = new Map<string, GitCommit>();
@@ -2760,8 +2732,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
 			const results: GitSearchResults = new Map<string, GitSearchResultData>();
 			const operations = parseSearchQuery(search);
 
-			let op;
-			let values = operations.get('commit:');
+			const values = operations.get('commit:');
 			if (values != null) {
 				const commitsResults = await Promise.allSettled<Promise<GitCommit | undefined>[]>(
 					values.map(v => this.getCommit(repoPath, v.replace(doubleQuoteRegex, ''))),
@@ -2786,33 +2757,8 @@ export class GitHubGitProvider implements GitProvider, Disposable {
 				};
 			}
 
-			const queryValues: string[] = [];
-
-			for ([op, values] of operations.entries()) {
-				switch (op) {
-					case 'message:':
-						queryValues.push(...values.map(m => m.replace(/ /g, '+')));
-						break;
-
-					case 'author:':
-						queryValues.push(
-							...values.map(a => {
-								a = a.replace(/ /g, '+');
-								if (a.startsWith('@')) return `author:${a.slice(1)}`;
-								if (a.startsWith('"@')) return `author:"${a.slice(2)}`;
-								if (a.includes('@')) return `author-email:${a}`;
-								return `author-name:${a}`;
-							}),
-						);
-						break;
-
-					// case 'change:':
-					// case 'file:':
-					// 	break;
-				}
-			}
-
-			if (queryValues.length === 0) {
+			const queryArgs = await this.getQueryArgsFromSearchQuery(search, operations, repoPath);
+			if (queryArgs.length === 0) {
 				return {
 					repoPath: repoPath,
 					query: search,
@@ -2823,7 +2769,7 @@ export class GitHubGitProvider implements GitProvider, Disposable {
 
 			const { metadata, github, session } = await this.ensureRepositoryContext(repoPath);
 
-			const query = `repo:${metadata.repo.owner}/${metadata.repo.name}+${queryValues.join('+').trim()}`;
+			const query = `repo:${metadata.repo.owner}/${metadata.repo.name}+${queryArgs.join('+').trim()}`;
 
 			async function searchForCommitsCore(
 				this: GitHubGitProvider,
@@ -3182,6 +3128,57 @@ export class GitHubGitProvider implements GitProvider, Disposable {
 
 		return ref;
 	}
+
+	private async getQueryArgsFromSearchQuery(
+		search: SearchQuery,
+		operations: Map<string, string[]>,
+		repoPath: string,
+	) {
+		const query = [];
+
+		for (const [op, values] of operations.entries()) {
+			switch (op) {
+				case 'message:':
+					query.push(...values.map(m => m.replace(/ /g, '+')));
+					break;
+
+				case 'author:': {
+					let currentUser: GitUser | undefined;
+					if (values.includes('@me')) {
+						currentUser = await this.getCurrentUser(repoPath);
+					}
+
+					for (let value of values) {
+						if (!value) continue;
+						value = value.replace(doubleQuoteRegex, search.matchRegex ? '\\b' : '');
+						if (!value) continue;
+
+						if (value === '@me') {
+							if (currentUser?.username == null) continue;
+
+							value = `@${currentUser.username}`;
+						}
+
+						value = value.replace(/ /g, '+');
+						if (value.startsWith('@')) {
+							query.push(`author:${value.slice(1)}`);
+						} else if (value.includes('@')) {
+							query.push(`author-email:${value}`);
+						} else {
+							query.push(`author-name:${value}`);
+						}
+					}
+
+					break;
+				}
+				// case 'change:':
+				// case 'file:':
+				// 	break;
+			}
+		}
+
+		return query;
+	}
 }
 
 function encodeAuthority<T>(scheme: string, metadata?: T): string {