Procházet zdrojové kódy

Changes tag parsing - hopefully fixes #855

Unifies tag parsing to include msg, ref, & dates
Bumps required Git to 2.7.2
Adds more tag sorting options similar to branches
main
Eric Amodio před 5 roky
rodič
revize
358fa9071c
17 změnil soubory, kde provedl 181 přidání a 115 odebrání
  1. +6
    -2
      package.json
  2. +1
    -1
      src/commands/git/cherry-pick.ts
  3. +1
    -1
      src/commands/git/merge.ts
  4. +1
    -1
      src/commands/git/rebase.ts
  5. +2
    -2
      src/commands/git/switch.ts
  6. +39
    -21
      src/commands/quickCommand.helpers.ts
  7. +3
    -1
      src/config.ts
  8. +3
    -3
      src/extension.ts
  9. +2
    -2
      src/git/git.ts
  10. +2
    -23
      src/git/gitService.ts
  11. +1
    -1
      src/git/models/repository.ts
  12. +68
    -4
      src/git/models/tag.ts
  13. +26
    -36
      src/git/parsers/tagParser.ts
  14. +2
    -2
      src/messages.ts
  15. +9
    -5
      src/quickpicks/gitQuickPicks.ts
  16. +1
    -5
      src/quickpicks/referencesQuickPick.ts
  17. +14
    -5
      src/views/nodes/tagNode.ts

+ 6
- 2
package.json Zobrazit soubor

@ -1308,11 +1308,15 @@
"default": "name:desc",
"enum": [
"name:desc",
"name:asc"
"name:asc",
"date:desc",
"date:asc"
],
"enumDescriptions": [
"Sorts tags by name in descending order",
"Sorts tags by name in ascending order"
"Sorts tags by name in ascending order",
"Sorts tags by date in descending order",
"Sorts tags by date in ascending order"
],
"markdownDescription": "Specifies how tags are sorted in quick pick menus and views",
"scope": "window"

+ 1
- 1
src/commands/git/cherry-pick.ts Zobrazit soubor

