Reworks branch parsing to include tracking info

Reworks current branch retrieval for better performance
This commit is contained in:
Eric Amodio
2017-09-04 01:32:43 -04:00
parent 2bba14260f
commit 5a2bd02402
5 changed files with 77 additions and 23 deletions

View File

@@ -10,6 +10,7 @@ import * as iconv from 'iconv-lite';
export { IGit }; export { IGit };
export * from './models/models'; export * from './models/models';
export * from './parsers/blameParser'; export * from './parsers/blameParser';
export * from './parsers/branchParser';
export * from './parsers/diffParser'; export * from './parsers/diffParser';
export * from './parsers/logParser'; export * from './parsers/logParser';
export * from './parsers/stashParser'; export * from './parsers/stashParser';
@@ -33,10 +34,11 @@ const GitWarnings = [
/no such path/, /no such path/,
/does not have any commits/, /does not have any commits/,
/Path \'.*?\' does not exist in/, /Path \'.*?\' does not exist in/,
/Path \'.*?\' exists on disk, but not in/ /Path \'.*?\' exists on disk, but not in/,
/no upstream configured for branch/
]; ];
async function gitCommand(options: { cwd: string, encoding?: string }, ...args: any[]) { async function gitCommand(options: { cwd: string, encoding?: string, onError?: (ex: Error) => string | undefined }, ...args: any[]) {
try { try {
// Fixes https://github.com/eamodio/vscode-gitlens/issues/73 // Fixes https://github.com/eamodio/vscode-gitlens/issues/73
// See https://stackoverflow.com/questions/4144417/how-to-handle-asian-characters-in-file-names-in-git-on-os-x // See https://stackoverflow.com/questions/4144417/how-to-handle-asian-characters-in-file-names-in-git-on-os-x
@@ -50,6 +52,11 @@ async function gitCommand(options: { cwd: string, encoding?: string }, ...args:
return iconv.decode(Buffer.from(s, 'binary'), opts.encoding); return iconv.decode(Buffer.from(s, 'binary'), opts.encoding);
} }
catch (ex) { catch (ex) {
if (options.onError !== undefined) {
const result = options.onError(ex);
if (result !== undefined) return result;
}
const msg = ex && ex.toString(); const msg = ex && ex.toString();
if (msg) { if (msg) {
for (const warning of GitWarnings) { for (const warning of GitWarnings) {
@@ -168,15 +175,27 @@ export class Git {
return gitCommand({ cwd: root }, ...params, `--`, file); return gitCommand({ cwd: root }, ...params, `--`, file);
} }
static branch(repoPath: string, all: boolean) { static branch(repoPath: string, options: { all: boolean } = { all: false }) {
const params = [`branch`]; const params = [`branch`, `-vv`];
if (all) { if (options.all) {
params.push(`-a`); params.push(`-a`);
} }
return gitCommand({ cwd: repoPath }, ...params); return gitCommand({ cwd: repoPath }, ...params);
} }
static branch_current(repoPath: string) {
const params = [`rev-parse`, `--abbrev-ref`, `--symbolic-full-name`, `@`, `@{u}`];
const onError = (ex: Error) => {
if (/no upstream configured for branch/.test(ex && ex.toString())) {
return ex.message.split('\n')[0];
}
return undefined;
};
return gitCommand({ cwd: repoPath, onError: onError }, ...params);
}
static checkout(repoPath: string, fileName: string, sha: string) { static checkout(repoPath: string, fileName: string, sha: string) {
const [file, root] = Git.splitPath(fileName, repoPath); const [file, root] = Git.splitPath(fileName, repoPath);

View File

@@ -5,15 +5,9 @@ export class GitBranch {
current: boolean; current: boolean;
name: string; name: string;
remote: boolean; remote: boolean;
tracking?: string;
constructor(branch: string) { constructor(public readonly repoPath: string, branch: string, current: boolean = false, tracking?: string) {
branch = branch.trim();
if (branch.startsWith('* ')) {
branch = branch.substring(2);
this.current = true;
}
if (branch.startsWith('remotes/')) { if (branch.startsWith('remotes/')) {
branch = branch.substring(8); branch = branch.substring(8);
this.remote = true; this.remote = true;
@@ -24,6 +18,24 @@ export class GitBranch {
branch = branch.substring(0, index); branch = branch.substring(0, index);
} }
this.current = current;
this.name = branch; this.name = branch;
this.tracking = tracking;
}
getName(): string {
return this.remote
? this.name.substring(this.name.indexOf('/') + 1)
: this.name;
}
getRemote(): string | undefined {
if (this.remote) return GitBranch.getRemote(this.name);
if (this.tracking !== undefined) return GitBranch.getRemote(this.tracking);
return undefined;
}
static getRemote(branch: string): string {
return branch.substring(0, branch.indexOf('/'));
} }
} }

View File

@@ -0,0 +1,24 @@
'use strict';
import { GitBranch } from './../git';
const branchWithTrackingRegex = /^(\*?)\s+(.+?)\s+([0-9,a-f]+)\s+(?:\[(.*?\/.*?)(?:\:|\]))?/gm;
export class GitBranchParser {
static parse(data: string, repoPath: string): GitBranch[] | undefined {
if (!data) return undefined;
const branches: GitBranch[] = [];
let match: RegExpExecArray | null = null;
do {
match = branchWithTrackingRegex.exec(data);
if (match == null) break;
branches.push(new GitBranch(repoPath, match[2], match[1] === '*', match[4]));
} while (match != null);
if (!branches.length) return undefined;
return branches;
}
}

View File

@@ -3,7 +3,7 @@ import { Functions, Iterables, Objects } from './system';
import { Disposable, Event, EventEmitter, FileSystemWatcher, Location, Position, Range, TextDocument, TextDocumentChangeEvent, TextEditor, Uri, workspace } from 'vscode'; import { Disposable, Event, EventEmitter, FileSystemWatcher, Location, Position, Range, TextDocument, TextDocumentChangeEvent, TextEditor, Uri, workspace } from 'vscode';
import { IConfig } from './configuration'; import { IConfig } from './configuration';
import { DocumentSchemes, ExtensionKey, GlyphChars } from './constants'; import { DocumentSchemes, ExtensionKey, GlyphChars } from './constants';
import { Git, GitAuthor, GitBlame, GitBlameCommit, GitBlameLine, GitBlameLines, GitBlameParser, GitBranch, GitCommit, GitDiff, GitDiffChunkLine, GitDiffParser, GitLog, GitLogCommit, GitLogParser, GitRemote, GitStash, GitStashParser, GitStatus, GitStatusFile, GitStatusParser, IGit, setDefaultEncoding } from './git/git'; import { Git, GitAuthor, GitBlame, GitBlameCommit, GitBlameLine, GitBlameLines, GitBlameParser, GitBranch, GitBranchParser, GitCommit, GitDiff, GitDiffChunkLine, GitDiffParser, GitLog, GitLogCommit, GitLogParser, GitRemote, GitStash, GitStashParser, GitStatus, GitStatusFile, GitStatusParser, IGit, setDefaultEncoding } from './git/git';
import { GitUri, IGitCommitInfo, IGitUriData } from './git/gitUri'; import { GitUri, IGitCommitInfo, IGitUriData } from './git/gitUri';
import { Logger } from './logger'; import { Logger } from './logger';
import * as fs from 'fs'; import * as fs from 'fs';
@@ -572,17 +572,16 @@ export class GitService extends Disposable {
async getBranch(repoPath: string): Promise<GitBranch | undefined> { async getBranch(repoPath: string): Promise<GitBranch | undefined> {
Logger.log(`getBranch('${repoPath}')`); Logger.log(`getBranch('${repoPath}')`);
const data = await Git.branch(repoPath, false); const data = await Git.branch_current(repoPath);
const branches = data.split('\n').filter(_ => !!_).map(_ => new GitBranch(_)); const branch = data.split('\n');
return branches.find(_ => _.current); return new GitBranch(repoPath, branch[0], true, branch[1]);
} }
async getBranches(repoPath: string): Promise<GitBranch[]> { async getBranches(repoPath: string): Promise<GitBranch[]> {
Logger.log(`getBranches('${repoPath}')`); Logger.log(`getBranches('${repoPath}')`);
const data = await Git.branch(repoPath, true); const data = await Git.branch(repoPath, { all: true });
const branches = data.split('\n').filter(_ => !!_).map(_ => new GitBranch(_)); return GitBranchParser.parse(data, repoPath) || [];
return branches;
} }
getCacheEntryKey(fileName: string): string; getCacheEntryKey(fileName: string): string;

View File

@@ -4,7 +4,7 @@ import { QuickPickItem, QuickPickOptions, Uri, window } from 'vscode';
import { Commands, CopyMessageToClipboardCommandArgs, CopyShaToClipboardCommandArgs, DiffWithPreviousCommandArgs, DiffWithWorkingCommandArgs, ShowQuickCommitDetailsCommandArgs, ShowQuickCommitFileDetailsCommandArgs, ShowQuickFileHistoryCommandArgs } from '../commands'; import { Commands, CopyMessageToClipboardCommandArgs, CopyShaToClipboardCommandArgs, DiffWithPreviousCommandArgs, DiffWithWorkingCommandArgs, ShowQuickCommitDetailsCommandArgs, ShowQuickCommitFileDetailsCommandArgs, ShowQuickFileHistoryCommandArgs } from '../commands';
import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, KeyCommandQuickPickItem, OpenFileCommandQuickPickItem } from './common'; import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, KeyCommandQuickPickItem, OpenFileCommandQuickPickItem } from './common';
import { GlyphChars } from '../constants'; import { GlyphChars } from '../constants';
import { GitBranch, GitLog, GitLogCommit, GitService, GitUri, RemoteResource } from '../gitService'; import { GitLog, GitLogCommit, GitService, GitUri, RemoteResource } from '../gitService';
import { Keyboard, KeyCommand, KeyNoopCommand } from '../keyboard'; import { Keyboard, KeyCommand, KeyNoopCommand } from '../keyboard';
import { OpenRemotesCommandQuickPickItem } from './remotes'; import { OpenRemotesCommandQuickPickItem } from './remotes';
import * as moment from 'moment'; import * as moment from 'moment';
@@ -128,11 +128,11 @@ export class CommitFileDetailsQuickPick {
const remotes = Arrays.uniqueBy(await git.getRemotes(commit.repoPath), _ => _.url, _ => !!_.provider); const remotes = Arrays.uniqueBy(await git.getRemotes(commit.repoPath), _ => _.url, _ => !!_.provider);
if (remotes.length) { if (remotes.length) {
if (commit.workingFileName && commit.status !== 'D') { if (commit.workingFileName && commit.status !== 'D') {
const branch = await git.getBranch(commit.repoPath || git.repoPath) as GitBranch; const branch = await git.getBranch(commit.repoPath || git.repoPath);
items.push(new OpenRemotesCommandQuickPickItem(remotes, { items.push(new OpenRemotesCommandQuickPickItem(remotes, {
type: 'file', type: 'file',
fileName: commit.workingFileName, fileName: commit.workingFileName,
branch: branch.name branch: branch!.name
} as RemoteResource, currentCommand)); } as RemoteResource, currentCommand));
} }
if (!stash) { if (!stash) {