@ -0,0 +1,4 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="256" height="256" viewBox="0 0 14 16" xml:space="preserve"> | |||
<path fill="#FFFFFF" fill-rule="evenodd" d="M10.86 7c-.45-1.72-2-3-3.86-3-1.86 0-3.41 1.28-3.86 3H0v2h3.14c.45 1.72 2 3 3.86 3 1.86 0 3.41-1.28 3.86-3H14V7h-3.14zM7 10.2c-1.22 0-2.2-.98-2.2-2.2 0-1.22.98-2.2 2.2-2.2 1.22 0 2.2.98 2.2 2.2 0 1.22-.98 2.2-2.2 2.2z"></path> | |||
</svg> |
@ -0,0 +1,4 @@ | |||
<?xml version="1.0" encoding="utf-8"?> | |||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="256" height="256" viewBox="0 0 14 16" xml:space="preserve"> | |||
<path fill="#00000" fill-rule="evenodd" d="M10.86 7c-.45-1.72-2-3-3.86-3-1.86 0-3.41 1.28-3.86 3H0v2h3.14c.45 1.72 2 3 3.86 3 1.86 0 3.41-1.28 3.86-3H14V7h-3.14zM7 10.2c-1.22 0-2.2-.98-2.2-2.2 0-1.22.98-2.2 2.2-2.2 1.22 0 2.2.98 2.2 2.2 0 1.22-.98 2.2-2.2 2.2z"></path> | |||
</svg> |
@ -0,0 +1,30 @@ | |||
'use strict'; | |||
import { TextEditor, TextEditorEdit, Uri, window } from 'vscode'; | |||
import { Commands, EditorCommand, getCommandUri } from './common'; | |||
import { GitExplorer } from '../views/gitExplorer'; | |||
import { GitService, GitUri } from '../gitService'; | |||
import { Messages } from '../messages'; | |||
import { Logger } from '../logger'; | |||
export class ShowStashListCommand extends EditorCommand { | |||
constructor(private git: GitService, private explorer: GitExplorer) { | |||
super(Commands.ShowStashList); | |||
} | |||
async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri) { | |||
uri = getCommandUri(uri, editor); | |||
try { | |||
const repoPath = await this.git.getRepoPathFromUri(uri); | |||
if (!repoPath) return Messages.showNoRepositoryWarningMessage(`Unable to show stashed changes`); | |||
this.explorer.addStash(new GitUri(uri, { repoPath: repoPath, fileName: uri!.fsPath })); | |||
return undefined; | |||
} | |||
catch (ex) { | |||
Logger.error(ex, 'ShowStashListCommand'); | |||
return window.showErrorMessage(`Unable to show stash list. See output channel for more details`); | |||
} | |||
} | |||
} |
@ -0,0 +1,41 @@ | |||
'use strict'; | |||
import { Command, ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode'; | |||
import { Commands, DiffWithPreviousCommandArgs } from '../commands'; | |||
import { ExplorerNode, ResourceType } from './explorerNode'; | |||
import { GitCommit, GitService, GitUri, IGitStatusFile } from '../gitService'; | |||
export class CommitFileNode extends ExplorerNode { | |||
readonly resourceType: ResourceType = 'commit-file'; | |||
command: Command; | |||
constructor(public status: IGitStatusFile, public commit: GitCommit, uri: GitUri, context: ExtensionContext, git: GitService) { | |||
super(uri, context, git); | |||
this.command = { | |||
title: 'Compare File with Previous', | |||
command: Commands.DiffWithPrevious, | |||
arguments: [ | |||
GitUri.fromFileStatus(this.status, this.commit.repoPath), | |||
{ | |||
commit: commit, | |||
showOptions: { | |||
preserveFocus: true, | |||
preview: true | |||
} | |||
} as DiffWithPreviousCommandArgs | |||
] | |||
}; | |||
} | |||
getChildren(): Promise<ExplorerNode[]> { | |||
return Promise.resolve([]); | |||
} | |||
getTreeItem(): TreeItem { | |||
const item = new TreeItem(`${GitUri.getFormattedPath(this.status.fileName)}`, TreeItemCollapsibleState.None); | |||
item.contextValue = this.resourceType; | |||
item.command = this.command; | |||
return item; | |||
} | |||
} |
@ -0,0 +1,54 @@ | |||
'use strict'; | |||
import { Iterables } from '../system'; | |||
import { Command, ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode'; | |||
import { CommitFileNode } from './commitFileNode'; | |||
import { ExplorerNode, ResourceType } from './explorerNode'; | |||
import { CommitFormatter, GitCommit, GitService, GitUri } from '../gitService'; | |||
export class CommitNode extends ExplorerNode { | |||
readonly resourceType: ResourceType = 'commit'; | |||
command: Command; | |||
constructor(public commit: GitCommit, uri: GitUri, context: ExtensionContext, git: GitService) { | |||
super(uri, context, git); | |||
// this.command = { | |||
// title: 'Compare File with Previous', | |||
// command: Commands.DiffWithPrevious, | |||
// arguments: [ | |||
// Uri.file(commit.uri.fsPath), | |||
// { | |||
// commit: commit, | |||
// showOptions: { | |||
// preserveFocus: true, | |||
// preview: true | |||
// } | |||
// } as DiffWithPreviousCommandArgs | |||
// ] | |||
// }; | |||
} | |||
async getChildren(): Promise<ExplorerNode[]> { | |||
const log = await this.git.getLogForRepo(this.commit.repoPath, this.commit.sha, 1); | |||
if (log === undefined) return []; | |||
const commit = Iterables.first(log.commits.values()); | |||
if (commit === undefined) return []; | |||
return [...Iterables.map(commit.fileStatuses, s => new CommitFileNode(s, commit, this.uri, this.context, this.git))]; | |||
} | |||
getTreeItem(): TreeItem { | |||
const label = CommitFormatter.fromTemplate(this.git.config.explorer.commitFormat, this.commit, this.git.config.defaultDateFormat); | |||
const item = new TreeItem(label, TreeItemCollapsibleState.Collapsed); | |||
item.contextValue = this.resourceType; | |||
item.iconPath = { | |||
dark: this.context.asAbsolutePath('images/dark/icon-commit.svg'), | |||
light: this.context.asAbsolutePath('images/light/icon-commit.svg') | |||
}; | |||
item.command = this.command; | |||
return item; | |||
} | |||
} |
@ -0,0 +1,15 @@ | |||
'use strict'; | |||
import { ExtensionContext, TreeItem } from 'vscode'; | |||
import { GitService, GitUri } from '../gitService'; | |||
export declare type ResourceType = 'status' | 'branches' | 'repository' | 'branch-history' | 'file-history' | 'stash-history' | 'commit' | 'stash-commit' | 'commit-file'; | |||
export abstract class ExplorerNode { | |||
abstract readonly resourceType: ResourceType; | |||
constructor(public uri: GitUri, protected context: ExtensionContext, protected git: GitService) { } | |||
abstract getChildren(): ExplorerNode[] | Promise<ExplorerNode[]>; | |||
abstract getTreeItem(): TreeItem | Promise<TreeItem>; | |||
} |
@ -0,0 +1,29 @@ | |||
'use strict'; | |||
import { Iterables } from '../system'; | |||
import { ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode'; | |||
import { CommitNode } from './commitNode'; | |||
import { ExplorerNode, ResourceType } from './explorerNode'; | |||
import { GitService, GitUri } from '../gitService'; | |||
export class FileHistoryNode extends ExplorerNode { | |||
static readonly rootType: ResourceType = 'file-history'; | |||
readonly resourceType: ResourceType = 'file-history'; | |||
constructor(uri: GitUri, context: ExtensionContext, git: GitService) { | |||
super(uri, context, git); | |||
} | |||
async getChildren(): Promise<CommitNode[]> { | |||
const log = await this.git.getLogForFile(this.uri.repoPath, this.uri.fsPath, this.uri.sha); | |||
if (log === undefined) return []; | |||
return [...Iterables.map(log.commits.values(), c => new CommitNode(c, this.uri, this.context, this.git))]; | |||
} | |||
getTreeItem(): TreeItem { | |||
const item = new TreeItem(`History of ${this.uri.getFormattedPath()}`, TreeItemCollapsibleState.Expanded); | |||
item.contextValue = this.resourceType; | |||
return item; | |||
} | |||
} |
@ -0,0 +1,62 @@ | |||
'use strict'; | |||
import { Event, EventEmitter, ExtensionContext, TreeDataProvider, TreeItem, Uri, window } from 'vscode'; | |||
import { UriComparer } from '../comparers'; | |||
import { ExplorerNode, FileHistoryNode, RepositoryNode, ResourceType, StashNode } from './gitExplorerNodes'; | |||
import { GitService, GitUri } from '../gitService'; | |||
export * from './gitExplorerNodes'; | |||
export class GitExplorer implements TreeDataProvider<ExplorerNode> { | |||
private _onDidChangeTreeData = new EventEmitter<ExplorerNode>(); | |||
public get onDidChangeTreeData(): Event<ExplorerNode> { | |||
return this._onDidChangeTreeData.event; | |||
} | |||
private _roots: ExplorerNode[] = []; | |||
constructor(private context: ExtensionContext, private git: GitService) { | |||
const editor = window.activeTextEditor; | |||
const uri = (editor !== undefined && editor.document !== undefined) | |||
? new GitUri(editor.document.uri, { repoPath: git.repoPath, fileName: editor.document.uri.fsPath }) | |||
: new GitUri(Uri.file(git.repoPath), { repoPath: git.repoPath, fileName: git.repoPath }); | |||
this._roots.push(new RepositoryNode(uri, context, git)); | |||
} | |||
async getTreeItem(node: ExplorerNode): Promise<TreeItem> { | |||
return node.getTreeItem(); | |||
} | |||
async getChildren(node?: ExplorerNode): Promise<ExplorerNode[]> { | |||
if (this._roots.length === 0) return []; | |||
if (node === undefined) return this._roots; | |||
return node.getChildren(); | |||
} | |||
addHistory(uri: GitUri) { | |||
this._add(uri, FileHistoryNode); | |||
} | |||
addStash(uri: GitUri) { | |||
this._add(uri, StashNode); | |||
} | |||
private _add<T extends ExplorerNode>(uri: GitUri, type: { new (uri: GitUri, context: ExtensionContext, git: GitService): T, rootType: ResourceType }) { | |||
if (!this._roots.some(_ => _.resourceType === type.rootType && UriComparer.equals(uri, _.uri))) { | |||
this._roots.push(new type(uri, this.context, this.git)); | |||
} | |||
this._onDidChangeTreeData.fire(); | |||
} | |||
clear() { | |||
this._roots = []; | |||
this._onDidChangeTreeData.fire(); | |||
} | |||
refresh() { | |||
this._onDidChangeTreeData.fire(); | |||
} | |||
} |
@ -0,0 +1,9 @@ | |||
'use strict'; | |||
export * from './explorerNode'; | |||
export * from './commitFileNode'; | |||
export * from './commitNode'; | |||
export * from './fileHistoryNode'; | |||
export * from './repositoryNode'; | |||
export * from './stashCommitNode'; | |||
export * from './stashNode'; |
@ -0,0 +1,105 @@ | |||
'use strict'; | |||
import { Iterables, Strings } from '../system'; | |||
import { ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode'; | |||
import { CommitNode } from './commitNode'; | |||
import { GlyphChars } from '../constants'; | |||
import { ExplorerNode, ResourceType } from './explorerNode'; | |||
import { GitBranch, GitService, GitUri } from '../gitService'; | |||
import { StashNode } from './stashNode'; | |||
export class RepositoryNode extends ExplorerNode { | |||
static readonly rootType: ResourceType = 'repository'; | |||
readonly resourceType: ResourceType = 'repository'; | |||
constructor(uri: GitUri, context: ExtensionContext, git: GitService) { | |||
super(uri, context, git); | |||
} | |||
async getChildren(): Promise<ExplorerNode[]> { | |||
return [ | |||
new StatusNode(this.uri, this.context, this.git), | |||
new StashNode(this.uri, this.context, this.git), | |||
new BranchesNode(this.uri, this.context, this.git) | |||
]; | |||
} | |||
getTreeItem(): TreeItem { | |||
const item = new TreeItem(`Repository ${GlyphChars.Dash} ${this.uri.repoPath}`, TreeItemCollapsibleState.Expanded); | |||
item.contextValue = this.resourceType; | |||
return item; | |||
} | |||
} | |||
export class BranchesNode extends ExplorerNode { | |||
readonly resourceType: ResourceType = 'branches'; | |||
constructor(uri: GitUri, context: ExtensionContext, git: GitService) { | |||
super(uri, context, git); | |||
} | |||
async getChildren(): Promise<BranchHistoryNode[]> { | |||
const branches = await this.git.getBranches(this.uri.repoPath!); | |||
if (branches === undefined) return []; | |||
return [...Iterables.filterMap(branches.sort(_ => _.current ? 0 : 1), b => b.remote ? undefined : new BranchHistoryNode(b, this.uri, this.context, this.git))]; | |||
} | |||
getTreeItem(): TreeItem { | |||
const item = new TreeItem(`Branches`, TreeItemCollapsibleState.Collapsed); | |||
item.contextValue = this.resourceType; | |||
return item; | |||
} | |||
} | |||
export class BranchHistoryNode extends ExplorerNode { | |||
readonly resourceType: ResourceType = 'branch-history'; | |||
constructor(public branch: GitBranch, uri: GitUri, context: ExtensionContext, git: GitService) { | |||
super(uri, context, git); | |||
} | |||
async getChildren(): Promise<CommitNode[]> { | |||
const log = await this.git.getLogForRepo(this.uri.repoPath!, this.branch.name); | |||
if (log === undefined) return []; | |||
return [...Iterables.map(log.commits.values(), c => new CommitNode(c, this.uri, this.context, this.git))]; | |||
} | |||
getTreeItem(): TreeItem { | |||
const item = new TreeItem(`${this.branch.name}${this.branch.current ? ` ${GlyphChars.Dash} current` : ''}`, TreeItemCollapsibleState.Collapsed); | |||
item.contextValue = this.resourceType; | |||
return item; | |||
} | |||
} | |||
export class StatusNode extends ExplorerNode { | |||
readonly resourceType: ResourceType = 'status'; | |||
constructor(uri: GitUri, context: ExtensionContext, git: GitService) { | |||
super(uri, context, git); | |||
} | |||
async getChildren(): Promise<ExplorerNode[]> { | |||
return []; | |||
// const status = await this.git.getStatusForRepo(this.uri.repoPath!); | |||
// if (status === undefined) return []; | |||
// return [...Iterables.map(status.files, b => new CommitFile(b, this.uri, this.context, this.git))]; | |||
} | |||
async getTreeItem(): Promise<TreeItem> { | |||
const status = await this.git.getStatusForRepo(this.uri.repoPath!); | |||
let suffix = ''; | |||
if (status !== undefined) { | |||
suffix = ` ${GlyphChars.Dash} ${GlyphChars.ArrowUp} ${status.state.ahead} ${GlyphChars.ArrowDown} ${status.state.behind} ${Strings.pad(GlyphChars.Dot, 1, 1)} ${status.branch} ${GlyphChars.ArrowLeftRight} ${status.upstream}`; | |||
} | |||
const item = new TreeItem(`Status${suffix}`, TreeItemCollapsibleState.Collapsed); | |||
item.contextValue = this.resourceType; | |||
return item; | |||
} | |||
} |
@ -0,0 +1,40 @@ | |||
'use strict'; | |||
import { Command, ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode'; | |||
import { CommitFileNode } from './commitFileNode'; | |||
import { ExplorerNode, ResourceType } from './explorerNode'; | |||
import { CommitFormatter, GitService, GitStashCommit, GitUri } from '../gitService'; | |||
export class StashCommitNode extends ExplorerNode { | |||
readonly resourceType: ResourceType = 'stash-commit'; | |||
command: Command; | |||
constructor(public commit: GitStashCommit, uri: GitUri, context: ExtensionContext, git: GitService) { | |||
super(uri, context, git); | |||
// this.command = { | |||
// title: 'Show Stash Details', | |||
// command: Commands.ShowQuickCommitDetails, | |||
// arguments: [ | |||
// new GitUri(commit.uri, commit), | |||
// { | |||
// commit: commit, | |||
// sha: commit.sha | |||
// } as ShowQuickCommitDetailsCommandArgs | |||
// ] | |||
// }; | |||
} | |||
getChildren(): Promise<CommitFileNode[]> { | |||
return Promise.resolve((this.commit as GitStashCommit).fileStatuses.map(_ => new CommitFileNode(_, this.commit, this.uri, this.context, this.git))); | |||
} | |||
getTreeItem(): TreeItem { | |||
const label = CommitFormatter.fromTemplate(this.git.config.explorer.stashFormat, this.commit, this.git.config.defaultDateFormat); | |||
const item = new TreeItem(label, TreeItemCollapsibleState.Collapsed); | |||
item.contextValue = this.resourceType; | |||
item.command = this.command; | |||
return item; | |||
} | |||
} |
@ -0,0 +1,29 @@ | |||
'use strict'; | |||
import { Iterables } from '../system'; | |||
import { ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode'; | |||
import { ExplorerNode, ResourceType } from './explorerNode'; | |||
import { GitService, GitUri } from '../gitService'; | |||
import { StashCommitNode } from './stashCommitNode'; | |||
export class StashNode extends ExplorerNode { | |||
static readonly rootType: ResourceType = 'stash-history'; | |||
readonly resourceType: ResourceType = 'stash-history'; | |||
constructor(uri: GitUri, context: ExtensionContext, git: GitService) { | |||
super(uri, context, git); | |||
} | |||
async getChildren(): Promise<StashCommitNode[]> { | |||
const stash = await this.git.getStashList(this.uri.repoPath!); | |||
if (stash === undefined) return []; | |||
return [...Iterables.map(stash.commits.values(), c => new StashCommitNode(c, this.uri, this.context, this.git))]; | |||
} | |||
getTreeItem(): TreeItem { | |||
const item = new TreeItem(`Stashed Changes`, TreeItemCollapsibleState.Collapsed); | |||
item.contextValue = this.resourceType; | |||
return item; | |||
} | |||
} |