Skip to content

Commit

Permalink
Updates commit search
Browse files Browse the repository at this point in the history
Adds alt tokens for each search by
Adds message: token
Adds double quote support for word boundary
  • Loading branch information
eamodio committed Sep 11, 2019
1 parent 5d56039 commit 438a147
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 70 deletions.
31 changes: 18 additions & 13 deletions src/commands/git/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/* eslint-disable no-loop-func */
import { QuickInputButton } from 'vscode';
import { Container } from '../../container';
import { GitLog, GitLogCommit, GitService, Repository } from '../../git/gitService';
import { GitLog, GitLogCommit, GitService, Repository, searchOperators, SearchOperators } from '../../git/gitService';
import { GlyphChars } from '../../constants';
import { QuickCommandBase, StepAsyncGenerator, StepSelection, StepState } from '../quickCommand';
import { RepositoryQuickPickItem } from '../../quickpicks';
Expand Down Expand Up @@ -32,13 +32,18 @@ export interface SearchGitCommandArgs {
prefillOnly?: boolean;
}

const searchOperators = new Set<string>(['', 'author:', 'change:', 'commit:', 'file:']);
const searchOperatorToTitleMap = new Map<string, string>([
const searchOperatorToTitleMap = new Map<SearchOperators, string>([
['', 'Search by Message'],
['author:', 'Search by Author or Committer'],
['change:', 'Search by Changes'],
['=:', 'Search by Message'],
['message:', 'Search by Message'],
['@:', 'Search by Author'],
['author:', 'Search by Author'],
['#:', 'Search by Commit ID'],
['commit:', 'Search by Commit ID'],
['file:', 'Search by File']
['?:', 'Search by File'],
['file:', 'Search by File'],
['~:', 'Search by Changes'],
['change:', 'Search by Changes']
]);

export class SearchGitCommand extends QuickCommandBase<State> {
Expand Down Expand Up @@ -132,30 +137,30 @@ export class SearchGitCommand extends QuickCommandBase<State> {
}

if (state.search === undefined || state.counter < 2) {
const items: QuickPickItemOfT<string>[] = [
const items: QuickPickItemOfT<SearchOperators>[] = [
{
label: searchOperatorToTitleMap.get('')!,
description: `pattern ${GlyphChars.Dash} use quotes to search for phrases`,
item: ''
description: `pattern or message: pattern or =: pattern ${GlyphChars.Dash} use quotes to search for phrases`,
item: 'message:'
},
{
label: searchOperatorToTitleMap.get('author:')!,
description: 'author: pattern',
description: 'author: pattern or @: pattern',
item: 'author:'
},
{
label: searchOperatorToTitleMap.get('commit:')!,
description: 'commit: sha',
description: 'commit: sha or #: sha',
item: 'commit:'
},
{
label: searchOperatorToTitleMap.get('file:')!,
description: 'file: glob',
description: 'file: glob or ?: glob',
item: 'file:'
},
{
label: searchOperatorToTitleMap.get('change:')!,
description: 'change: pattern',
description: 'change: pattern or ~: pattern',
item: 'change:'
}
];
Expand Down
7 changes: 6 additions & 1 deletion src/git/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -807,7 +807,12 @@ export class Git {
search: string[] = emptyArray,
{ maxCount, useShow }: { maxCount?: number; useShow?: boolean } = {}
) {
const params = [useShow ? 'show' : 'log', '--name-status', `--format=${GitLogParser.defaultFormat}`];
const params = [
useShow ? 'show' : 'log',
'--name-status',
`--format=${GitLogParser.defaultFormat}`,
'--use-mailmap'
];
if (maxCount && !useShow) {
params.push(`-n${maxCount}`);
}
Expand Down
145 changes: 100 additions & 45 deletions src/git/gitService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,55 @@ const RepoSearchWarnings = {
doesNotExist: /no such file or directory/i
};

const doubleQuoteRegex = /"/g;
const userConfigRegex = /^user\.(name|email) (.*)$/gm;
const mappedAuthorRegex = /(.+)\s<(.+)>/;
const searchMessageOperationRegex = /(?=(.*?)\s?(?:(?:author:|commit:|file:|change:)|$))/;
const searchMessageValuesRegex = /(?:^|\b)\s?(?:\s?"([^"]*)(?:"|$)|([^"\s]*))/g;
const searchOperationRegex = /((?:author|commit|file|change):)\s?(?=(.*?)\s?(?:(?:author:|commit:|file:|change:)|$))/g;
const searchMessageOperationRegex = /(?=(.*?)\s?(?:(?:=:|message:|@:|author:|#:|commit:|\?:|file:|~:|change:)|$))/;
const searchMessageValuesRegex = /(".+"|[^\b\s]+)/g;
const searchOperationRegex = /((?:=|message|@|author|#|commit|\?|file|~|change):)\s?(?=(.*?)\s?(?:(?:=:|message:|@:|author:|#:|commit:|\?:|file:|~:|change:)|$))/g;

const emptyPromise: Promise<GitBlame | GitDiff | GitLog | undefined> = Promise.resolve(undefined);
const reflogCommands = ['merge', 'pull'];

export type SearchOperators =
| ''
| '=:'
| 'message:'
| '@:'
| 'author:'
| '#:'
| 'commit:'
| '?:'
| 'file:'
| '~:'
| 'change:';
export const searchOperators = new Set<string>([
'',
'=:',
'message:',
'@:',
'author:',
'#:',
'commit:',
'?:',
'file:',
'~:',
'change:'
]);
const normalizeSearchOperatorsMap = new Map<SearchOperators, SearchOperators>([
['', 'message:'],
['=:', 'message:'],
['message:', 'message:'],
['@:', 'author:'],
['author:', 'author:'],
['#:', 'commit:'],
['commit:', 'commit:'],
['?:', 'file:'],
['file:', 'file:'],
['~:', 'change:'],
['change:', 'change:']
]);

export class GitService implements Disposable {
private _onDidChangeRepositories = new EventEmitter<void>();
get onDidChangeRepositories(): Event<void> {
Expand Down Expand Up @@ -1454,49 +1494,48 @@ export class GitService implements Disposable {
searchArgs.add('-m');
searchArgs.add(`-M${similarityThreshold == null ? '' : `${similarityThreshold}%`}`);
for (const value of values) {
searchArgs.add(value);
searchArgs.add(value.replace(doubleQuoteRegex, ''));
}
} else {
searchArgs.add(`-M${similarityThreshold == null ? '' : `${similarityThreshold}%`}`);
searchArgs.add('--all');
searchArgs.add('--full-history');
searchArgs.add(search.matchRegex ? '--extended-regexp' : '--fixed-strings');
if (!search.matchCase) {
if (search.matchRegex && !search.matchCase) {
searchArgs.add('--regexp-ignore-case');
}

for ([op, values] of operations.entries()) {
switch (op) {
case 'author:':
case 'message:':
searchArgs.add('-m');
if (search.matchAll) {
searchArgs.add('--all-match');
}
for (const value of values) {
searchArgs.add(`--author=${value}`);
searchArgs.add(`--committer=${value}`);
searchArgs.add(`--grep=${value.replace(doubleQuoteRegex, '\\b')}`);
}

break;

case 'change:':
case 'author:':
searchArgs.add('-m');
for (const value of values) {
searchArgs.add(`-G=${value}`);
searchArgs.add(`--author=${value.replace(doubleQuoteRegex, '\\b')}`);
}

break;

case 'file:':
case 'change:':
for (const value of values) {
files.push(value);
searchArgs.add(`-G=${value}`);
}

break;

case '':
searchArgs.add('-m');
if (search.matchAll) {
searchArgs.add('--all-match');
}
case 'file:':
for (const value of values) {
searchArgs.add(`--grep=${value}`);
files.push(value.replace(doubleQuoteRegex, ''));
}

break;
Expand Down Expand Up @@ -2835,28 +2874,7 @@ export class GitService implements Disposable {

let match = searchMessageOperationRegex.exec(search);
if (match != null && match[1] !== '') {
const messageSearch = match[1];

let quoted;
let unquoted;
do {
match = searchMessageValuesRegex.exec(messageSearch);
if (match == null) break;

[, quoted, unquoted] = match;
if (!quoted && !unquoted) {
searchMessageValuesRegex.lastIndex = 0;
break;
}

let values = operations.get('');
if (values === undefined) {
values = [quoted || unquoted];
operations.set('', values);
} else {
values.push(quoted || unquoted);
}
} while (match != null);
this.parseSearchMessageOperations(match[1], operations);
}

do {
Expand All @@ -2866,16 +2884,53 @@ export class GitService implements Disposable {
[, op, value] = match;

if (op !== undefined) {
let values = operations.get(op);
if (values === undefined) {
values = [value];
operations.set(op, values);
op = normalizeSearchOperatorsMap.get(op as SearchOperators)!;

if (op === 'message:') {
this.parseSearchMessageOperations(value, operations);
} else {
values.push(value);
let values = operations.get(op);
if (values === undefined) {
values = [value];
operations.set(op, values);
} else {
values.push(value);
}
}
}
} while (match != null);

return operations;
}

private static parseSearchMessageOperations(message: string, operations: Map<string, string[]>) {
let values = operations.get('message:');

if (message === emptyStr) {
if (values === undefined) {
values = [''];
operations.set('message:', values);
} else {
values.push('');
}

return;
}

let match;
let value;
do {
match = searchMessageValuesRegex.exec(message);
if (match == null) break;

[, value] = match;

if (values === undefined) {
values = [value];
operations.set('message:', values);
} else {
values.push(value);
}
} while (match != null);
}
}
21 changes: 10 additions & 11 deletions src/views/nodes/searchNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { debug, gate, Iterables, log, Promises } from '../../system';
import { View } from '../viewBase';
import { CommandMessageNode, MessageNode } from './common';
import { ResourceType, unknownGitUri, ViewNode } from './viewNode';
import { SearchOperators } from '../../git/gitService';

export class SearchNode extends ViewNode {
private _children: (ViewNode | MessageNode)[] = [];
Expand All @@ -21,9 +22,7 @@ export class SearchNode extends ViewNode {
command: 'gitlens.showCommitSearch'
};

const getCommandArgs = (
search: '' | 'author:' | 'change:' | 'commit:' | 'file:'
): SearchCommitsCommandArgs => {
const getCommandArgs = (search: SearchOperators): SearchCommitsCommandArgs => {
return {
search: { pattern: search },
prefillOnly: true
Expand All @@ -36,10 +35,10 @@ export class SearchNode extends ViewNode {
this,
{
...command,
arguments: [this, getCommandArgs('')]
arguments: [this, getCommandArgs('message:')]
},
'Search by Message',
`pattern ${GlyphChars.Dash} use quotes to search for phrases`,
`pattern or message: pattern or =: pattern ${GlyphChars.Dash} use quotes to search for phrases`,
`Click to search for commits with matching messages ${GlyphChars.Dash} use quotes to search for phrases`
),
new CommandMessageNode(
Expand All @@ -49,9 +48,9 @@ export class SearchNode extends ViewNode {
...command,
arguments: [this, getCommandArgs('author:')]
},
`${GlyphChars.Space.repeat(4)} or, Author or Committer`,
'author: pattern',
'Click to search for commits with matching authors or committers'
`${GlyphChars.Space.repeat(4)} or, Author`,
'author: pattern or @: pattern',
'Click to search for commits with matching authors'
),
new CommandMessageNode(
this.view,
Expand All @@ -61,7 +60,7 @@ export class SearchNode extends ViewNode {
arguments: [this, getCommandArgs('commit:')]
},
`${GlyphChars.Space.repeat(4)} or, Commit ID`,
'commit: sha',
'commit: sha or #: sha',
'Click to search for commits with matching commit ids'
),
new CommandMessageNode(
Expand All @@ -72,7 +71,7 @@ export class SearchNode extends ViewNode {
arguments: [this, getCommandArgs('file:')]
},
`${GlyphChars.Space.repeat(4)} or, Files`,
'file: glob',
'file: glob or ?: glob',
'Click to search for commits with matching files'
),
new CommandMessageNode(
Expand All @@ -83,7 +82,7 @@ export class SearchNode extends ViewNode {
arguments: [this, getCommandArgs('change:')]
},
`${GlyphChars.Space.repeat(4)} or, Changes`,
'change: pattern',
'change: pattern or ~: pattern',
'Click to search for commits with matching changes'
)
];
Expand Down

0 comments on commit 438a147

Please sign in to comment.