@ -132,7 +132,7 @@ export class CherryPickGitCommand extends QuickCommandBase {
)}(select or enter a reference)`,
matchOnDescription: true,
matchOnDetail: true,
items: await getBranchesAndOrTags(state.repo, true, {
items: await getBranchesAndOrTags(state.repo, ['branches', 'tags'], {
filterBranches: b => b.id !== destId
}),
onValidateValue: getValidateGitReferenceFn(state.repo)

+ 1
- 1
src/commands/git/merge.ts Zobrazit soubor

@ -125,7 +125,7 @@ export class MergeGitCommand extends QuickCommandBase {
)}(select or enter a reference)`,
matchOnDescription: true,
matchOnDetail: true,
items: await getBranchesAndOrTags(state.repo, true, {
items: await getBranchesAndOrTags(state.repo, ['branches', 'tags'], {
filterBranches: b => b.id !== destId,
picked: state.reference && state.reference.ref
}),

+ 1
- 1
src/commands/git/rebase.ts Zobrazit soubor

@ -145,7 +145,7 @@ export class RebaseGitCommand extends QuickCommandBase {
} onto${GlyphChars.Space.repeat(3)}(select or enter a reference)`,
matchOnDescription: true,
matchOnDetail: true,
items: await getBranchesAndOrTags(state.repo, true, {
items: await getBranchesAndOrTags(state.repo, ['branches', 'tags'], {
picked: state.reference && state.reference.ref
}),
additionalButtons: [pickBranchOrCommitButton],

+ 2
- 2
src/commands/git/switch.ts Zobrazit soubor

@ -138,7 +138,7 @@ export class SwitchGitCommand extends QuickCommandBase {
const items = await getBranchesAndOrTags(
state.repos,
showTags,
showTags ? ['branches', 'tags'] : ['branches'],
state.repos.length === 1 ? undefined : { filterBranches: b => !b.remote }
);
@ -171,7 +171,7 @@ export class SwitchGitCommand extends QuickCommandBase {
quickpick.items = await getBranchesAndOrTags(
state.repos!,
showTags,
showTags ? ['branches', 'tags'] : ['branches'],
state.repos!.length === 1 ? undefined : { filterBranches: b => !b.remote }
);

+ 39
- 21
src/commands/quickCommand.helpers.ts Zobrazit soubor

@ -9,21 +9,19 @@ export async function getBranches(
repos: Repository | Repository[],
options: { filterBranches?: (b: GitBranch) => boolean; picked?: string | string[] } = {}
): Promise<BranchQuickPickItem[]> {
return getBranchesAndOrTags(repos, false, options) as Promise<BranchQuickPickItem[]>;
return getBranchesAndOrTags(repos, ['branches'], options) as Promise<BranchQuickPickItem[]>;
}
export async function getTags(
repos: Repository | Repository[],
options: { filterTags?: (t: GitTag) => boolean; picked?: string | string[] } = {}
): Promise<TagQuickPickItem[]> {
return getBranchesAndOrTags(repos, true, { ...options, filterBranches: () => false }) as Promise<
TagQuickPickItem[]
>;
return getBranchesAndOrTags(repos, ['tags'], options) as Promise<TagQuickPickItem[]>;
}
export async function getBranchesAndOrTags(
repos: Repository | Repository[],
includeTags: boolean,
include: ('tags' | 'branches')[],
{
filterBranches,
filterTags,
@ -34,7 +32,7 @@ export async function getBranchesAndOrTags(
picked?: string | string[];
} = {}
): Promise<(BranchQuickPickItem | TagQuickPickItem)[]> {
let branches: GitBranch[];
let branches: GitBranch[] | undefined;
let tags: GitTag[] | undefined;
let singleRepo = false;
@ -42,32 +40,36 @@ export async function getBranchesAndOrTags(
singleRepo = true;
const repo = repos instanceof Repository ? repos : repos[0];
[branches, tags] = await Promise.all<GitBranch[], GitTag[] | undefined>([
repo.getBranches({ filter: filterBranches, sort: true }),
includeTags ? repo.getTags({ filter: filterTags, includeRefs: true, sort: true }) : undefined
[branches, tags] = await Promise.all<GitBranch[] | undefined, GitTag[] | undefined>([
include.includes('branches') ? repo.getBranches({ filter: filterBranches, sort: true }) : undefined,
include.includes('tags') ? repo.getTags({ filter: filterTags, sort: true }) : undefined
]);
} else {
const [branchesByRepo, tagsByRepo] = await Promise.all<GitBranch[][], GitTag[][] | undefined>([
Promise.all(repos.map(r => r.getBranches({ filter: filterBranches, sort: true }))),
includeTags
? Promise.all(repos.map(r => r.getTags({ filter: filterTags, includeRefs: true, sort: true })))
const [branchesByRepo, tagsByRepo] = await Promise.all<GitBranch[][] | undefined, GitTag[][] | undefined>([
include.includes('branches')
? Promise.all(repos.map(r => r.getBranches({ filter: filterBranches, sort: true })))
: undefined,
include.includes('tags')
? Promise.all(repos.map(r => r.getTags({ filter: filterTags, sort: true })))
: undefined
]);
branches = GitBranch.sort(
Arrays.intersection(...branchesByRepo, ((b1: GitBranch, b2: GitBranch) => b1.name === b2.name) as any)
);
if (include.includes('branches')) {
branches = GitBranch.sort(
Arrays.intersection(...branchesByRepo!, ((b1: GitBranch, b2: GitBranch) => b1.name === b2.name) as any)
);
}
if (includeTags) {
if (include.includes('tags')) {
tags = GitTag.sort(
Arrays.intersection(...tagsByRepo!, ((t1: GitTag, t2: GitTag) => t1.name === t2.name) as any)
);
}
}
if (!includeTags) {
if (include.includes('branches') && !include.includes('tags')) {
return Promise.all(
branches.map(b =>
branches!.map(b =>
BranchQuickPickItem.create(
b,
picked != null && (typeof picked === 'string' ? b.ref === picked : picked.includes(b.ref)),
@ -82,8 +84,23 @@ export async function getBranchesAndOrTags(
);
}
if (include.includes('tags') && !include.includes('branches')) {
return Promise.all(
tags!.map(t =>
TagQuickPickItem.create(
t,
picked != null && (typeof picked === 'string' ? t.ref === picked : picked.includes(t.ref)),
{
message: singleRepo,
ref: singleRepo
}
)
)
);
}
return Promise.all<BranchQuickPickItem | TagQuickPickItem>([
...branches
...branches!
.filter(b => !b.remote)
.map(b =>
BranchQuickPickItem.create(
@ -101,12 +118,13 @@ export async function getBranchesAndOrTags(
t,
picked != null && (typeof picked === 'string' ? t.ref === picked : picked.includes(t.ref)),
{
message: singleRepo,
ref: singleRepo,
type: true
}
)
),
...branches
...branches!
.filter(b => b.remote)
.map(b =>
BranchQuickPickItem.create(

+ 3
- 1
src/config.ts Zobrazit soubor

@ -201,7 +201,9 @@ export enum StatusBarCommand {
export enum TagSorting {
NameDesc = 'name:desc',
NameAsc = 'name:asc'
NameAsc = 'name:asc',
DateDesc = 'date:desc',
DateAsc = 'date:asc'
}
export enum ViewBranchesLayout {

+ 3
- 3
src/extension.ts Zobrazit soubor

@ -150,10 +150,10 @@ async function migrateSettings(context: ExtensionContext, previousVersion: strin
}
function notifyOnUnsupportedGitVersion(version: string) {
if (GitService.compareGitVersion('2.2.0') !== -1) return;
if (GitService.compareGitVersion('2.7.2') !== -1) return;
// If git is less than v2.2.0
void Messages.showGitVersionUnsupportedErrorMessage(version);
// If git is less than v2.7.2
void Messages.showGitVersionUnsupportedErrorMessage(version, '2.7.2');
}
async function showWelcomeOrWhatsNew(version: string, previousVersion: string | undefined) {

+ 2
- 2
src/git/git.ts Zobrazit soubor

@ -8,7 +8,7 @@ import { Logger } from '../logger';
import { Objects, Strings } from '../system';
import { findGitPath, GitLocation } from './locator';
import { run, RunOptions } from './shell';
import { GitBranchParser, GitLogParser, GitReflogParser, GitStashParser } from './parsers/parsers';
import { GitBranchParser, GitLogParser, GitReflogParser, GitStashParser, GitTagParser } from './parsers/parsers';
import { GitFileStatus } from './models/file';
export * from './models/models';
@ -1156,6 +1156,6 @@ export namespace Git {
}
export function tag(repoPath: string) {
return git<string>({ cwd: repoPath }, 'tag', '-l', '-n1');
return git<string>({ cwd: repoPath }, 'tag', '-l', `--format=${GitTagParser.defaultFormat}`);
}
}

+ 2
- 23
src/git/gitService.ts Zobrazit soubor

@ -177,7 +177,6 @@ export class GitService implements Disposable {
private readonly _branchesCache = new Map<string, GitBranch[]>();
private readonly _tagsCache = new Map<string, GitTag[]>();
private readonly _tagsWithRefsCache = new Map<string, GitTag[]>();
private readonly _trackedCache = new Map<string, boolean | Promise<boolean>>();
private readonly _userMapCache = new Map<string, { name?: string; email?: string } | null>();
@ -198,7 +197,6 @@ export class GitService implements Disposable {
this._repositoryTree.forEach(r => r.dispose());
this._branchesCache.clear();
this._tagsCache.clear();
this._tagsWithRefsCache.clear();
this._trackedCache.clear();
this._userMapCache.clear();
@ -230,7 +228,6 @@ export class GitService implements Disposable {
this._branchesCache.delete(repo.path);
this._tagsCache.delete(repo.path);
this._tagsWithRefsCache.clear();
this._trackedCache.clear();
if (e.changed(RepositoryChange.Config)) {
@ -1176,10 +1173,7 @@ export class GitService implements Disposable {
@log()
async getBranchesAndTagsTipsFn(repoPath: string | undefined, currentName?: string) {
const [branches, tags] = await Promise.all([
this.getBranches(repoPath),
this.getTags(repoPath, { includeRefs: true })
]);
const [branches, tags] = await Promise.all([this.getBranches(repoPath), this.getTags(repoPath)]);
const branchesAndTagsBySha = Arrays.groupByFilterMap(
(branches as { name: string; sha: string }[]).concat(tags as { name: string; sha: string }[]),
@ -2475,27 +2469,12 @@ export class GitService implements Disposable {
@log()
async getTags(
repoPath: string | undefined,
options: { filter?: (t: GitTag) => boolean; includeRefs?: boolean; sort?: boolean } = {}
options: { filter?: (t: GitTag) => boolean; sort?: boolean } = {}
): Promise<GitTag[]> {
if (repoPath === undefined) return [];
let tags: GitTag[] | undefined;
try {
if (options.includeRefs) {
tags = this._tagsWithRefsCache.get(repoPath);
if (tags !== undefined) return tags;
const data = await Git.show_ref__tags(repoPath);
tags = GitTagParser.parseWithRef(data, repoPath) || [];
const repo = await this.getRepository(repoPath);
if (repo !== undefined && repo.supportsChangeEvents) {
this._tagsWithRefsCache.set(repoPath, tags);
}
return tags;
}
tags = this._tagsCache.get(repoPath);
if (tags !== undefined) return tags;

+ 1
- 1
src/git/models/repository.ts Zobrazit soubor

@ -360,7 +360,7 @@ export class Repository implements Disposable {
return Container.git.getStatusForRepo(this.path);
}
getTags(options?: { filter?: (t: GitTag) => boolean; includeRefs?: boolean; sort?: boolean }): Promise<GitTag[]> {
getTags(options?: { filter?: (t: GitTag) => boolean; sort?: boolean }): Promise<GitTag[]> {
return Container.git.getTags(this.path, options);
}

+ 68
- 4
src/git/models/tag.ts Zobrazit soubor

@ -1,7 +1,17 @@
'use strict';
import { memoize } from '../../system';
import { Dates, memoize } from '../../system';
import { GitReference } from './models';
import { configuration, TagSorting } from '../../configuration';
import { configuration, DateStyle, TagSorting } from '../../configuration';
export const TagDateFormatting = {
dateFormat: undefined! as string | null,
dateStyle: undefined! as DateStyle,
reset: () => {
TagDateFormatting.dateFormat = configuration.get('defaultDateFormat');
TagDateFormatting.dateStyle = configuration.get('defaultDateStyle');
}
};
export class GitTag implements GitReference {
static is(tag: any): tag is GitTag {
@ -16,6 +26,10 @@ export class GitTag implements GitReference {
const order = configuration.get('sortTagsBy');
switch (order) {
case TagSorting.DateAsc:
return tags.sort((a, b) => a.date.getTime() - b.date.getTime());
case TagSorting.DateDesc:
return tags.sort((a, b) => b.date.getTime() - a.date.getTime());
case TagSorting.NameAsc:
return tags.sort((a, b) =>
b.name.localeCompare(a.name, undefined, { numeric: true, sensitivity: 'base' })
@ -32,15 +46,65 @@ export class GitTag implements GitReference {
constructor(
public readonly repoPath: string,
public readonly name: string,
public readonly sha?: string,
public readonly annotation?: string
public readonly sha: string,
public readonly message: string,
public readonly date: Date,
public readonly commitDate: Date | undefined
) {}
get formattedDate(): string {
return TagDateFormatting.dateStyle === DateStyle.Absolute
? this.formatDate(TagDateFormatting.dateFormat)
: this.formatDateFromNow();
}
get ref() {
return this.name;
}
@memoize()
private get commitDateFormatter(): Dates.DateFormatter | undefined {
return this.commitDate == null ? undefined : Dates.getFormatter(this.commitDate);
}
@memoize()
private get dateFormatter(): Dates.DateFormatter {
return Dates.getFormatter(this.date);
}
@memoize<GitTag['formatCommitDate']>(format => (format == null ? 'MMMM Do, YYYY h:mma' : format))
formatCommitDate(format?: string | null) {
const formatter = this.commitDateFormatter;
if (formatter == null) return '';
if (format == null) {
format = 'MMMM Do, YYYY h:mma';
}
return formatter.format(format);
}
formatCommitDateFromNow() {
const formatter = this.commitDateFormatter;
if (formatter == null) return '';
return formatter.fromNow();
}
@memoize<GitTag['formatDate']>(format => (format == null ? 'MMMM Do, YYYY h:mma' : format))
formatDate(format?: string | null) {
if (format == null) {
format = 'MMMM Do, YYYY h:mma';
}
return this.dateFormatter.format(format);
}
formatDateFromNow() {
return this.dateFormatter.fromNow();
}
@memoize()
getBasename(): string {
const index = this.name.lastIndexOf('/');
return index !== -1 ? this.name.substring(index + 1) : this.name;

+ 26
- 36
src/git/parsers/tagParser.ts Zobrazit soubor

@ -2,10 +2,21 @@
import { GitTag } from '../git';
import { debug } from '../../system';
const tagWithRefRegex = /([0-9,a-f]+)\srefs\/tags\/(.*)/gm;
const tagWithAnnotationRegex = /^(.+?)(?:$|(?:\s+)(.*)$)/gm;
const tagRegex = /^<n>(.+)<r>(.*)<d>(.*)<ad>(.*)<s>(.*)$/gm;
// Using %x00 codes because some shells seem to try to expand things if not
const lb = '%3c'; // `%${'<'.charCodeAt(0).toString(16)}`;
const rb = '%3e'; // `%${'>'.charCodeAt(0).toString(16)}`;
export class GitTagParser {
static defaultFormat = [
`${lb}n${rb}%(refname)`, // tag name
`${lb}r${rb}%(objectname)`, // ref
`${lb}d${rb}%(creatordate:iso8601)`, // created date
`${lb}ad${rb}%(authordate:iso8601)`, // author date
`${lb}s${rb}%(subject)` // message
].join('');
@debug({ args: false, singleLine: true })
static parse(data: string, repoPath: string): GitTag[] | undefined {
if (!data) return undefined;
@ -13,52 +24,31 @@ export class GitTagParser {
const tags: GitTag[] = [];
let name;
let annotation;
let ref;
let date;
let commitDate;
let message;
let match;
do {
match = tagWithAnnotationRegex.exec(data);
match = tagRegex.exec(data);
if (match == null) break;
[, name, annotation] = match;
tags.push(
new GitTag(
repoPath,
// Stops excessive memory usage -- https://bugs.chromium.org/p/v8/issues/detail?id=2869
` ${name}`.substr(1),
undefined,
// Stops excessive memory usage -- https://bugs.chromium.org/p/v8/issues/detail?id=2869
annotation == null || annotation.length === 0 ? undefined : ` ${annotation}`.substr(1)
)
);
} while (true);
return tags;
}
static parseWithRef(data: string, repoPath: string): GitTag[] | undefined {
if (!data) return undefined;
const tags: GitTag[] = [];
let sha;
let name;
let match: RegExpExecArray | null;
do {
match = tagWithRefRegex.exec(data);
if (match == null) break;
[, name, ref, date, commitDate, message] = match;
[, sha, name] = match;
// Strip off refs/tags/
name = name.substr(10);
tags.push(
new GitTag(
repoPath,
name,
// Stops excessive memory usage -- https://bugs.chromium.org/p/v8/issues/detail?id=2869
` ${name}`.substr(1),
` ${ref}`.substr(1),
// Stops excessive memory usage -- https://bugs.chromium.org/p/v8/issues/detail?id=2869
` ${sha}`.substr(1)
` ${message}`.substr(1),
new Date(date),
commitDate == null || commitDate.length === 0 ? undefined : new Date(commitDate)
)
);
} while (true);

+ 2
- 2
src/messages.ts Zobrazit soubor

@ -72,10 +72,10 @@ export class Messages {
);
}
static showGitVersionUnsupportedErrorMessage(version: string): Promise<MessageItem | undefined> {
static showGitVersionUnsupportedErrorMessage(version: string, required: string): Promise<MessageItem | undefined> {
return Messages.showMessage(
'error',
`GitLens requires a newer version of Git (>= 2.2.0) than is currently installed (${version}). Please install a more recent version of Git.`,
`GitLens requires a newer version of Git (>= ${required}) than is currently installed (${version}). Please install a more recent version of Git.`,
SuppressedMessages.GitVersionWarning
);
}

+ 9
- 5
src/quickpicks/gitQuickPicks.ts Zobrazit soubor

@ -368,7 +368,7 @@ export namespace TagQuickPickItem {
tag: GitTag,
picked?: boolean,
options: {
annotation?: boolean;
message?: boolean;
checked?: boolean;
ref?: boolean;
type?: boolean;
@ -379,15 +379,19 @@ export namespace TagQuickPickItem {
description = 'tag';
}
if (options.ref && tag.sha) {
if (options.ref) {
description = description
? `${description}${Strings.pad('$(git-commit)', 2, 2)}${GitService.shortenSha(tag.sha)}`
: `${Strings.pad('$(git-commit)', 0, 2)}${GitService.shortenSha(tag.sha)}`;
description = description
? `${description}${Strings.pad(GlyphChars.Dot, 2, 2)}${tag.formattedDate}`
: tag.formattedDate;
}
if (options.annotation && tag.annotation) {
const annotation = emojify(tag.annotation);
description = description ? `${description}${Strings.pad(GlyphChars.Dot, 2, 2)}${annotation}` : annotation;
if (options.message) {
const message = emojify(tag.message);
description = description ? `${description}${Strings.pad(GlyphChars.Dot, 2, 2)}${message}` : message;
}
const item: TagQuickPickItem = {

+ 1
- 5
src/quickpicks/referencesQuickPick.ts Zobrazit soubor

@ -160,11 +160,7 @@ export class ReferencesQuickPick {
})
: undefined,
include & ReferencesQuickPickIncludes.Tags
? Container.git.getTags(this.repoPath, {
...options,
filter: filterTags,
includeRefs: true
})
? Container.git.getTags(this.repoPath, { ...options, filter: filterTags })
: undefined
]),
token

+ 14
- 5
src/views/nodes/tagNode.ts Zobrazit soubor

@ -2,7 +2,7 @@
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { ViewBranchesLayout } from '../../configuration';
import { Container } from '../../container';
import { GitService, GitTag, GitUri } from '../../git/gitService';
import { GitService, GitTag, GitUri, TagDateFormatting } from '../../git/gitService';
import { Iterables, Strings } from '../../system';
import { RepositoriesView } from '../repositoriesView';
import { CommitNode } from './commitNode';
@ -11,7 +11,6 @@ import { insertDateMarkers } from './helpers';
import { PageableViewNode, ResourceType, ViewNode, ViewRefNode } from './viewNode';
import { emojify } from '../../emojis';
import { RepositoryNode } from './repositoryNode';
import { Git } from '../../git/git';
import { GlyphChars } from '../../constants';
export class TagNode extends ViewRefNode<RepositoriesView> implements PageableViewNode {
@ -72,9 +71,19 @@ export class TagNode extends ViewRefNode implements PageableVi
const item = new TreeItem(this.label, TreeItemCollapsibleState.Collapsed);
item.id = this.id;
item.contextValue = ResourceType.Tag;
item.description = this.tag.annotation !== undefined ? emojify(this.tag.annotation) : '';
item.tooltip = `${this.tag.name}${
this.tag.annotation !== undefined ? `\n${emojify(this.tag.annotation)}` : ''
item.description = `${GitService.shortenSha(this.tag.sha, { force: true })}${Strings.pad(
GlyphChars.Dot,
2,
2
)}${emojify(this.tag.message)}`;
item.tooltip = `${this.tag.name}${Strings.pad(GlyphChars.Dash, 2, 2)}${GitService.shortenSha(this.tag.sha, {
force: true
})}\n${this.tag.formatDateFromNow()} (${this.tag.formatDate(TagDateFormatting.dateFormat)})\n\n${emojify(
this.tag.message
)}${
this.tag.commitDate != null && this.tag.date !== this.tag.commitDate
? `\n${this.tag.formatCommitDateFromNow()} (${this.tag.formatCommitDate(TagDateFormatting.dateFormat)})`
: ''
}`;
return item;

Načítá se…
Zrušit
Uložit