Skip to content

Commit

Permalink
Replaced several git commands with git extension API
Browse files Browse the repository at this point in the history
  • Loading branch information
ole1986 committed Nov 27, 2019
1 parent 8129209 commit b72180f
Show file tree
Hide file tree
Showing 24 changed files with 106 additions and 274 deletions.
2 changes: 0 additions & 2 deletions src/adapter/parsers/refs/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ export * from './types';
import { injectable, multiInject } from 'inversify';
import { ILogService } from '../../../common/types';
import { Ref } from '../../../types';
// import { TYPES } from '../constants';
// import * as TYPES from '../types';
import { IRefsParser } from '../types';
import { IRefParser } from './types';

Expand Down
34 changes: 27 additions & 7 deletions src/adapter/repository/factory.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,49 @@
import { inject, injectable } from 'inversify';
import * as md5 from 'md5';
import { Uri } from 'vscode';
import { IServiceContainer } from '../../ioc/types';
import { IGitService, IGitServiceFactory } from '../../types';
import { IGitCommandExecutor } from '../exec';
import { ILogParser } from '../parsers/types';
import { Git } from './git';
import { API } from './git.d';
import { IGitArgsService } from './types';

@injectable()
export class GitServiceFactory implements IGitServiceFactory {
private readonly gitServices = new Map<string, IGitService>();
private readonly gitServices = new Map<number, IGitService>();
private gitApi: API;
constructor(@inject(IGitCommandExecutor) private gitCmdExecutor: IGitCommandExecutor,
@inject(ILogParser) private logParser: ILogParser,
@inject(IGitArgsService) private gitArgsService: IGitArgsService,
@inject(IServiceContainer) private serviceContainer: IServiceContainer) {

this.gitApi = this.gitCmdExecutor.gitExtension.getAPI(1);
}
public async createGitService(workspaceRoot: string, resource: Uri | string): Promise<IGitService> {

public async createGitService(resource?: Uri | string): Promise<IGitService> {
const resourceUri = typeof resource === 'string' ? Uri.file(resource) : resource;
let repoIndex: number = -1;

if (!resourceUri) {
// no git service initialized, so take the selected as primary
repoIndex = this.gitApi.repositories.findIndex(x => x.ui.selected);
}

const id = md5(workspaceRoot + resourceUri.fsPath);
if (!this.gitServices.has(id)) {
this.gitServices.set(id, new Git(this.serviceContainer, workspaceRoot, resourceUri, this.gitCmdExecutor, this.logParser, this.gitArgsService));
if (repoIndex === -1) {
// find the correct repository from the given resource uri
this.gitApi.repositories.forEach((x, i) => {
if (resourceUri!.fsPath.startsWith(x.rootUri.fsPath)) {
if (repoIndex === -1 || x.rootUri.fsPath.length > this.gitApi.repositories[repoIndex].rootUri.fsPath.length) {
repoIndex = i;
}
}
});
}
return this.gitServices.get(id)!;

if (!this.gitServices.has(repoIndex)) {
this.gitServices.set(repoIndex, new Git(this.gitApi.repositories[repoIndex], this.serviceContainer, this.gitCmdExecutor, this.logParser, this.gitArgsService));
}

return this.gitServices.get(repoIndex)!;
}
}
166 changes: 42 additions & 124 deletions src/adapter/repository/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,66 +11,28 @@ import { ActionedUser, Branch, CommittedFile, FsUri, Hash, IGitService, LogEntri
import { IGitCommandExecutor } from '../exec';
import { IFileStatParser, ILogParser } from '../parsers/types';
import { ITEM_ENTRY_SEPARATOR, LOG_ENTRY_SEPARATOR, LOG_FORMAT_ARGS } from './constants';
import { API, Repository } from './git.d';
import { GitOriginType } from './index';
import { IGitArgsService } from './types';

@injectable()
export class Git implements IGitService {
private gitRootPath: string | undefined;
private knownGitRoots: Set<string>;
constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer,
private workspaceRoot: string,
private resource: Uri,
private gitApi: API;
constructor(private repo: Repository, @inject(IServiceContainer) private serviceContainer: IServiceContainer,
@inject(IGitCommandExecutor) private gitCmdExecutor: IGitCommandExecutor,
@inject(ILogParser) private logParser: ILogParser,
@inject(IGitArgsService) private gitArgsService: IGitArgsService) {
this.knownGitRoots = new Set<string>();
}
public getHashCode() {
return this.workspaceRoot;

this.gitApi = this.gitCmdExecutor.gitExtension.getAPI(1);
}

@cache('IGitService')
public async getGitRoot(): Promise<string> {
if (this.gitRootPath) {
return this.gitRootPath;
}
const gitRootPath = await this.gitCmdExecutor.exec(this.resource.fsPath, ...this.gitArgsService.getGitRootArgs());
return this.gitRootPath = gitRootPath.split(/\r?\n/g)[0].trim();
return this.repo.rootUri.fsPath;
}
@cache('IGitService', 5 * 60 * 1000)
public async getGitRoots(rootDirectory?: string): Promise<string[]> {
// Lets not enable support for sub modules for now.
if (rootDirectory && (this.knownGitRoots.has(rootDirectory) || this.knownGitRoots.has(Uri.file(rootDirectory).fsPath))) {
return [rootDirectory];
}
const rootDirectories: string[] = [];
if (rootDirectory) {
rootDirectories.push(rootDirectory);
} else {
const workspace = this.serviceContainer.get<IWorkspaceService>(IWorkspaceService);
const workspaceFolders = Array.isArray(workspace.workspaceFolders) ? workspace.workspaceFolders.map(item => item.uri.fsPath) : [];
rootDirectories.push(...workspaceFolders);
}
if (rootDirectories.length === 0) {
return [];
}

public async getGitRoots(): Promise<string[]> {
// Instead of traversing the directory structure for the entire workspace, use the Git extension API to get all repo paths
const git = this.gitCmdExecutor.gitExtension.getAPI(1);

const sourceControlFolders: string[] = git.repositories.map(repo => repo.rootUri.path);
sourceControlFolders.sort();
// gitFoldersList should be an Array of Arrays
const gitFoldersList = [sourceControlFolders];
const gitRoots = new Set<string>();
gitFoldersList
.reduce<string[]>((aggregate, items) => { aggregate.push(...items); return aggregate; }, [])
.forEach(item => {
gitRoots.add(item);
this.knownGitRoots.add(item);
});
return Array.from(gitRoots.values());
return this.gitApi.repositories.map(x => path.basename(x.rootUri.path));
}
public async getGitRelativePath(file: Uri | FsUri) {
if (!path.isAbsolute(file.fsPath)) {
Expand All @@ -79,16 +41,25 @@ export class Git implements IGitService {
const gitRoot: string = await this.getGitRoot();
return path.relative(gitRoot, file.fsPath).replace(/\\/g, '/');
}
@cache('IGitService', 10 * 1000)
public async getHeadHashes(): Promise<{ ref: string; hash: string }[]> {
const fullHashArgs = ['show-ref'];
const fullHashRefsOutput = await this.exec(...fullHashArgs);
return fullHashRefsOutput.split(/\r?\n/g)
.filter(line => line.length > 0)
.filter(line => line.indexOf('refs/heads/') > 0 || line.indexOf('refs/remotes/') > 0)
.map(line => line.trim().split(' '))
.filter(lineParts => lineParts.length > 1)
.map(hashAndRef => { return { ref: hashAndRef[1], hash: hashAndRef[0] }; });
public async getHeadHashes(): Promise<{ ref?: string; hash?: string }[]> {
return this.repo.state.refs.filter(x => x.type <= 1).map(x => { return { ref: x.name, hash: x.commit }; });
}
public async getBranches(): Promise<Branch[]> {
const currentBranchName = await this.getCurrentBranch();
const gitRootPath = this.repo.rootUri.fsPath;
const localBranches = this.repo.state.refs.filter(x => x.type === 0);

return localBranches.map(x => {
// tslint:disable-next-line:no-object-literal-type-assertion
return {
gitRoot: gitRootPath,
name: x.name,
current: currentBranchName === x.name
} as Branch;
});
}
public async getCurrentBranch(): Promise<string> {
return this.repo.state.HEAD!.name || '';
}

@cache('IGitService', 60 * 1000)
Expand Down Expand Up @@ -126,42 +97,6 @@ export class Git implements IGitService {
})
.sort((a, b) => a.name > b.name ? 1 : -1);
}

// tslint:disable-next-line:no-suspicious-comment
// TODO: We need a way of clearing this cache, if a new branch is created.
@cache('IGitService', 30 * 1000)
public async getBranches(): Promise<Branch[]> {
const output = await this.exec('branch');
const gitRootPath = await this.getGitRoot();
return output.split(/\r?\n/g)
.filter(line => line.trim())
.filter(line => line.length > 0)
.map(line => {
const isCurrent = line.startsWith('*');
const name = isCurrent ? line.substring(1).trim() : line.trim();
return {
gitRoot: gitRootPath,
name,
current: isCurrent
};
});
}
// tslint:disable-next-line:no-suspicious-comment
// TODO: We need a way of clearing this cache, if a new branch is created.
@cache('IGitService', 30 * 1000)
public async getCurrentBranch(): Promise<string> {
const args = this.gitArgsService.getCurrentBranchArgs();
const branch = await this.exec(...args);
return branch.split(/\r?\n/g)[0].trim();
}
@cache('IGitService')
public async getObjectHash(object: string): Promise<string> {
// Get the hash of the given ref
// E.g. git show --format=%H --shortstat remotes/origin/tyriar/xterm-v3
const args = this.gitArgsService.getObjectHashArgs(object);
const output = await this.exec(...args);
return output.split(/\r?\n/g)[0].trim();
}
@cache('IGitService')
public async getOriginType(): Promise<GitOriginType | undefined> {
const url = await this.getOriginUrl();
Expand All @@ -173,33 +108,23 @@ export class Git implements IGitService {
} else if (url.indexOf('visualstudio') > 0) {
return GitOriginType.vsts;
}

return undefined;
}
@cache('IGitService')
public async getOriginUrl(): Promise<string> {
try {
const remoteName = await this.exec('status', '--porcelain=v1', '-b', '--untracked-files=no').then((branchDetails) => {
const matchResult = branchDetails.match(/.*\.\.\.(.*?)\//);
return matchResult && matchResult[1] ? matchResult[1] : 'origin';
});
const currentBranchName = await this.getCurrentBranch();
const branch = await this.repo.getBranch(currentBranchName);

const url = await this.exec('remote', 'get-url', remoteName);
return url.substring(0, url.length - 1);
} catch {
return "";
if (branch.upstream) {
const remoteIndex = this.repo.state.remotes.findIndex(x => x.name === branch.upstream!.remote);
return this.repo.state.remotes[remoteIndex].fetchUrl || '';
}

return '';
}
public async getRefsContainingCommit(hash: string): Promise<string[]> {
const args = this.gitArgsService.getRefsContainingCommitArgs(hash);
const entries = await this.exec(...args);
return entries.split(/\r?\n/g)
.map(line => line.trim())
.filter(line => line.length > 0)
// Remove the '*' prefix from current branch
.map(line => line.startsWith('*') ? line.substring(1) : line)
// Remove the '->' from ref pointers (take first portion)
.map(ref => ref.indexOf(' ') ? ref.split(' ')[0].trim() : ref);
// tslint:disable-next-line:possible-timing-attack
return this.repo.state.refs.filter(x => x.commit === hash).map(x => x.name || '');
}
public async getLogEntries(pageIndex: number = 0, pageSize: number = 0, branch: string = '', searchText: string = '', file?: Uri, lineNumber?: number, author?: string): Promise<LogEntries> {
if (pageSize <= 0) {
Expand All @@ -208,7 +133,7 @@ export class Git implements IGitService {
pageSize = workspace.getConfiguration('gitHistory').get<number>('pageSize', 100);
}
const relativePath = file ? await this.getGitRelativePath(file) : undefined;
const args = await this.gitArgsService.getLogArgs(pageIndex, pageSize, branch, searchText, relativePath, lineNumber, author);
const args = this.gitArgsService.getLogArgs(pageIndex, pageSize, branch, searchText, relativePath, lineNumber, author);

const gitRootPathPromise = this.getGitRoot();
const outputPromise = this.exec(...args.logArgs);
Expand Down Expand Up @@ -243,7 +168,7 @@ export class Git implements IGitService {
const headHashes = await this.getHeadHashes();
const headHashesOnly = headHashes.map(item => item.hash);
// tslint:disable-next-line:prefer-type-cast
const headHashMap = new Map<string, string>(headHashes.map(item => [item.ref, item.hash] as [string, string]));
//const headHashMap = new Map<string, string>(headHashes.map(item => [item.ref, item.hash] as [string, string]));

items.forEach(async item => {
item.gitRoot = gitRepoPath;
Expand All @@ -256,14 +181,14 @@ export class Git implements IGitService {
if (!item.isLastCommit) {
return;
}
const refsContainingThisCommit = await this.getRefsContainingCommit(item.hash.full);
/*const refsContainingThisCommit = await this.getRefsContainingCommit(item.hash.full);
const hashesOfRefs = refsContainingThisCommit
.filter(ref => headHashMap.has(ref))
.map(ref => headHashMap.get(ref)!)
// tslint:disable-next-line:possible-timing-attack
.filter(hash => hash !== item.hash.full);
// If we have hashes other than current, then yes it has been merged
item.isThisLastCommitMerged = hashesOfRefs.length > 0;
item.isThisLastCommitMerged = hashesOfRefs.length > 0;*/
});

// tslint:disable-next-line:no-suspicious-comment
Expand All @@ -280,15 +205,6 @@ export class Git implements IGitService {
} as LogEntries;
}
@cache('IGitService')
public async getHash(hash: string): Promise<Hash> {
const hashes = await this.exec('show', '--format=%H-%h', '--no-patch', hash);
const parts = hashes.split(/\r?\n/g).filter(item => item.length > 0)[0].split('-');
return {
full: parts[0],
short: parts[1]
};
}
@cache('IGitService')
public async getCommitDate(hash: string): Promise<Date | undefined> {
const args = this.gitArgsService.getCommitDateArgs(hash);
const output = await this.exec(...args);
Expand Down Expand Up @@ -359,7 +275,9 @@ export class Git implements IGitService {
fsStream.end();
resolve(Uri.file(tmpFile));
} catch (ex) {
// tslint:disable-next-line:no-console
console.error('Git History: failed to get file contents (again)');
// tslint:disable-next-line:no-console
console.error(ex);
reject(ex);
}
Expand Down
6 changes: 0 additions & 6 deletions src/adapter/repository/gitArgsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,6 @@ import { GitLogArgs, IGitArgsService } from './types';
export class GitArgsService implements IGitArgsService {
constructor(private isWindows: boolean = /^win/.test(process.platform)) {}

public getGitRootArgs(): string[] {
return ['rev-parse', '--show-toplevel'];
}
public getCurrentBranchArgs(): string[] {
return ['rev-parse', '--abbrev-ref', 'HEAD'];
}
public getCommitDateArgs(hash: string) {
return ['show', `--format=${Helpers.GetCommitInfoFormatCode(CommitInfo.CommitterDateUnixTime)}`, hash];
}
Expand Down
2 changes: 0 additions & 2 deletions src/adapter/repository/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ export type GitLogArgs = {
export const IGitArgsService = Symbol('IGitArgsService');

export interface IGitArgsService {
getGitRootArgs(): string[];
getAuthorsArgs(): string[];
getCurrentBranchArgs(): string[];
getCommitDateArgs(hash: string): string[];
getCommitArgs(hash: string): string[];
getCommitParentHashesArgs(hash: string): string[];
Expand Down
10 changes: 0 additions & 10 deletions src/application/types/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,6 @@ export interface IWorkspaceService {
*/
readonly onDidChangeConfiguration: Event<ConfigurationChangeEvent>;

/**
* Returns the [workspace folder](#WorkspaceFolder) that contains a given uri.
* * returns `undefined` when the given uri doesn't match any workspace folder
* * returns the *input* when the given uri is a workspace folder itself
*
* @param uri An uri.
* @return A workspace folder or `undefined`
*/
getWorkspaceFolder(uri: Uri): WorkspaceFolder | undefined;

/**
* Returns a path that is relative to the workspace folder or folders.
*
Expand Down
3 changes: 0 additions & 3 deletions src/application/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ export class WorkspaceService implements IWorkspaceService {
public getConfiguration(section?: string, resource?: Uri): WorkspaceConfiguration {
return workspace.getConfiguration(section, resource);
}
public getWorkspaceFolder(uri: Uri): WorkspaceFolder | undefined {
return workspace.getWorkspaceFolder(uri);
}
public asRelativePath(pathOrUri: string | Uri, includeWorkspaceFolder?: boolean): string {
return workspace.asRelativePath(pathOrUri, includeWorkspaceFolder);
}
Expand Down
2 changes: 1 addition & 1 deletion src/commandHandlers/commit/compare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export class GitCompareCommitCommandHandler implements IGitCompareCommandHandler
await this.commandManager.executeCommand('setContext', 'git.commit.compare.view.show', true);
// display explorer view when running compare
await this.commandManager.executeCommand('workbench.view.explorer');
const gitService = await this.serviceContainer.get<IGitServiceFactory>(IGitServiceFactory).createGitService(commit.workspaceFolder, commit.logEntry.gitRoot);
const gitService = await this.serviceContainer.get<IGitServiceFactory>(IGitServiceFactory).createGitService(commit.logEntry.gitRoot);
const fileDiffs = await gitService.getDifferences(this.selectedCommit!.logEntry.hash.full, commit.logEntry.hash.full);
const compareCommit = new CompareCommitDetails(this.selectedCommit, commit, fileDiffs);
this.commitViewerFactory.getCompareCommitViewer().showCommitTree(compareCommit);
Expand Down
2 changes: 1 addition & 1 deletion src/commandHandlers/commit/gitBranchFromCommit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class GitBranchFromCommitCommandHandler implements IGitBranchFromCommitCo
return;
}

const gitService = await this.serviceContainer.get<IGitServiceFactory>(IGitServiceFactory).createGitService(commit.workspaceFolder, commit.logEntry.gitRoot);
const gitService = await this.serviceContainer.get<IGitServiceFactory>(IGitServiceFactory).createGitService(commit.logEntry.gitRoot);
gitService.createBranch(newBranchName, commit.logEntry.hash.full)
.catch(async err => {
const currentBranchName = await gitService.getCurrentBranch();
Expand Down
Loading

0 comments on commit b72180f

Please sign in to comment.